Skip to main content

Grill Decisions — #02 ATO

Data: 2026-07-07
Ferramenta: grill-with-docs skill


Decisão 1: Comportamento do verify-email/verify-phone

Pergunta: O endpoint verify-email (e verify-phone) aceitam email/telefone arbitrário no body — diferente de verify-recover-method que faz DB-lookup do contato cadastrado. Qual deve ser o comportamento correto?

Opções:

  • DB-lookup obrigatório (ignorar body, sempre enviar para contato cadastrado)
  • Body com validação (aceitar body, validar que pertence ao CPF)
  • Descontinuar endpoint (unificar no verify-recover-method)

Decisão: Body com validação

Trade-off: Manter endpoints separados para update-contact (pós-login) vs recover (sem login) permite que cada fluxo tenha suas próprias regras. O update-contact precisa aceitar contato NOVO (não cadastrado ainda). A validação vem do JWT — só usuário logado pode disparar OTP para novo contato.


Decisão 2: Separação de escopo dos tokens

Pergunta: Como resolver o compartilhamento indevido de tokens entre fluxos? verify-code (update-contact) e change-password usam a mesma chave de cache VerificationCodeModelInCache.

Opções:

  • Separar chaves de cache
  • change-password exigir JWT
  • Ambas
  • Token com scope/claim

Decisão: Separar chaves de cache

Trade-off: Simples, uma linha de código. verify-code (update-contact) → UpdateContactToken-{cpf}, change-password continua em VerificationCodeModelInCache-{cpf}. Se no futuro precisarmos de JWT no change-password, adicionamos depois. Defesa suficiente para quebrar o chain.


Decisão 3: VerifyCodeEasy (backdoor)

Pergunta: O VerifyCodeEasy (código previsível por data) é um bypass de OTP em verify-code E verify-code-recover. Remover agora ou tratar separadamente?

Opções:

  • Remover já (neste fix)
  • Tratar em fix separado

Decisão: Tratar em fix separado

Trade-off: É vulnerabilidade crítica mas independente do chain de ATO. Merece análise própria (impacto em QA, testes, flag de ambiente). Incluir agora diluiria o foco e aumentaria risco de regressão desnecessária.


Decisão 4: Recover sub-flow (update contact durante recover)

Pergunta: O recover sub-flow (atualizar contato durante recuperação de senha) é um caso especial: usuário não logado, mas precisa trocar telefone/email. No app, ele chama verify-phoneverify-codechange-cellphone. Mas change-cellphone exige JWT no backend. Como tratar?

Opções:

  • Remover sub-fluxo do recover
  • Novo endpoint para recover sub-flow
  • Manter como está (fora do escopo)

Decisão: Remover sub-fluxo do recover

Trade-off: Perde-se a funcionalidade de atualizar contato durante recover. Usuário sem contato válido precisa contatar suporte. Ganha-se: simplificação do app, remoção de inconsistência de auth, redução de superfície de ataque. Se dados mostrarem alto volume de usuários nessa situação, reavaliar com endpoint dedicado.


Decisão 5: Escopo app vs backend

Pergunta: O app Flutter precisará de alterações. Quem faz?

Opções:

  • Apenas backend neste fix
  • Backend + análise do app

Decisão: Backend + análise do app

Trade-off: O plano inclui mapeamento completo do que precisa mudar no app (arquivos, fluxos), sem implementar. Time mobile implementa em card separado. Evita bottleneck e mantém responsabilidades claras.


Decisão 6: Validação CPF vs userId (JWT)

Pergunta: Como validar que o CPF do body pertence ao usuário autenticado, sendo que o JWT não tem claim cpf?

Descoberta: JWT contém apenas sub (userId), profile, unique_name, schema. Sem claim cpf.

Opções:

  • GetByIdAsync + comparar Cpf (1 query extra)
  • Adicionar cpf ao JWT (muda TokenService)
  • Trocar cache key de CPF para userId

Decisão: GetByIdAsync(userId)UserModel.Cpf comparar com input.Cpf

Trade-off: 1 query extra por chamada (update-contact é infrequente). GetByIdAsync já existe e inclui .Profile (necessário para bypass Admin). Não altera TokenService nem contratos existentes.


Decisão 7: Bypass para perfis Admin

Pergunta: E se o CPF do body não bater com o userId? Perfis Admin podem validar CPF de terceiros?

Opções:

  • Sempre rejeitar mismatch
  • Permitir para perfis Admin (Coezzion_Administrador = 1, Administrador = 20)

Decisão: Permitir bypass para ProfileType.Coezzion_Administrador (1) e ProfileType.Administrador (20)

Trade-off: Suporte/Admin pode auxiliar usuários na atualização de contato. Mais permissivo, mas restrito aos 2 perfis administrativos (não inclui Coezzion_Usuario nem Easy).


Decisão 8: Restaurar validação de token no ChangeEmail

Pergunta: ChangeEmail tem validação de token de cache COMENTADA. Usuário logado pode trocar email sem OTP. Incluir no #02?

Opções:

  • Incluir no #02 (~5 linhas, paridade com ChangeCellPhone)
  • Card separado (falha distinta, exige JWT)

Decisão: Incluir no #02

Trade-off: Já estamos mexendo no VerifyCodeService (cache keys). Restaurar validação é barato. Sem isso, o JWT que adicionamos no verify-email/verify-code serve apenas para autorizar o envio de OTP, mas o change-email aceitaria qualquer token (ou token nenhum). Defense-in-depth.


Decisão 9: Verificar flags Confirmed no VerifyRecoverMethod

Pergunta: verify-recover-method não verifica CellPhoneConfirmed/EmailConfirmed. GetRecoverMethods filtra, mas chamada direta burla. Corrigir?

Opções:

  • Adicionar verificação (~2 linhas)
  • Fora do escopo (contato não confirmado ainda pertence ao CPF correto via DB-lookup)

Decisão: Adicionar verificação

Trade-off: Consistência com GetRecoverMethods. Se o recover flow decide que contatos não confirmados não devem ser usados para recuperação, o envio de OTP também deve respeitar. Evita que atacante explore contatos antigos/não verificados.


Decisão 10: Corrigir logging em ValidateVerificationCode

Pergunta: Log registra input.Cpf em vez de input.Contact. Corrigir?

Opções:

  • Corrigir (1 linha, mesmo arquivo)
  • Não (não afeta segurança)

Decisão: Corrigir

Trade-off: Custo zero. Melhora diagnóstico em produção. Já estamos alterando VerifyCodeService.cs.


Decisão 11: Remover x-api-key dos endpoints que ganham JWT

Pergunta: verify-email/phone/code ganham [Authorize(JwtBearer)]. Manter [UseApiKey("SendMessage")]?

Verificação: change-cellphone e change-email já são JWT-only (sem [UseApiKey]). Padrão do codebase: endpoints autenticados usam JWT, endpoints públicos usam x-api-key.

Opções:

  • Manter x-api-key + JWT (defense in depth, mas mistura mecanismos)
  • Remover x-api-key (padrão consistente com change-cellphone/change-email)

Decisão: Remover [UseApiKey] de verify-phone, verify-email, verify-code

Trade-off: Padrão consistente. App continua enviando x-api-key (ignorado pelo backend). Sem breaking change. JWT é suficiente para autorização de endpoints autenticados.