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:
15
packages/shared/package.json
Normal file
15
packages/shared/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
7
packages/shared/src/generate-password.ts
Normal file
7
packages/shared/src/generate-password.ts
Normal 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');
|
||||
}
|
||||
2
packages/shared/src/index.ts
Normal file
2
packages/shared/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './permissions';
|
||||
export * from './generate-password';
|
||||
85
packages/shared/src/permissions.ts
Normal file
85
packages/shared/src/permissions.ts
Normal 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'],
|
||||
};
|
||||
14
packages/shared/tsconfig.json
Normal file
14
packages/shared/tsconfig.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user