Skip to main content

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ódulo
  • src/app/modules/role/ — roles asignables a usuarios (por tenant)
  • src/app/modules/role-permission/ — concesiones (grants) que asignan un conjunto de permisos a un rol
  • src/app/modules/permission-catalog-seed/ — siembra del catálogo global y de los roles por defecto
  • src/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.

CampoTipoDescripción
identifierstring (UUID)ID externo único
namestringNombre legible
descriptionstringDescripción
acronymstringCódigo único en mayúsculas (USER, TICKET, ...)
isActivebooleanPor defecto true

Permission​

Colección permissions. Catálogo global sin tenantId.

CampoTipoDescripción
identifierstring (UUID)ID externo único
namestringNombre legible
descriptionstringDescripción
acronymstringCódigo único en mayúsculas (USER_CREATE, TICKET_READ, ...)
moduleIdObjectId → ModuleMódulo al que pertenece
isActivebooleanPor defecto true

Role​

Colección roles. Pertenece a un tenant.

CampoTipoDescripción
identifierstring (UUID)ID externo único
namestringNombre del rol
descriptionstringDescripción
acronymstringCódigo en mayúsculas (ADMIN, AGENT)
isActivebooleanPor defecto true
tenantIdObjectId → TenantTenant propietario

RolePermission​

Colección role-permissions. Un documento agrupa varios permisos para un rol (no es una fila rol-permiso uno a uno).

CampoTipoDescripción
identifierstring (UUID)ID externo único
namestringNombre de la concesión
descriptionstringDescripción
acronymstringCódigo en mayúsculas
roleIdObjectId → RoleRol que recibe los permisos
permissionsObjectId[] → PermissionPermisos concedidos
tenantIdObjectId → TenantTenant propietario
isActivebooleanPor 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étodoRuta relativaPermiso
POST/createACCESS_CONTROL_CREATE
GET/get-allACCESS_CONTROL_READ
GET/get-one/:idACCESS_CONTROL_READ
PATCH/update/:idACCESS_CONTROL_UPDATE
DELETE/remove/:idACCESS_CONTROL_DELETE
Ruta baseMódulo
/api/v1/modulesSystem Modules
/api/v1/rolesRoles
/api/v1/permissionsPermissions
/api/v1/role-permissionsConcesiones 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ámetroTipoObligatorioValidaciónUsado en
idstringSí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)​

CampoTipoObligatorioValidacionesDescripción
namestringSí@IsString, @MinLength(2), @MaxLength(100)Nombre
descriptionstringNo@IsString, @MaxLength(500)Descripción
acronymstringSí@IsString, @MinLength(1), @MaxLength(20)Código; el modelo lo guarda en mayúsculas y es único
isActivebooleanNo@IsBooleanPor defecto true

CreatePermissionDto (body de POST /permissions/create) y UpdatePermissionDto (PATCH /permissions/update/:id)​

CampoTipoObligatorioValidacionesDescripción
namestringSí@IsString, @MinLength(2), @MaxLength(100)Nombre
descriptionstringNo@IsString, @MaxLength(500)Descripción
acronymstringSí@IsString, @MinLength(1), @MaxLength(30)Código único en mayúsculas
moduleIdstringSí@IsMongoId_id del módulo
isActivebooleanNo@IsBooleanPor defecto true

CreateRoleDto (body de POST /roles/create) y UpdateRoleDto (PATCH /roles/update/:id)​

CampoTipoObligatorioValidacionesDescripción
namestringSí@IsString, @MinLength(2), @MaxLength(100)Nombre
descriptionstringNo@IsString, @MaxLength(500)Descripción
acronymstringSí@IsString, @MinLength(1), @MaxLength(20)Código en mayúsculas
isActivebooleanNo@IsBooleanPor 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)​

CampoTipoObligatorioValidacionesDescripción
namestringSí@IsString, @MinLength(2), @MaxLength(100)Nombre
descriptionstringNo@IsString, @MaxLength(500)Descripción
acronymstringSí@IsString, @MinLength(1), @MaxLength(30)Código en mayúsculas
roleIdstringSí@IsMongoId_id del rol
permissionsstring[]Sí@IsArray, @IsMongoId({ each: true })_id de los permisos concedidos
tenantIdstringSí@IsMongoId_id del tenant (viaja en el body)
isActivebooleanNo@IsBooleanPor 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:

  1. 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.
  2. Inserta los permisos faltantes: por cada módulo, {MODULO}_CREATE, _READ, _UPDATE y _DELETE, más permisos extra de TICKET (TICKET_UNLOCK_ANY, TICKET_FILTER_BY_AGENT, TICKET_VIEW_AGENT_AVATAR y los TICKET_FILTER_STATUS_* / TICKET_FILTER_SCOPE_*).
  3. Para cada tenant crea los roles ADMIN y AGENT (si no existen) y una concesión por rol: DEFAULT_ADMIN_GRANT con todos los permisos y DEFAULT_AGENT_GRANT con los permisos de los módulos TICKET, CONTACT, MESSAGE, LABEL, QUICK_RESPONSE, STARRED_MESSAGE, FILE e INTERNAL_CHAT que no estén marcados adminOnly.

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 :id de get-one, update y remove en roles, permisos, módulos y concesiones se buscan por identifier (UUID), no por _id de MongoDB, mientras que los DTO de relación (roleId, moduleId, permissions, tenantId) esperan _id de MongoDB.
  • findOne, update y remove de roles y concesiones no filtran por tenant (solo findAll lo hace), por lo que el control de acceso depende del permiso ACCESS_CONTROL_*.
  • CreateRolePermissionDto exige tenantId en 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.