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

@@ -0,0 +1,15 @@
{
"name": "@b2bcall/shared",
"version": "0.1.0",
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc"
},
"dependencies": {},
"devDependencies": {
"@types/node": "^24.0.0",
"typescript": "^5.7.3"
}
}

View File

@@ -0,0 +1,7 @@
import { randomBytes } from 'node:crypto';
// Senha aleatória forte para bootstrap de contas (super_admin, novos
// usuários criados por um admin) — nunca senha padrão (agente.md seção 70).
export function generateStrongPassword(): string {
return randomBytes(24).toString('base64url');
}

View File

@@ -0,0 +1,2 @@
export * from './permissions';
export * from './generate-password';

View File

@@ -0,0 +1,85 @@
// Catálogo de permissões do RBAC (ver docs/ARCHITECTURE.md e agente.md seção
// 11). Fonte única de verdade usada pelo seed (packages/database) e pelos
// guards/decorators da API (apps/api). Adicionar uma permissão aqui NUNCA
// deve vir acompanhado de "if (user.role === 'admin')" no código — sempre
// via checagem de permissão (RolePermission) resolvida no backend.
export const PERMISSIONS = [
'dashboard.view',
'trunks.view',
'trunks.create',
'trunks.update',
'trunks.delete',
'extensions.view',
'extensions.create',
'extensions.update',
'extensions.delete',
'dialplans.view',
'dialplans.create',
'dialplans.update',
'dialplans.delete',
'queues.view',
'queues.create',
'queues.update',
'queues.delete',
'agents.view',
'agents.create',
'agents.update',
'agents.delete',
'campaigns.view',
'campaigns.create',
'campaigns.start',
'campaigns.pause',
'campaigns.stop',
'campaigns.update',
'campaigns.delete',
'reports.view',
'reports.export',
'monitoring.view',
'asterisk.view',
'asterisk.configure',
'asterisk.reload',
'users.view',
'users.create',
'users.update',
'roles.manage',
'audit.view',
'settings.manage',
] as const;
export type Permission = (typeof PERMISSIONS)[number];
// Perfis iniciais (seed). Os níveis NÃO são fixos: super_admin pode
// reconfigurar quais permissões cada perfil possui pela tela de
// "Perfis e Permissões". isSystem apenas impede excluir o perfil em si.
export const DEFAULT_ROLE_PERMISSIONS: Record<string, Permission[]> = {
super_admin: [...PERMISSIONS],
admin: PERMISSIONS.filter((p) => p !== 'roles.manage'),
supervisor: [
'dashboard.view',
'queues.view',
'agents.view',
'agents.update',
'campaigns.view',
'campaigns.start',
'campaigns.pause',
'campaigns.stop',
'reports.view',
'reports.export',
'monitoring.view',
],
agent: ['dashboard.view'],
};

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "ES2022",
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"esModuleInterop": true,
"skipLibCheck": true,
"strict": false
},
"include": ["src"]
}