erp-core/docs/05-user-stories/mgn-001/US-MGN-001-002-003-validar-permisos-en-runtime.md

9.5 KiB

US-MGN-001-002-003: Validar Permisos en Runtime (Guards/Middleware)

RF Asociado: RF-MGN-001-002 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: <PermissionGuard permission="sale.order.create"><Button/></PermissionGuard>
  • 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:

const { hasPermission } = usePermissions(['sale.order.create', 'sale.order.delete']);

return (
  <>
    {hasPermission('sale.order.create') && <Button>Nueva Orden</Button>}
    {hasPermission('sale.order.delete') && <Button>Eliminar</Button>}
  </>
);

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:

@Injectable()
export class PermissionsGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    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:

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