# US-MGN-001-002-003: Validar Permisos en Runtime (Guards/Middleware)
**RF Asociado:** [RF-MGN-001-002](../../02-modelado/requerimientos-funcionales/mgn-001/RF-MGN-001-002-gestion-roles.md)
**Módulo:** MGN-001 - Fundamentos
**Epic:** Autenticación y Seguridad
**Prioridad:** P0 (MVP)
**Story Points:** 3
**Sprint:** Sprint 2
**Estado:** Ready for Development
**Fecha:** 2025-11-24
---
## User Story
**Como** sistema de seguridad,
**Quiero** validar automáticamente los permisos del usuario antes de ejecutar cualquier acción,
**Para** garantizar que solo usuarios autorizados puedan acceder a funcionalidades específicas.
---
## Descripción Detallada
Esta user story implementa el middleware/guard que valida permisos en cada request. El sistema debe:
1. Interceptar requests a endpoints protegidos
2. Extraer user_id y tenant_id del JWT token
3. Consultar permisos del usuario (roles + permisos, con caché)
4. Validar si tiene el permiso requerido (ej: 'sale.order.create')
5. Si tiene permiso: continuar con la acción
6. Si NO tiene permiso: retornar 403 Forbidden y registrar en audit_log
---
## Criterios de Aceptación
### Escenario 1: Usuario con permiso ejecuta acción
**Dado que** soy usuario con rol "Contador" que tiene permiso 'account.invoice.create',
**Cuando** intento crear una factura via POST /api/v1/invoices,
**Entonces** el middleware valida mis permisos, confirma que tengo el permiso, y permite la ejecución.
### Escenario 2: Usuario sin permiso intenta acción
**Dado que** soy usuario con rol "Auditor" que solo tiene 'account.invoice.read',
**Cuando** intento eliminar una factura via DELETE /api/v1/invoices/123,
**Entonces** el middleware retorna 403 "No tiene permisos para esta acción" y registra el intento en audit_log.
### Escenario 3: Administrador bypass RBAC
**Dado que** soy usuario con rol "Administrator" (is_admin=true),
**Cuando** intento cualquier acción,
**Entonces** el middleware hace bypass de validación de permisos y permite todo.
### Escenario 4: Usuario con múltiples roles (allow overrides deny)
**Dado que** tengo rol "Auditor" (solo read) y rol "Contador" (CRUD),
**Cuando** intento crear factura,
**Entonces** el sistema aplica política "allow overrides deny" y permite la acción (porque Contador lo permite).
### Escenario 5: Permisos cacheados actualizados
**Dado que** administrador actualiza mis permisos (agrega 'sale.order.create'),
**Cuando** intento crear orden de venta,
**Entonces** el sistema detecta caché inválido, recarga permisos, y permite la acción.
### Escenario 6: Frontend condiciona UI según permisos
**Dado que** no tengo permiso 'account.payment.delete',
**Cuando** el frontend consulta mis permisos via hook usePermissions(),
**Entonces** el botón "Eliminar Pago" no se renderiza en la UI.
---
## Reglas de Negocio
- **RN-1:** Todos los endpoints transaccionales deben estar protegidos con guard de permisos.
- **RN-2:** Rol Administrator (is_admin=true) hace bypass completo de RBAC.
- **RN-3:** Usuario con múltiples roles: "allow overrides deny".
- **RN-4:** Permisos se cachean en Redis con TTL 1 hora.
- **RN-5:** Al cambiar permisos de rol, se invalida caché de todos los usuarios con ese rol.
- **RN-6:** Intentos de acceso no autorizado se registran en audit_log para auditoría.
- **RN-7:** Performance: validación de permisos < 10ms (usando caché).
---
## Tareas Técnicas (Checklist de Implementación)
### Backend
- [ ] Crear guard: `PermissionsGuard` que implementa `CanActivate`
- [ ] Crear decorador: `@RequirePermission('model.action')` para endpoints
- [ ] Implementar método: `PermissionsService.getUserPermissions(userId)` con caché
- [ ] Implementar método: `PermissionsService.hasPermission(userId, permission)`
- [ ] Implementar lógica de "allow overrides deny" para múltiples roles
- [ ] Implementar bypass para is_admin=true
- [ ] Implementar caché de permisos en Redis (key: `permissions:user:{userId}`)
- [ ] Implementar invalidación de caché al actualizar permisos de rol
- [ ] Crear endpoint: `GET /api/v1/auth/me/permissions` - Listar permisos del usuario actual
- [ ] Registrar intentos no autorizados en audit_log (user_id, action, timestamp, ip)
- [ ] Agregar unit tests (10 test cases)
- [ ] Agregar integration tests (8 test cases)
- [ ] Documentar en Swagger respuestas 403
### Frontend
- [ ] Crear hook: `usePermissions(permissions: string[])` que retorna boolean[]
- [ ] Crear hook: `useHasPermission(permission: string)` que retorna boolean
- [ ] Crear componente: ``
- [ ] Crear API client: `authApi.getMyPermissions()`
- [ ] Implementar Zustand store: `useAuthStore.permissions`
- [ ] Cargar permisos del usuario en login y almacenar en store
- [ ] Implementar condicional rendering basado en permisos
- [ ] Agregar component tests (6 test cases)
- [ ] Documentar uso de hooks en README
### Database
- [ ] Verificar tablas necesarias (roles, permissions, user_roles)
- [ ] Crear tabla: `audit.access_log` (user_id, action, resource, success, ip, timestamp)
- [ ] Crear índice: `idx_access_log_user_timestamp` para queries de auditoría
- [ ] Crear vista materializada (opcional): `user_permissions_view` para performance
---
## Mockups / Wireframes
**No hay UI específica (es funcionalidad de backend/middleware).**
**Ejemplo de UI condicionado por permisos:**
```tsx
const { hasPermission } = usePermissions(['sale.order.create', 'sale.order.delete']);
return (
<>
{hasPermission('sale.order.create') && }
{hasPermission('sale.order.delete') && }
>
);
```
---
## Casos de Prueba (Test Scenarios)
### Pruebas Funcionales
1. **TC-001: Permiso concedido**
- **Input:** usuario con 'sale.order.create' intenta POST /orders
- **Expected:** Request pasa guard, acción ejecutada
2. **TC-002: Permiso denegado**
- **Input:** usuario sin 'sale.order.delete' intenta DELETE /orders/1
- **Expected:** 403 Forbidden, audit_log registrado
3. **TC-003: Administrator bypass**
- **Input:** usuario Administrator intenta cualquier acción
- **Expected:** Bypass validación, siempre permitido
4. **TC-004: Múltiples roles (allow overrides deny)**
- **Input:** usuario con RolA (deny) y RolB (allow)
- **Expected:** Acción permitida (allow wins)
5. **TC-005: Caché hit**
- **Input:** 2 requests seguidas del mismo usuario
- **Expected:** 1ra consulta DB, 2da usa caché Redis
6. **TC-006: Caché invalidation**
- **Input:** actualizar permisos de rol, usuario intenta acción
- **Expected:** Caché invalidado, consulta DB, permisos actualizados
7. **TC-007: Frontend condicional rendering**
- **Input:** usePermissions(['sale.order.create'])
- **Expected:** Hook retorna [true] si tiene permiso, [false] si no
### Pruebas No Funcionales
1. **Performance:**
- Validación con caché: < 10ms
- Validación sin caché (cache miss): < 50ms
2. **Seguridad:**
- No exponer información sensible en mensajes de error
- Registrar TODOS los intentos de acceso no autorizado
3. **Concurrencia:**
- Manejar race conditions en invalidación de caché
---
## Dependencias
- **User Stories bloqueantes:**
- US-MGN-001-002-001 (Crear Roles)
- US-MGN-001-002-002 (Asignar Permisos)
- **Módulos requeridos:** Redis para caché
- **Datos maestros necesarios:** Roles y permisos configurados
---
## Notas de Implementación
**Backend - NestJS Guard:**
```typescript
@Injectable()
export class PermissionsGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise {
const request = context.switchToHttp().getRequest();
const user = request.user; // Del JWT
const requiredPermission = this.reflector.get('permission', context.getHandler());
if (user.is_admin) return true; // Bypass
const hasPermission = await this.permissionsService.hasPermission(
user.id,
requiredPermission
);
if (!hasPermission) {
await this.auditLog(user.id, requiredPermission, false);
throw new ForbiddenException('No tiene permisos para esta acción');
}
return true;
}
}
```
**Frontend - React Hook:**
```typescript
export function usePermissions(permissions: string[]) {
const { user } = useAuthStore();
return permissions.map(perm =>
user.permissions?.includes(perm) || user.is_admin
);
}
```
---
## Estimación Detallada
| Tarea | Estimación |
|-------|------------|
| Backend Development | 2 horas |
| Frontend Development | 2 horas |
| Testing (Unit + Integration) | 1.5 horas |
| Code Review | 0.5 horas |
| **TOTAL** | **6 horas** |
**Equivalente:** 3 Story Points
---
## Definition of Done (DoD)
- [ ] PermissionsGuard implementado y funcionando
- [ ] Decorador @RequirePermission funcionando
- [ ] Caché de permisos en Redis implementado
- [ ] Hooks usePermissions y useHasPermission funcionando
- [ ] Audit log de accesos no autorizados funcionando
- [ ] Unit tests escritos y pasando (cobertura > 80%)
- [ ] Integration tests escritos y pasando
- [ ] Code review completado
- [ ] Documentación actualizada
- [ ] Merge a branch `develop` completado
---
## Referencias
- [RF-MGN-001-002: Gestión de Roles y Permisos](../../02-modelado/requerimientos-funcionales/mgn-001/RF-MGN-001-002-gestion-roles.md)
- [ET-BACKEND-MGN-001-002](../../02-modelado/especificaciones-tecnicas/backend/mgn-001/ET-BACKEND-MGN-001-002-gestion-roles.md)
- [ET-FRONTEND-MGN-001-002](../../02-modelado/especificaciones-tecnicas/frontend/mgn-001/ET-FRONTEND-MGN-001-002-gestion-roles.md)
- [TRACEABILITY-MGN-001.yaml](../../02-modelado/trazabilidad/TRACEABILITY-MGN-001.yaml)