RBAC multitenant para Asterisk Suite
RBAC multitenant para Asterisk Suite
Objetivo
Implementar autorizacion por permisos atomicos dentro de cada empresa, manteniendo separada la identidad global de usuarios y la logica de negocio de cada tenant.
La arquitectura futura esperada es:
public
users
companies
company_users
refresh_tokens
audit_logs
tenant / schema / base de empresa
products
customers
sales
settings
permissions
business_roles
business_role_permissions
business_user_roles
user_permission_overrides
public sabe quien es el usuario y a que empresas pertenece. El tenant sabe que permisos tiene ese usuario dentro del negocio.
Separacion de responsabilidades
Hay dos niveles de autorizacion:
- Rol general de empresa
Vive en public.company_users.role.
Sirve para permisos administrativos sobre la empresa como tenant:
OWNER
ADMIN
EDITOR
USER
Ejemplos de uso:
Entrar a la empresa
Invitar usuarios
Cambiar subdominio
Administrar configuracion general
Administrar roles y permisos del negocio
- Permisos finos de negocio
Viven dentro del tenant.
Sirven para acciones del modelo de negocio:
products.read
products.create
products.update
products.delete
customers.read
customers.create
customers.update
customers.delete
sales.read
sales.create
sales.update
sales.delete
reports.read
settings.read
settings.update
Modelos Prisma sugeridos
Public schema
enum CompanyUserRole {
OWNER
ADMIN
EDITOR
USER
@@schema("public")
}
model companies {
id String @id @default(uuid()) @db.Uuid
name String @db.VarChar(255)
tax_id String? @db.VarChar(50)
phone String? @db.VarChar(30)
subdomain String? @unique @db.VarChar(100)
schema_name String? @unique @db.VarChar(100)
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
deleted_at DateTime?
created_by String? @db.Uuid
updated_by String? @db.Uuid
deleted_by String? @db.Uuid
companyUsers company_users[]
@@schema("public")
}
model company_users {
company_id String @db.Uuid
user_id String @db.Uuid
role CompanyUserRole @default(USER)
company companies @relation(fields: [company_id], references: [id], onDelete: Cascade)
user users @relation(fields: [user_id], references: [id], onDelete: Cascade)
@@id([company_id, user_id])
@@index([user_id])
@@schema("public")
}
Tenant schema
No hace falta company_id, porque el tenant activo ya representa a la empresa.
enum PermissionEffect {
ALLOW
DENY
}
model permissions {
id String @id @default(uuid()) @db.Uuid
code String @unique @db.VarChar(120)
description String?
active Boolean @default(true)
role_permissions business_role_permissions[]
user_permission_overrides user_permission_overrides[]
}
model business_roles {
id String @id @default(uuid()) @db.Uuid
code String @unique @db.VarChar(80)
name String @db.VarChar(120)
description String?
is_system Boolean @default(false)
active Boolean @default(true)
created_at DateTime @default(now())
updated_at DateTime? @updatedAt
deleted_at DateTime?
permissions business_role_permissions[]
users business_user_roles[]
}
model business_role_permissions {
role_id String @db.Uuid
permission_id String @db.Uuid
role business_roles @relation(fields: [role_id], references: [id], onDelete: Cascade)
permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade)
@@id([role_id, permission_id])
}
model business_user_roles {
user_id String @db.Uuid
role_id String @db.Uuid
role business_roles @relation(fields: [role_id], references: [id], onDelete: Cascade)
@@id([user_id, role_id])
@@index([user_id])
}
model user_permission_overrides {
user_id String @db.Uuid
permission_id String @db.Uuid
effect PermissionEffect
permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade)
@@id([user_id, permission_id])
@@index([user_id])
}
business_user_roles.user_id y user_permission_overrides.user_id apuntan logicamente a public.users.id, pero no necesitan relacion Prisma directa si los datos de negocio viven en otra base o schema.
Modulos NestJS sugeridos
AuthModule
Responsabilidad actual:
Login
Refresh token
Logout
JWT strategy
Usuario autenticado
Debe seguir siendo global. No deberia resolver permisos finos del negocio.
El JWT deberia contener identidad, no todo el mapa de permisos:
{
sub: user.id,
name: user.name,
email: user.email,
role: user.role
}
user.role puede quedar para permisos de plataforma o soporte interno, no para products.create.
TenantModule
Modulo transversal para resolver la empresa activa y preparar el contexto tenant.
Responsabilidades:
Resolver tenant por subdominio, header o parametro
Buscar la empresa en public.companies
Validar membresia en public.company_users
Guardar company, schema_name y companyUserRole en request context
Configurar Prisma o conexion hacia el schema/base del tenant
Piezas sugeridas:
src/tenant/tenant.module.ts
src/tenant/tenant.service.ts
src/tenant/tenant.guard.ts
src/tenant/tenant-context.ts
src/tenant/decorators/current-tenant.decorator.ts
El guard principal seria:
@UseGuards(JwtAuthGuard, TenantGuard)
AuthorizationModule
Modulo transversal para permisos.
Responsabilidades:
Exponer decorador @RequirePermissions()
Resolver permisos efectivos del usuario dentro del tenant
Aplicar overrides ALLOW/DENY
Exponer PermissionsGuard
Opcionalmente cachear permisos por user + tenant
Piezas sugeridas:
src/authorization/authorization.module.ts
src/authorization/authorization.service.ts
src/authorization/guards/permissions.guard.ts
src/authorization/decorators/permissions.decorator.ts
src/authorization/types/permission-effect.enum.ts
Uso esperado en controladores:
@UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard)
@RequirePermissions('products.create')
@Post()
create() {}
AccessControlModule
Modulo funcional para administrar roles, permisos y asignaciones del negocio.
Vive conceptualmente dentro del tenant, porque administra tablas del tenant.
Responsabilidades:
CRUD de business_roles
Asignar permisos a roles
Asignar roles a usuarios
Agregar o quitar overrides por usuario
Listar permisos efectivos de un usuario
Sembrar roles base al crear una empresa
Piezas sugeridas:
src/modules/core/access-control/access-control.module.ts
src/modules/core/access-control/roles.controller.ts
src/modules/core/access-control/permissions.controller.ts
src/modules/core/access-control/user-permissions.controller.ts
src/modules/core/access-control/access-control.service.ts
Endpoints tentativos:
GET /access-control/permissions
GET /access-control/roles
POST /access-control/roles
PATCH /access-control/roles/:id
DELETE /access-control/roles/:id
PUT /access-control/roles/:id/permissions
GET /access-control/users/:userId/roles
PUT /access-control/users/:userId/roles
GET /access-control/users/:userId/permissions/effective
PUT /access-control/users/:userId/permissions/overrides
Estos endpoints deberian requerir un permiso como:
settings.roles.manage
o permitirlos automaticamente a OWNER en public.company_users.role.
Flujo de una request protegida
1. Llega request con Authorization: Bearer token.
2. JwtAuthGuard valida token y carga req.user.
3. TenantGuard resuelve tenant:
- subdominio, header o parametro
- public.companies
- public.company_users
4. TenantGuard guarda contexto:
- userId
- companyId
- schemaName
- companyUserRole
5. PermissionsGuard lee @RequirePermissions().
6. Si companyUserRole es OWNER, permite.
7. Consulta permisos efectivos en el tenant.
8. Si hay DENY explicito, rechaza.
9. Si hay ALLOW explicito o permiso heredado por rol, permite.
10. Si no hay permiso, rechaza.
Regla de precedencia
OWNER publico
permite todo dentro de la empresa
DENY explicito
gana sobre roles y allow
ALLOW explicito
permite aunque el rol no lo tenga
Permiso por rol
permite si cualquier rol del usuario lo incluye
Sin coincidencia
denegado
Roles base recomendados por tenant
Al crear una empresa conviene sembrar permisos y roles base.
Admin
*
Supervisor
products.read
products.create
products.update
customers.read
customers.create
sales.read
reports.read
Vendedor
products.read
customers.read
customers.create
sales.read
sales.create
Operador
products.read
warehouse.read
warehouse.update
Solo lectura
products.read
customers.read
sales.read
reports.read
Para no guardar * como permiso magico en todos lados, hay dos caminos:
OWNERpublico saltea la evaluacion fina.Admintenant recibe todos los permisos atomicos existentes.
La opcion 2 suele ser mas clara para auditoria.
Historia tecnica sugerida
Historia 1: Normalizar rol general de empresa
Como sistema
quiero que company_users.role use un enum publico
para evitar strings inconsistentes y separar roles generales de permisos finos.
Criterios:
Crear enum CompanyUserRole
Migrar valores actuales
Default USER
OWNER puede administrar empresa
ADMIN puede administrar configuracion operativa
Historia 2: Crear contexto tenant
Como backend
quiero resolver la empresa activa por request
para conectar la request con el schema/base correcta.
Criterios:
TenantGuard valida JWT + membresia
RequestContext guarda userId, companyId, schemaName y companyUserRole
Si no hay membresia, retorna 403
Historia 3: Crear catalogo de permisos tenant
Como empresa
quiero tener permisos atomicos del negocio
para controlar acciones concretas del sistema.
Criterios:
Crear tabla permissions
Seed inicial de permisos
Codigo unico por permiso
Permisos activos/inactivos
Historia 4: Crear roles finos tenant
Como administrador de empresa
quiero agrupar permisos en roles
para asignar responsabilidades a usuarios.
Criterios:
Crear business_roles
Crear business_role_permissions
CRUD de roles
Asignacion de permisos a roles
No permitir borrar roles del sistema si is_system = true
Historia 5: Asignar roles a usuarios
Como administrador de empresa
quiero asignar roles del negocio a usuarios
para controlar su acceso operativo.
Criterios:
Crear business_user_roles
Un usuario puede tener varios roles
Validar que el user_id pertenezca a la empresa desde public.company_users
Listar roles por usuario
Historia 6: Overrides por usuario
Como administrador de empresa
quiero permitir o denegar permisos puntuales a un usuario
para cubrir excepciones sin crear roles nuevos.
Criterios:
Crear user_permission_overrides
Soportar ALLOW y DENY
DENY tiene prioridad sobre roles
Endpoint para ver permisos efectivos
Historia 7: Guard de permisos
Como desarrollador
quiero proteger endpoints con @RequirePermissions()
para aplicar permisos finos de negocio de forma uniforme.
Criterios:
Crear decorador @RequirePermissions()
Crear PermissionsGuard
OWNER publico permite todo
DENY explicito rechaza
Permiso por rol o ALLOW explicito permite
Sin permiso retorna 403
Integracion progresiva
No hace falta migrar todos los controladores de una vez.
Orden recomendado:
1. Auth y TenantGuard
2. AccessControlModule
3. PermissionsGuard
4. Productos
5. Clientes
6. Ventas
7. Reportes
8. Configuracion
Mientras un controlador no tenga @RequirePermissions(), solo queda protegido por JWT y membresia tenant.