UZDI implements a layered security model: passwords are hashed with bcrypt, all string inputs are sanitized globally before validation using a regex-based pipe, route access is controlled by a Role-Based Access Control (RBAC) system enforced through a NestJS guard and metadata decorator, and the frontend mirrors the same role restrictions in its navigation guard. An audit trail records every DML operation on sensitive tables automatically via PostgreSQL triggers.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Zapiony/PUCE_UZDI_2026/llms.txt
Use this file to discover all available pages before exploring further.
Authentication
Password Hashing
Passwords are hashed usingbcrypt (version 6.x) with a cost factor of 10 before being stored in seguridad.prsn.prsnpass. Plain-text passwords are never persisted.
Token Flow
On a successfulPOST /api/v1/auth/login, the backend returns a token string that the frontend stores in localStorage under the key uzdi_token.
Frontend Axios Interceptor
Every outgoing request from the frontend automatically attaches the stored token as aBearer header via an axios request interceptor defined in src/services/api.ts:
401 Unauthorized responses by clearing localStorage and redirecting to /login:
Role-Based Access Control (RBAC)
Role Enum
All valid roles are defined insrc/common/enums/rol.enum.ts:
@Roles() Decorator
Defined in src/common/decorators/roles.decorator.ts, this decorator attaches role metadata to a controller class or method handler using NestJS’s SetMetadata:
@Roles() is not applied to a handler, RolesGuard allows all authenticated requests through.
RolesGuard
Defined in src/common/guards/roles.guard.ts, this guard reads the roles metadata set by @Roles() and compares it against req.user.rol:
req.user is populated by MockAuthMiddleware during development (see below) and will be populated by the JWT strategy in production.
MockAuthMiddleware
Defined in src/common/middleware/mock-auth.middleware.ts and applied globally in AppModule, this middleware inspects the Authorization header and injects a mock user object into req.user if a Bearer token is present:
AppModule for all routes:
SanitizationPipe
Defined in src/common/pipes/sanitization.pipe.ts and registered globally in main.ts before ValidationPipe, this pipe processes every incoming request body, query parameter, and route parameter using pure regex — it does not invoke the dompurify package at runtime.
For each value it encounters, it:
- Trims leading and trailing whitespace from strings
- Strips
<script>tags using a regex pattern - Escapes
<and>characters to<and>
Frontend RBAC
The frontend mirrors backend role restrictions using a route-level navigation guard insrc/router/index.ts. Each protected route has a minimum required tppr_id (person type ID stored in localStorage.uzdi_user):
tppr_id | Type | Access level |
|---|---|---|
1 | Técnico | Standard user — restricted views |
2 | Coordinador | Elevated access — management views |
3 | Administrativo / Director | Full access |
ROUTE_MIN_ROL map assigns a minimum tppr_id to each route path. The navigation guard reads tppr_id from localStorage.uzdi_user before each navigation and redirects to /app/dashboard if the user’s role does not meet the minimum requirement.
The
tppr_id stored in localStorage.uzdi_user is set at login time from the API response. It reflects the seguridad.tipo_persona catalog entry for the logged-in user, not the Rol enum used on the backend. The two systems are complementary: the backend Rol enum controls API endpoint access; the frontend tppr_id controls route and UI visibility.Audit Trail
Every DML operation (INSERT, UPDATE, DELETE) on audited tables is automatically logged toseguridad.historial by the fn_registrar_historial PostgreSQL trigger function. Application code does not need to write audit records manually.
The historial table schema:
| Column | Type | Description |
|---|---|---|
hist_id | SERIAL | Auto-incremented primary key |
tabla_afectada | VARCHAR(100) | Fully qualified table name: 'schema.table' |
registro_id | INT4 | Primary key value of the affected row |
usua_id | INT4 | Acting user — from app.usuario_id session setting, or 0 if unset |
fecha | TIMESTAMP | Timestamp of the operation (NOW()) |
accion | VARCHAR(100) | 'INSERT', 'UPDATE', or 'DELETE' |
estado_anterior | TEXT | JSON snapshot of the row before the operation (NULL on INSERT) |
estado_nuevo | TEXT | JSON snapshot of the row after the operation (NULL on DELETE) |
descripcion | TEXT | Always 'Registro automático generado por trigger' |
app.usuario_id session variable before executing the DML: