Análise #02 ATO — verify-email/verify-phone
Data: 2026-07-07
Fonte: pentest.md, recover.md, update-contact.md, recover-anl.md
Codebase: coezzion-service-auth, coezzion_vendas_app
1. O Chain do ATO
POST /api/auth/verify-email {cpf:"vitima", email:"atacante@x.com"}
├─ Auth: x-api-key apenas (sem JWT)
├─ Email do body → destinatário do OTP (sem DB-lookup)
└─ OTP enviado para atacante@x.com ✓
POST /api/auth/verify-code {cpf:"vitima", email:"atacante@x.com", verificationCode:"123456"}
├─ Auth: x-api-key apenas
├─ Valida OTP no cache
└─ Gera token → salva em VerificationCodeModelInCache-{cpf} ← MESMA chave do recover
POST /api/auth/change-password {cpf:"vitima", token, password:"novaSenha1"}
├─ Auth: x-api-key apenas
├─ Lê token de VerificationCodeModelInCache-{cpf}
└─ Senha da vítima alterada ✓ → ATO completo
2. Endpoints Mapeados
Arquivo central: src/Auth.API/Controllers/PasswordController.cs
Fluxo de Update-Contact (pós-login)
| Endpoint | Auth Backend | Auth App | Comportamento |
|---|---|---|---|
POST verify-phone | [UseApiKey] | JWT (Authorization Bearer) | Envia OTP para cellPhone do body |
POST verify-email | [UseApiKey] | JWT (Authorization Bearer) | Envia OTP para email do body |
POST verify-code | [UseApiKey] | JWT (Authorization Bearer) | Valida OTP, gera token |
POST change-cellphone | [Authorize(JwtBearer)] + [UseApiKey] | JWT | Atualiza telefone |
POST change-email | [Authorize(JwtBearer)] + [UseApiKey] | JWT | Atualiza email |
Fluxo de Recover (sem login)
| Endpoint | Auth | Comportamento |
|---|---|---|
POST recover-methods/{cpf} | [UseApiKey] | Lista contatos mascarados (DB-lookup) |
POST verify-recover-method | [UseApiKey] | Envia OTP para contato cadastrado (DB-lookup) |
POST verify-code-recover | [UseApiKey] | Valida OTP, gera token (DB-lookup do contato) |
POST change-password | [UseApiKey] | Troca senha com token do cache |
Recover Sub-Flow (sem login, update-contact durante recover) → REMOVIDO
| Endpoint | Auth | Problema |
|---|---|---|
POST verify-phone | [UseApiKey] | Mesmo endpoint do update-contact, sem JWT |
POST verify-email | [UseApiKey] | Mesmo endpoint do update-contact, sem JWT |
POST verify-code | [UseApiKey] | Mesmo endpoint do update-contact, sem JWT |
POST change-cellphone | [Authorize(JwtBearer)] | Exige JWT que o sub-flow não tem ← inconsistente |
3. Causa Raiz Detalhada
3.1 verify-email/verify-phone aceitam contato arbitrário
Arquivo: src/Auth.API/Application/Services/Implementations/VerifyCodeService.cs
Método VerifyEmailAsync → VerifyContact(ContactType.Email, input.Cpf, input.Email):
input.Emailvem do body da request- É usado diretamente como destinatário do OTP
- O DB é consultado apenas para obter
user.Name(personalização do template) - Não verifica se o email pertence ao CPF
Comparação com verify-recover-method (seguro):
VerifyRecoverMethodAsync→GetContactData(type, cpf)→_userRepository.GetEmail(cpf)- Busca o contato cadastrado no banco
- Só envia para o contato registrado
3.2 Sem exigência de JWT
O PasswordController aplica [UseApiKey("SendMessage")] nos endpoints verify-phone, verify-email, verify-code. Nenhum deles tem [Authorize].
O app Flutter envia Authorization: Bearer no fluxo update-contact (customAuthorizationToken), mas o backend ignora por não exigir.
3.3 Cache key compartilhada
Arquivo: src/Auth.API/Application/Services/Implementations/VerifyCodeService.cs
VerifyVerificationCodeAsync (linha ~201):
var key = $"VerificationCodeModelInCache-{input.Cpf}";
UserService.ChangePassword lê da mesma chave:
var key = $"VerificationCodeModelInCache-{cpf}";
Token de qualquer fonte (verify-code ou verify-code-recover) é aceito por change-password.
4. Fluxo de Dados — verify-email
Request: POST /api/auth/verify-email
Body: { "cpf": "12345678900", "email": "atacante@x.com" }
Header: x-api-key: <recover_methods_token>
PasswordController.VerifyEmail()
→ input.Email = "atacante@x.com" (do body)
→ IVerifyCodeService.VerifyEmailAsync(input)
VerifyCodeService.VerifyEmailAsync(input)
→ VerifyContact(ContactType.Email, input.Cpf, input.Email)
→ contact = "atacante@x.com" (direto do input)
VerifyCodeService.VerifyContact(contactType, cpf, contact)
→ user = _userRepository.GetCpfAuthAsync(cpf) // busca user.Name apenas
→ data = CreateData(ContactType.Email, "atacante@x.com", user)
→ data.Contact = "atacante@x.com"
→ data.Arguments = { name: user.Name }
→ SaveData(data, key) // cache
→ _verificationCodeService.SendCodeAsync(data)
VerificationCodeService.SendEmail(model)
→ SendEmailDTO { EmailTo = "atacante@x.com", ... }
→ IMessageService.SendEmail(dto)
→ Email enviado para atacante@x.com ✓
5. Contextos de Uso dos Endpoints (antes do fix)
| Contexto | JWT? | verify-email | verify-code | change-* |
|---|---|---|---|---|
| Update-contact (pós-login) | Sim (enviado) | Aceita (não exige) | Aceita (não exige) | Exige JWT |
| Recover principal | Não | ❌ (não usa) | ❌ (usa verify-code-recover) | x-api-key |
| Recover sub-flow (update contact) | Não | Aceita (não exige) | Aceita (não exige) | Exige JWT ← inconsistente |
6. Arquivos Relevantes no Backend
| Camada | Arquivo | Responsabilidade |
|---|---|---|
| Controller | src/Auth.API/Controllers/PasswordController.cs | Todos endpoints de verify/change/recover |
| Service | src/Auth.API/Application/Services/Implementations/VerifyCodeService.cs | OTP, validação, cache keys |
| Service | src/Auth.API/Application/Services/Implementations/UserService.cs | recover-methods, change-password, change-cellphone, change-email |
| Service | src/Auth.API/Application/Services/Implementations/VerificationCodeService.cs | Geração e envio de OTP (SMS/Email) |
| Filter | src/Auth.API/Filters/UseApiKeyAttribute.cs | Validação x-api-key |
| Integration | src/Auth.API/Integration/MessageService.cs | SMS e e-mail |
| Integration | src/Auth.API/Integration/AdminService.cs | Persistência de senha/telefone/email |
| InputModel | src/Auth.API/Application/InputModels/VerifyEmailInputModel.cs | { Cpf, Email } |
| InputModel | src/Auth.API/Application/InputModels/VerifyPhoneInputModel.cs | { Cpf, CellPhone } |
| InputModel | src/Auth.API/Application/InputModels/VerifyCodeInputModel.cs | { VerificationCode, Cpf, CellPhone, Email } |
| Entity | src/Auth.API/Data/Entities/VerificationCodeModel.cs | Modelos de cache |
7. Arquivos Relevantes no App Flutter
| Camada | Arquivo | Responsabilidade |
|---|---|---|
| Controller | lib/controllers/code_verification/by_method/code_verification_by_method_controller.dart | Sub-fluxo de recover (a remover) |
| Controller | lib/controllers/code_verification/phone/code_verification_by_phone_controller.dart | verify-phone + verify-code + change-cellphone |
| Controller | lib/controllers/code_verification/email/code_verification_by_email_controller.dart | verify-email + verify-code + change-email |
| Controller | lib/controllers/update_phone/update_phone_controller.dart | Entrada de novo telefone |
| Controller | lib/controllers/update_email/update_email_controller.dart | Entrada de novo email |
| Interceptor | lib/middlewares/http/recover_methods_interceptor.dart | Injeta x-api-key |
| Interceptor | lib/middlewares/http/change_password.dart | Injeta Authorization |
| Service | lib/services/firebase_remote_config_service.dart | Tokens de Remote Config |
| Utils | lib/shared/utils/api_utils.dart | Constantes de endpoints |