feat: add authentication and RBAC

- packages/database: schema Prisma (users/sessions/roles/permissions/
  user_roles/role_permissions/audit_logs/password_reset_tokens), migration
  inicial e seed (permissoes+perfis+bootstrap super_admin com senha
  aleatoria em FIRST_LOGIN.txt). Decisao de ORM (Prisma) documentada em
  docs/ARCHITECTURE.md
- packages/shared: catalogo de permissoes (fonte unica usada por seed e API)
- apps/api: NestJS 11 + Fastify
  - autenticacao: Argon2id, access JWT + refresh token opaco com rotacao,
    cookies HttpOnly/SameSite=Lax, change/forgot/reset password
  - rate limiting progressivo de login via Redis (bloqueio crescente por IP)
  - RBAC reforcado no backend (PermissionsGuard), protecao contra
    auto-elevacao de privilegio
  - auditoria (audit_logs) nas acoes sensiveis, com redacao de segredos
  - health checks reais (postgres+redis), swagger desabilitavel, logs
    estruturados JSON com request_id de correlacao, filtro global de
    excecoes sem vazar erro cru
- infrastructure/docker/api.Dockerfile: build multi-stage do monorepo pnpm
- docker-compose.yml: servico api na rede interna, sem porta publicada

Testado via containers reais: login, /me, refresh, change-password,
rate limit (7 tentativas -> 429), RBAC (nega/permite), bloqueio de
auto-elevacao (403), audit log populado, health checks, lint e testes
unitarios passando.
This commit is contained in:
2026-08-27 12:23:02 -03:00
parent eee6d7aece
commit a2898fa566
69 changed files with 10161 additions and 7 deletions

View File

@@ -121,3 +121,40 @@ services:
resources:
limits:
memory: 400M
api:
build:
context: .
dockerfile: infrastructure/docker/api.Dockerfile
image: b2bcall-api:0.1.0
container_name: b2bcall-api
restart: unless-stopped
networks:
- b2bcall-net
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
env_file:
- .env
environment:
POSTGRES_HOST: postgres
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
# Sem "ports": só o Nginx (Fase 9) expõe HTTP ao mundo externo. Em dev,
# acessar via `docker compose exec` ou publicar temporariamente.
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 15s
timeout: 5s
retries: 5
start_period: 15s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 300M