feat: add apps/api (NestJS + Fastify) with authentication endpoints
- POST /auth/login, /auth/refresh, /auth/logout, /auth/select-tenant, /auth/change-password, GET /auth/tenants — wired to packages/auth - JwtAuthGuard + DomainExceptionFilter (401/403 without leaking internals) - LoginRateLimitGuard: Redis-backed 5/min per IP and per email (agente.md secao 149), safe across multiple API instances - helmet + restrictive cors (deny-by-default) + global rate limit - GET /health, /health/live, /health/ready checking Postgres and Redis - changePassword() added to packages/auth for the mustChangePassword flow - fixed REDIS_HOST/POSTGRES_HOST docker-compose-only hostnames not resolving from the host process; added REDIS_URL for host-side use - verified end-to-end with curl: login, wrong password / unknown email (same generic error), authenticated route, missing token, refresh rotation, logout revocation, and the 429 rate limit kicking in after 5 attempts
This commit is contained in:
@@ -71,18 +71,52 @@ O seed cria `admin@b2bcall.local` com senha aleatória de 24 bytes, salva uma
|
||||
`mustChangePassword = true`. Rodar de novo o seed não recria o admin se já
|
||||
existir um usuário com role `platform_super_admin`.
|
||||
|
||||
## O que falta (fica para quando existir `apps/api`)
|
||||
## Camada HTTP (`apps/api`)
|
||||
|
||||
Tudo abaixo depende de uma camada HTTP (NestJS/Fastify) que ainda não existe:
|
||||
NestJS sobre Fastify (agente.md secao 12). Rotas em `apps/api/src/auth`:
|
||||
|
||||
```
|
||||
POST /auth/login { email, password } -> { accessToken, refreshToken, sessionId, mustChangePassword }
|
||||
POST /auth/refresh { refreshToken } -> { accessToken, refreshToken }
|
||||
POST /auth/logout (autenticado) -> 204
|
||||
GET /auth/tenants (autenticado) -> [{ tenantId, code, name, status }]
|
||||
POST /auth/select-tenant (autenticado) { tenantId } -> { accessToken }
|
||||
POST /auth/change-password (autenticado) { currentPassword, newPassword } -> 204
|
||||
```
|
||||
|
||||
- `JwtAuthGuard` (`apps/api/src/common/guards`) valida o access token e injeta
|
||||
`request.user` (claims do JWT); `@CurrentUser()` expõe isso no controller.
|
||||
- `DomainExceptionFilter` traduz os erros de `packages/auth`
|
||||
(`InvalidCredentialsError` → 401, `InvalidRefreshTokenError` → 401,
|
||||
`NotATenantMemberError` → 403) sem vazar stack trace.
|
||||
- `LoginRateLimitGuard` conta tentativas de login por IP **e** por e-mail via
|
||||
Redis (`INCR`+`EXPIRE`, 5/minuto — agente.md secao 149), funcionando
|
||||
corretamente com múltiplas instâncias da API (ao contrário de um contador em
|
||||
memória local).
|
||||
- `helmet` + `cors` restritivo (`CORS_ORIGIN` via env, `false` por padrão —
|
||||
nega tudo até ser configurado) + rate limit global de 300/min como defesa
|
||||
extra (agente.md secao 182).
|
||||
- `GET /health`, `/health/live`, `/health/ready` (agente.md secao 187) —
|
||||
`ready` checa Postgres e Redis de verdade.
|
||||
- API escuta só em `127.0.0.1:3000` (`API_PORT`) — nunca exposta direto,
|
||||
fica atrás do nginx quando ele existir.
|
||||
|
||||
**Pegadinha de rede corrigida durante os testes**: `REDIS_HOST=redis` /
|
||||
`POSTGRES_HOST=postgres` no `.env` são os nomes de serviço do Docker Compose —
|
||||
só resolvem de dentro da rede Docker. Como `apps/api` roda direto no host
|
||||
(ainda não containerizada), ela usa `REDIS_URL`/`APP_DATABASE_URL`
|
||||
(`@localhost`) em vez disso. Quando `b2bcall-api` virar um serviço Docker na
|
||||
mesma rede, essas URLs precisam trocar para os hostnames internos.
|
||||
|
||||
## O que falta
|
||||
|
||||
- Rate limiting de login por IP/usuário (agente.md secao 149) — plugin
|
||||
`@fastify/rate-limit` ou equivalente, não implementável em `packages/auth`
|
||||
isoladamente.
|
||||
- Endpoints REST (`POST /auth/login`, `/auth/refresh`, `/auth/logout`,
|
||||
`/auth/select-tenant`) e guards HTTP que traduzem `InvalidCredentialsError`/
|
||||
`NotATenantMemberError` em 401/403.
|
||||
- Password reset (link expirável por e-mail) — precisa de um provedor de
|
||||
e-mail/SMTP, fora do escopo desta fase.
|
||||
- Reuse detection de refresh token roubado (família de tokens) — não
|
||||
implementado; a rotação simples (secao 148) já está feita, a detecção de
|
||||
reuso é um hardening adicional a avaliar depois.
|
||||
- Progressive blocking (backoff exponencial) no login — hoje é só janela fixa
|
||||
de 5/minuto.
|
||||
- Enforcement server-side de `mustChangePassword` bloqueando outras rotas
|
||||
(hoje só o client precisa respeitar a flag) — revisitar quando existirem
|
||||
rotas de negócio de verdade para proteger.
|
||||
|
||||
Reference in New Issue
Block a user