4.4 KiB
Prompt 04 — Autenticação: senha master, login de funcionário (JWT) e API Key
Contexto
Prompts 01–03 concluídos: app no ar, db_master e db_empresa com schema completo, DAOs básicos, provisionamento de tenant funcionando. Leia 10-tracker-progresso.md antes de começar.
Este prompt implementa as 3 camadas de autenticação da arquitetura v3.0: master (senha única), funcionário do escritório (JWT), app externo (API Key).
Objetivo
Implementar geração/validação de JWT (sem lib pesada — implementar HMAC-SHA256 manualmente ou usar uma lib mínima tipo java-jwt/jjwt, only se realmente necessário; preferir implementação própria já que o projeto é "Java puro"), hashing de senha (BCrypt — aqui vale trazer uma lib pequena e específica, tipo at.favre.lib:bcrypt, já que reimplementar bcrypt do zero não compensa), geração/validação de API Key, e os middlewares de autenticação usados pelos handlers HTTP dos próximos prompts.
Especificação
1. Hash de senha
- Usar BCrypt (biblioteca
at.favre.lib:bcrypt, única dependência externa além do driver JDBC e (se precisar) da lib de JWT). PasswordHasher.hash(senha)ePasswordHasher.verificar(senha, hash).
2. JWT
JwtService.gerar(Map<String,Object> claims, long validadeSegundos)→ string do token.JwtService.validar(String token)→Optional<Map<String,Object>> claims(vazio se inválido/expirado).- Claims para o token master:
{"escopo": "master"}. - Claims para o token de funcionário:
{"escopo": "empresa", "empresa_id": X, "funcionario_id": Y, "cargo": "..."}. - Assinatura HMAC-SHA256 usando
JWT_SECRET(variável de ambiente já lida no Prompt 01). - Validade sugerida: 8 horas para funcionário, 2 horas para master (reautenticação frequente por segurança, já que é a chave mestra do sistema).
3. API Key
- Geração:
ApiKeyService.gerar()→ retorna a chave em texto puro (mostrada uma única vez ao usuário) + o hash (SHA-256 é suficiente aqui, não precisa de bcrypt) que é salvo emapi_keys.key_hash. - Validação:
ApiKeyService.validar(String chaveRecebida)→ busca por hash no banco master, confereativa=true, retorna oempresa_idvinculado (ou vazio se inválida/revogada).
4. Endpoints
POST /api/master/login body: {"senha": "..."}
→ confere hash contra MASTER_PASSWORD_HASH (env var); se ok, retorna {"token": "..."}
POST /api/auth/login body: {"email": "...", "senha": "..."}
→ busca funcionario por email (em qual empresa? funcionarios.email é único globalmente,
então a busca não precisa de empresa_id no body), confere senha, gera JWT de empresa
→ se funcionarios.status != 'ativo' OU empresas.status != 'ativa': erro 403 com mensagem clara
("cadastro desativado" ou "escritório com pendência financeira", respectivamente)
5. Middlewares (para uso nos próximos prompts)
Criar filtros/handlers wrapper reutilizáveis:
exigirMaster(HttpHandler)— lê headerAuthorization: Bearer <token>, valida escopo=master, senão retorna 401.exigirFuncionario(HttpHandler)— idem, escopo=empresa; injetaempresa_id/funcionario_id/cargono contexto da requisição (pode ser viaHttpExchange.setAttributeou umThreadLocalsimples).exigirApiKey(HttpHandler)— lê headerX-API-Key, valida, injetaempresa_idno contexto.
6. Segurança básica
- Nunca logar senha, token completo ou API key em texto puro nos logs (logar no máximo os primeiros 6 caracteres + "...").
- Rate limiting simples no
/api/master/logine/api/auth/login(ex: máximo 5 tentativas por IP a cada 5 minutos, em memória — não precisa de Redis nesta fase).
Critérios de aceite
POST /api/master/logincom senha errada retorna 401; com senha certa retorna um JWT válido.- Token master usado em
exigirFuncionarioé rejeitado (escopo errado) — e vice-versa. POST /api/auth/logincom funcionário de empresa bloqueada (empresas.status='bloqueada') retorna 403 com mensagem específica de pendência financeira.- API Key gerada, salva com hash, validada corretamente; uma chave revogada (
ativa=false) é rejeitada. - 6ª tentativa de login em 5 minutos do mesmo IP é bloqueada temporariamente.
- Nenhum log do sistema imprime senha, token completo ou API key completa.
Ao concluir
Atualizar 10-tracker-progresso.md: Prompt 04 concluído, data, resumo. Confirmar Prompts 01–03 seguem marcados.