Skip to content

Cadastrar usuários e autenticar com JWT, exigindo token nos demais endpoints - #30

Merged
Rodrigosdk merged 11 commits into
developerfrom
feature/auth
Nov 12, 2026
Merged

Rodrigosdk merged 11 commits into
developerfrom
feature/auth

Conversation

@Rodrigosdk

@Rodrigosdk Rodrigosdk commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Esta branch adiciona usuários e autenticação à API. É possível se cadastrar e entrar com e-mail e senha, recebendo um token JWT, e todos os endpoints de negócio passam a exigir esse token. A senha é guardada com Argon2id. Um usuário inativado há até 30 dias pode voltar a se cadastrar com a mesma senha e é reativado. O CD passa a aplicar o main.bicep quando a infraestrutura muda, o que leva a chave do JWT para produção.

Mudanças

Hash de senha

  • Novo IPasswordHashing (Domain) e Argon2PasswordHasher (Infrastructure), com o pacote Konscious.Security.Cryptography.Argon2. O hash guarda salt aleatório e hash juntos em Base64, e a verificação compara em tempo constante (CryptographicOperations.FixedTimeEquals).
  • A senha não tem limite máximo de tamanho; o cadastro exige no mínimo 8 caracteres.

Entidade User e banco

  • User segue o padrão do Policyholder: construtor com validação de nome (até 150 caracteres), e-mail normalizado e senha obrigatória, Update que valida tudo antes de alterar, Deactivate e Reactivate.
  • DeactivatedAt registra quando o usuário foi inativado, e CanBeReactivated aplica o prazo de 30 dias (User.ReactivationPeriod).
  • Tabela users com e-mail único entre os usuários ativos (IX_users_email_active), filtro global de inativos e concorrência otimista por xmin. A coluna password é text, sem limite.
  • Migrations geradas pelo EF: CreateUsers e AddUserDeactivatedAt. A CreateUsers também atualiza o snapshot do modelo, que ainda registrava BirthDate, StartDate, EndDate e OccurredOn como DateTime; as colunas não mudam.

Repositório de usuários

  • IUsersWriteOnlyRepository, IUsersReadOnlyRepository e IUsersUpdateOnlyRepository, implementados pelo UsersRepository: cadastro, busca por id e por e-mail, verificação de e-mail em uso, busca incluindo inativos e busca do último usuário inativado com um e-mail.

Cadastro

  • POST /api/autenticacao/registrar recebe nome, email e senha e devolve 201 com o id.
  • Todos os erros de validação voltam juntos. E-mail já usado por um usuário ativo é recusado com 400.
  • E-mail de usuário inativado há até 30 dias: se a senha conferir, o usuário antigo é reativado e o id dele é devolvido; se não conferir, a resposta é 400 com "E-mail ou senha inválidos.". Depois de 30 dias, um usuário novo é criado.
  • Dois cadastros simultâneos com o mesmo e-mail: o índice único barra o segundo, e o UnitOfWork transforma o erro do banco na mesma mensagem de e-mail em uso, em vez de um 500.

Login e JWT

  • POST /api/autenticacao/entrar recebe email e senha e devolve 200 com nome, token e expiraEm.
  • Credenciais erradas, e-mail inexistente ou usuário inativo recebem 401 com a mesma mensagem, para não revelar quais e-mails existem. Quando o e-mail não existe, a senha é hasheada do mesmo jeito, para o tempo de resposta não denunciar isso.
  • O token é um JWT HS256 com o id do usuário em sub e um jti único. Issuer, audience e validade (60 minutos) ficam na seção Jwt do appsettings.json. A chave (Jwt:SigningKey, mínimo de 32 bytes) fica fora do repositório e é validada na inicialização, como as demais opções.
  • Registro e login ficam no mesmo AuthenticationController.

Autorização

  • Todos os endpoints exigem um token válido por padrão (política de fallback). Ficam abertos só registrar, entrar, /health, o documento OpenAPI e o redirecionamento da raiz para o Swagger.
  • Sem token, ou com token inválido ou vencido, a resposta é 401 no formato de erro da API, com a lista errors e o traceId.
  • A validade do token é conferida pelo TimeProvider da aplicação, o mesmo relógio que o gera.
  • O Swagger tem o botão Authorize, e o cadeado aparece só nas rotas protegidas.
  • Ainda não há perfis nem refresh token.

Infraestrutura e CD

  • O main.bicep recebe o parâmetro seguro jwtSigningKey e o grava no App Service como Jwt__SigningKey.
  • O cd.yml ganhou o job infra, que aplica o main.bicep só quando o push altera infra/main.bicep ou infra/bootstrap.sh. Ele roda antes do deploy e mantém a imagem que já está no ar, para que o código novo só suba depois das migrations.
  • O bootstrap.sh gera a chave do JWT, grava os secrets DATABASE_ADMIN_PASSWORD e JWT_SIGNING_KEY e a variável AZURE_RESOURCE_GROUP, e dá à identidade do GitHub o papel Contributor no resource group, no lugar de Website Contributor no Web App.
  • O docker/compose.yml traz uma chave padrão só para uso local, e o README mostra como configurá-la com dotnet user-secrets.

⚠️ Atenção

  • Quebra de contrato: todos os endpoints de segurados, apólices e sinistros passam a responder 401 sem o cabeçalho Authorization: Bearer <token>.
  • Antes do merge em main: rode ./infra/bootstrap.sh uma vez. Sem os novos secrets e a nova permissão, o job infra falha e o deploy não acontece. O bootstrap também gera uma nova senha do banco e uma nova chave do JWT, então tokens emitidos antes deixam de valer.
  • Permissão maior no CD: a identidade do GitHub passa de Website Contributor no Web App para Contributor no resource group, porque o Bicep também gerencia o banco e o plano.
  • Configuração obrigatória: sem Jwt:SigningKey com pelo menos 32 bytes, a API não sobe. Localmente, use dotnet user-secrets set "Jwt:SigningKey" "$(openssl rand -base64 48)" --project src/Backend/PolicyDesk.Api.
  • Novas dependências: Konscious.Security.Cryptography.Argon2 1.3.1, Microsoft.AspNetCore.Authentication.JwtBearer 10.0.14 e Microsoft.IdentityModel.JsonWebTokens 8.19.2.

Como testar

  1. docker compose -f docker/compose.yml -f docker/compose.dev.yml up -d --build e abra o Swagger.
  2. Chame GET /api/segurados sem token: deve responder 401.
  3. Cadastre-se em POST /api/autenticacao/registrar e entre em POST /api/autenticacao/entrar.
  4. Clique em Authorize, cole o token e repita o GET /api/segurados: deve responder 200.
  5. Tente entrar com a senha errada: deve responder 401 com "E-mail ou senha inválidos.".
  6. Inative o usuário no banco (active = false, deactivated_at = now()) e cadastre o mesmo e-mail com a mesma senha: deve responder 201 com o mesmo id. Com outra senha, deve responder 400.

Testes

  • Novos:
    • Argon2PasswordHasherTests: formato do hash, salt diferente a cada hash, senha certa, senha diferente, só com maiúsculas trocadas e com espaço a mais;
    • UsersTests: criação, validação de nome, e-mail e senha, Update sem alterar nada quando algo é inválido, inativação e prazo de reativação;
    • UsersRepositoryTests: cadastro e consultas, inativos fora das buscas, último inativo por e-mail e cadastro simultâneo com o mesmo e-mail;
    • RegisterUserUseCaseTests e LoginUseCaseTests: validações, senha guardada como hash, e-mail em uso, reativação dentro e fora do prazo, credenciais inválidas;
    • JwtAccessTokenGeneratorTests: token validado de verdade, sub, validade, tokens diferentes e recusa com outra chave;
    • testes de integração do cadastro, do login e da autenticação: 401 sem token, com token de outra chave e com token vencido, acesso com o token do login, /health aberto e esquema Bearer no OpenAPI só nas rotas protegidas;
    • configuração: chave do JWT ausente ou curta impede a inicialização.
  • Ajustados: os testes de integração enviam um token válido por padrão e usam clientes sem token para cadastro, login e os casos de 401. O teste que adianta o relógio renova o token, porque o antigo vencia no meio do teste.
  • Novos builders em CommonTestUtilities: UserBuilder, PasswordGenerator, PasswordHashingBuilder, RequestUserJsonBuilder, RequestLoginJsonBuilder, JwtOptionsBuilder, AccessTokenGeneratorBuilder e os mocks do repositório de usuários.

@Rodrigosdk
Rodrigosdk merged commit 162a5cd into developer Nov 12, 2026
3 checks passed
@Rodrigosdk
Rodrigosdk deleted the feature/auth branch November 12, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants