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-phone → verify-code → change-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
cpfao 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.