Roles y Permisos (RBAC)
Varios módulos trabajan en conjunto para implementar control de acceso basado en roles:
src/app/modules/module/— agrupaciones de permisos (secciones del sistema)src/app/modules/permission/— permisos individuales, asociados a un módulosrc/app/modules/role/— roles asignables a usuarios (por tenant)src/app/modules/role-permission/— concesiones (grants) que asignan un conjunto de permisos a un rolsrc/app/modules/permission-catalog-seed/— siembra del catálogo global y de los roles por defectosrc/app/core/permissions/—PermissionResolverService, que resuelve los permisos efectivos de un rol
La verificación en cada endpoint se hace con @RequirePermission y PermissionsGuard (ver Decoradores y Guards).
Módulo (System Module)
Colección modules. Es una sección del sistema (por ejemplo TICKET, SETTINGS). Es un catálogo global: no tiene tenantId.
| Campo | Tipo | Descripción |
|---|---|---|
identifier | string (UUID) | ID externo único |
name | string | Nombre legible |
description | string | Descripción |
acronym | string | Código único en mayúsculas (USER, TICKET, ...) |
isActive | boolean | Por defecto true |
Permission
Colección permissions. Catálogo global sin tenantId.
| Campo | Tipo | Descripción |
|---|---|---|
identifier | string (UUID) | ID externo único |
name | string | Nombre legible |
description | string | Descripción |
acronym | string | Código único en mayúsculas (USER_CREATE, TICKET_READ, ...) |
moduleId | ObjectId → Module | Módulo al que pertenece |
isActive | boolean | Por defecto true |
Role
Colección roles. Pertenece a un tenant.
| Campo | Tipo | Descripción |
|---|---|---|
identifier | string (UUID) | ID externo único |
name | string | Nombre del rol |
description | string | Descripción |
acronym | string | Código en mayúsculas (ADMIN, AGENT) |
isActive | boolean | Por defecto true |
tenantId | ObjectId → Tenant | Tenant propietario |
RolePermission
Colección role-permissions. Un documento agrupa varios permisos para un rol (no es una fila rol-permiso uno a uno).
| Campo | Tipo | Descripción |
|---|---|---|
identifier | string (UUID) | ID externo único |
name | string | Nombre de la concesión |
description | string | Descripción |
acronym | string | Código en mayúsculas |
roleId | ObjectId → Role | Rol que recibe los permisos |
permissions | ObjectId[] → Permission | Permisos concedidos |
tenantId | ObjectId → Tenant | Tenant propietario |
isActive | boolean | Por defecto true |
Endpoints
Los cuatro controladores usan @Auth() y PermissionsGuard, y todos exigen permisos del módulo ACCESS_CONTROL. Siguen el patrón CRUD_ROUTES, no REST puro:
| Método | Ruta relativa | Permiso |
|---|---|---|
POST | /create | ACCESS_CONTROL_CREATE |
GET | /get-all | ACCESS_CONTROL_READ |
GET | /get-one/:id | ACCESS_CONTROL_READ |
PATCH | /update/:id | ACCESS_CONTROL_UPDATE |
DELETE | /remove/:id | ACCESS_CONTROL_DELETE |
| Ruta base | Módulo |
|---|---|
/api/v1/modules | System Modules |
/api/v1/roles | Roles |
/api/v1/permissions | Permissions |
/api/v1/role-permissions | Concesiones rol-permisos |
Ejemplo: POST /api/v1/roles/create, PATCH /api/v1/role-permissions/update/:id. No existe toggle-active en estos controladores.
DTOs
Validaciones de class-validator. "Opcional" indica @IsOptional(). Los DTO de actualización son PartialType del de creación (mismos campos y validaciones, todos opcionales).
Parámetros de ruta
| Parámetro | Tipo | Obligatorio | Validación | Usado en |
|---|---|---|---|---|
id | string | Sí | Ninguna; se busca por identifier (UUID) | get-one/:id, update/:id, remove/:id de los cuatro controladores |
Ninguno de los cuatro controladores define DTO de query: get-all no recibe parámetros (roles y concesiones se filtran por el tenant del usuario).
CreateModuleDto (body de POST /modules/create) y UpdateModuleDto (PATCH /modules/update/:id)
| Campo | Tipo | Obligatorio | Validaciones | Descripción |
|---|---|---|---|---|
name | string | Sí | @IsString, @MinLength(2), @MaxLength(100) | Nombre |
description | string | No | @IsString, @MaxLength(500) | Descripción |
acronym | string | Sí | @IsString, @MinLength(1), @MaxLength(20) | Código; el modelo lo guarda en mayúsculas y es único |
isActive | boolean | No | @IsBoolean | Por defecto true |
CreatePermissionDto (body de POST /permissions/create) y UpdatePermissionDto (PATCH /permissions/update/:id)
| Campo | Tipo | Obligatorio | Validaciones | Descripción |
|---|---|---|---|---|
name | string | Sí | @IsString, @MinLength(2), @MaxLength(100) | Nombre |
description | string | No | @IsString, @MaxLength(500) | Descripción |
acronym | string | Sí | @IsString, @MinLength(1), @MaxLength(30) | Código único en mayúsculas |
moduleId | string | Sí | @IsMongoId | _id del módulo |
isActive | boolean | No | @IsBoolean | Por defecto true |
CreateRoleDto (body de POST /roles/create) y UpdateRoleDto (PATCH /roles/update/:id)
| Campo | Tipo | Obligatorio | Validaciones | Descripción |
|---|---|---|---|---|
name | string | Sí | @IsString, @MinLength(2), @MaxLength(100) | Nombre |
description | string | No | @IsString, @MaxLength(500) | Descripción |
acronym | string | Sí | @IsString, @MinLength(1), @MaxLength(20) | Código en mayúsculas |
isActive | boolean | No | @IsBoolean | Por defecto true |
tenantId lo inyecta el controlador desde el usuario autenticado.
CreateRolePermissionDto (body de POST /role-permissions/create) y UpdateRolePermissionDto (PATCH /role-permissions/update/:id)
| Campo | Tipo | Obligatorio | Validaciones | Descripción |
|---|---|---|---|---|
name | string | Sí | @IsString, @MinLength(2), @MaxLength(100) | Nombre |
description | string | No | @IsString, @MaxLength(500) | Descripción |
acronym | string | Sí | @IsString, @MinLength(1), @MaxLength(30) | Código en mayúsculas |
roleId | string | Sí | @IsMongoId | _id del rol |
permissions | string[] | Sí | @IsArray, @IsMongoId({ each: true }) | _id de los permisos concedidos |
tenantId | string | Sí | @IsMongoId | _id del tenant (viaja en el body) |
isActive | boolean | No | @IsBoolean | Por defecto true |
Resolución de permisos
PermissionResolverService.resolve(tenantId, roleId) busca los RolePermission del par tenant-rol, hace populate de permissions y devuelve un Set con los acronym. El resultado se cachea 10 segundos por clave tenantId:roleId. RolePermissionService invalida esa entrada en create, update y remove; los cambios en Permission o Role no la invalidan, por lo que pueden tardar hasta 10 s en reflejarse.
Catálogo y siembra por defecto
PermissionCatalogSeedService se ejecuta en onModuleInit solo si la variable de entorno MIGRATION_DB_DATA vale true. Hace lo siguiente de forma idempotente:
- Inserta los módulos faltantes de
CATALOG_MODULES(por acrónimo):USER,CONTACT,LABEL,QUICK_RESPONSE,CONNECTION,DEPARTMENT,TICKET,API_KEY,FILE,STARRED_MESSAGE,INTERNAL_CHAT,SETTINGS,MESSAGE,ACCESS_CONTROL. - Inserta los permisos faltantes: por cada módulo,
{MODULO}_CREATE,_READ,_UPDATEy_DELETE, más permisos extra deTICKET(TICKET_UNLOCK_ANY,TICKET_FILTER_BY_AGENT,TICKET_VIEW_AGENT_AVATARy losTICKET_FILTER_STATUS_*/TICKET_FILTER_SCOPE_*). - Para cada tenant crea los roles
ADMINyAGENT(si no existen) y una concesión por rol:DEFAULT_ADMIN_GRANTcon todos los permisos yDEFAULT_AGENT_GRANTcon los permisos de los módulosTICKET,CONTACT,MESSAGE,LABEL,QUICK_RESPONSE,STARRED_MESSAGE,FILEeINTERNAL_CHATque no estén marcadosadminOnly.
Verificación en cada petición
Ciclo de los roles por tenant
La siembra solo ocurre si MIGRATION_DB_DATA vale true. Se dispara en dos momentos: al iniciar la aplicación, para todos los tenants existentes, y al crear un tenant (TenantService.create). Si falla al crear el tenant, se registra el error y el siguiente arranque la reintenta.
Flujo de asignación
Gotchas
- Los
:iddeget-one,updateyremoveen roles, permisos, módulos y concesiones se buscan poridentifier(UUID), no por_idde MongoDB, mientras que los DTO de relación (roleId,moduleId,permissions,tenantId) esperan_idde MongoDB. findOne,updateyremovede roles y concesiones no filtran por tenant (solofindAlllo hace), por lo que el control de acceso depende del permisoACCESS_CONTROL_*.CreateRolePermissionDtoexigetenantIden el body, a diferencia de roles, que lo toma del usuario autenticado.- Módulos y permisos son compartidos por todos los tenants; modificarlos afecta a todos.