Initial project import
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# 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)` e `PasswordHasher.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 em `api_keys.key_hash`.
|
||||
- Validação: `ApiKeyService.validar(String chaveRecebida)` → busca por hash no banco master, confere `ativa=true`, retorna o `empresa_id` vinculado (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ê header `Authorization: Bearer <token>`, valida escopo=master, senão retorna 401.
|
||||
- `exigirFuncionario(HttpHandler)` — idem, escopo=empresa; injeta `empresa_id`/`funcionario_id`/`cargo` no contexto da requisição (pode ser via `HttpExchange.setAttribute` ou um `ThreadLocal` simples).
|
||||
- `exigirApiKey(HttpHandler)` — lê header `X-API-Key`, valida, injeta `empresa_id` no 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/login` e `/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/login` com 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/login` com 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.
|
||||
Reference in New Issue
Block a user