# RF-ROLE-004: Guards y Middlewares RBAC

## Identificacion

| Campo | Valor |
|-------|-------|
| **ID** | RF-ROLE-004 |
| **Modulo** | MGN-003 Roles/RBAC |
| **Prioridad** | P0 - Critica |
| **Estado** | Ready |
| **Fecha** | 2025-12-05 |

---

## Descripcion

El sistema debe implementar mecanismos de control de acceso basado en roles (RBAC) que se apliquen automaticamente a todos los endpoints protegidos. Esto incluye guards de NestJS, decoradores personalizados y middlewares para validar permisos antes de ejecutar cualquier accion.

---

## Actores

| Actor | Descripcion |
|-------|-------------|
| Sistema | Valida permisos en cada request |
| Usuario | Sujeto de validacion de permisos |

---

## Precondiciones

1. Usuario autenticado (JWT valido)
2. Roles y permisos cargados en el sistema
3. Endpoint decorado con permisos requeridos

---

## Arquitectura RBAC

```
Request HTTP
     │
     ▼
┌─────────────────┐
│  JwtAuthGuard   │  Valida token JWT
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ TenantGuard     │  Verifica tenant del usuario
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ RbacGuard       │  Valida permisos requeridos
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Controller      │  Ejecuta logica de negocio
└─────────────────┘
```

---

## Flujo de Validacion

### Validacion Estandar

```
1. Request llega al servidor
2. JwtAuthGuard extrae y valida token
3. Sistema carga usuario y sus roles desde cache/DB
4. TenantGuard verifica que usuario pertenece al tenant
5. RbacGuard obtiene permisos requeridos del decorator
6. Sistema calcula permisos efectivos del usuario
7. Sistema verifica si tiene TODOS los permisos requeridos
8. Si tiene permisos: continua al controller
9. Si no tiene: retorna 403 Forbidden
```

### Validacion con Permisos Alternativos (OR)

```
1. Endpoint requiere: users:update OR users:admin
2. Usuario tiene: users:update
3. Sistema verifica si tiene AL MENOS UNO
4. Usuario tiene users:update -> acceso permitido
```

### Validacion Condicional (Owner)

```
1. Endpoint requiere: users:update OR ser owner del recurso
2. Sistema verifica permisos
3. Si no tiene permiso, verifica si es owner
4. Si es owner del recurso: acceso permitido
```

---

## Componentes del Sistema

### 1. Decoradores

```typescript
// Permiso requerido (AND)
@Permissions('users:read', 'users:update')
// Usuario debe tener AMBOS permisos

// Permiso alternativo (OR)
@AnyPermission('users:update', 'users:admin')
// Usuario debe tener AL MENOS UNO

// Rol requerido
@Roles('admin', 'manager')
// Usuario debe tener AL MENOS UNO de los roles

// Acceso publico (sin auth)
@Public()

// Owner o permiso
@OwnerOrPermission('users:update')
```

### 2. Guards

```typescript
// JwtAuthGuard - Ya implementado en MGN-001
// Valida token, extrae usuario

// TenantGuard
// Verifica tenant_id del usuario vs tenant del recurso

// RbacGuard
// Valida permisos segun decoradores
```

### 3. Interceptors

```typescript
// PermissionInterceptor
// Agrega permisos efectivos al request para uso en controller

// AuditInterceptor
// Registra acciones sensibles con info de permisos
```

---

## Reglas de Negocio

| ID | Regla |
|----|-------|
| RN-001 | Permisos se validan en CADA request (no solo al login) |
| RN-002 | Permisos efectivos se cachean por sesion (TTL 5 min) |
| RN-003 | Cambios de roles invalidan cache de permisos |
| RN-004 | Super Admin bypasea validacion de permisos |
| RN-005 | Endpoints publicos no requieren autenticacion |
| RN-006 | Error 403 no revela que permisos faltan (seguridad) |
| RN-007 | Logs de acceso denegado para auditoria |

---

## Criterios de Aceptacion

### Escenario 1: Acceso con permiso valido

```gherkin
Given un usuario con permiso "users:read"
  And endpoint GET /api/v1/users requiere "users:read"
When el usuario hace la solicitud
Then el sistema permite el acceso
  And retorna status 200 con los datos
```

### Escenario 2: Acceso denegado por falta de permiso

```gherkin
Given un usuario con permiso "users:read" solamente
  And endpoint DELETE /api/v1/users/:id requiere "users:delete"
When el usuario intenta eliminar
Then el sistema retorna status 403
  And el mensaje es "No tienes permiso para realizar esta accion"
  And NO revela que permiso falta
```

### Escenario 3: Wildcard permite acceso

```gherkin
Given un usuario con permiso "users:*"
  And endpoint requiere "users:delete"
When el usuario hace la solicitud
Then el sistema permite el acceso
  And wildcard cubre el permiso especifico
```

### Escenario 4: Super Admin bypass

```gherkin
Given un usuario con rol "super_admin"
  And endpoint requiere "cualquier:permiso"
When el usuario hace la solicitud
Then el sistema permite el acceso
  And no valida permisos especificos
```

### Escenario 5: Permisos alternativos (OR)

```gherkin
Given endpoint requiere "users:update" OR "users:admin"
  And usuario tiene solo "users:admin"
When el usuario hace la solicitud
Then el sistema permite el acceso
```

### Escenario 6: Owner puede acceder

```gherkin
Given endpoint PATCH /api/v1/users/:id con @OwnerOrPermission('users:update')
  And usuario es owner del recurso (su propio perfil)
  And usuario NO tiene permiso "users:update"
When el usuario actualiza su perfil
Then el sistema permite el acceso
  And aplica validaciones de owner
```

### Escenario 7: Cache de permisos

```gherkin
Given permisos de usuario cacheados
When el admin cambia roles del usuario
Then el cache se invalida
  And siguiente request recalcula permisos
```

---

## Notas Tecnicas

### Implementacion de Decoradores

```typescript
// decorators/permissions.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const PERMISSIONS_KEY = 'permissions';
export const Permissions = (...permissions: string[]) =>
  SetMetadata(PERMISSIONS_KEY, { permissions, mode: 'AND' });

export const AnyPermission = (...permissions: string[]) =>
  SetMetadata(PERMISSIONS_KEY, { permissions, mode: 'OR' });

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) =>
  SetMetadata(ROLES_KEY, roles);

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

export const OWNER_OR_PERMISSION_KEY = 'ownerOrPermission';
export const OwnerOrPermission = (permission: string) =>
  SetMetadata(OWNER_OR_PERMISSION_KEY, permission);
```

### Implementacion de RbacGuard

```typescript
// guards/rbac.guard.ts
@Injectable()
export class RbacGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private permissionService: PermissionService,
    private cacheManager: Cache,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // 1. Verificar si es ruta publica
    const isPublic = this.reflector.getAllAndOverride<boolean>(
      IS_PUBLIC_KEY,
      [context.getHandler(), context.getClass()],
    );
    if (isPublic) return true;

    // 2. Obtener usuario del request
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    if (!user) return false;

    // 3. Super Admin bypass
    if (user.roles.includes('super_admin')) return true;

    // 4. Obtener permisos requeridos
    const requiredPermissions = this.reflector.getAllAndOverride<{
      permissions: string[];
      mode: 'AND' | 'OR';
    }>(PERMISSIONS_KEY, [context.getHandler(), context.getClass()]);

    if (!requiredPermissions) return true; // No requiere permisos

    // 5. Obtener permisos efectivos (con cache)
    const effectivePermissions = await this.getEffectivePermissions(user.id);

    // 6. Validar permisos
    const { permissions, mode } = requiredPermissions;

    if (mode === 'AND') {
      return permissions.every(p => this.hasPermission(effectivePermissions, p));
    } else {
      return permissions.some(p => this.hasPermission(effectivePermissions, p));
    }
  }

  private hasPermission(userPerms: string[], required: string): boolean {
    // Verificar permiso directo
    if (userPerms.includes(required)) return true;

    // Verificar wildcard
    const [module] = required.split(':');
    if (userPerms.includes(`${module}:*`)) return true;

    // Verificar wildcard de segundo nivel
    const parts = required.split(':');
    if (parts.length === 3) {
      if (userPerms.includes(`${parts[0]}:${parts[1]}:*`)) return true;
    }

    return false;
  }

  private async getEffectivePermissions(userId: string): Promise<string[]> {
    const cacheKey = `permissions:${userId}`;

    // Intentar desde cache
    const cached = await this.cacheManager.get<string[]>(cacheKey);
    if (cached) return cached;

    // Calcular permisos
    const permissions = await this.permissionService.calculateEffective(userId);

    // Guardar en cache (5 minutos)
    await this.cacheManager.set(cacheKey, permissions, 300000);

    return permissions;
  }
}
```

### Implementacion de OwnerGuard

```typescript
// guards/owner.guard.ts
@Injectable()
export class OwnerGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private permissionService: PermissionService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const ownerPermission = this.reflector.get<string>(
      OWNER_OR_PERMISSION_KEY,
      context.getHandler(),
    );

    if (!ownerPermission) return true;

    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const resourceId = request.params.id;

    // Verificar si tiene permiso
    const hasPermission = await this.permissionService.userHas(
      user.id,
      ownerPermission,
    );
    if (hasPermission) return true;

    // Verificar si es owner
    const isOwner = user.id === resourceId;
    return isOwner;
  }
}
```

### Uso en Controllers

```typescript
// users.controller.ts
@Controller('api/v1/users')
@UseGuards(JwtAuthGuard, TenantGuard, RbacGuard)
export class UsersController {

  @Get()
  @Permissions('users:read')
  findAll() {
    // Solo usuarios con users:read
  }

  @Post()
  @Permissions('users:create')
  create(@Body() dto: CreateUserDto) {
    // Solo usuarios con users:create
  }

  @Patch(':id')
  @OwnerOrPermission('users:update')
  update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
    // Owner del recurso O usuarios con users:update
  }

  @Delete(':id')
  @Permissions('users:delete')
  remove(@Param('id') id: string) {
    // Solo usuarios con users:delete
  }

  @Get('export')
  @AnyPermission('users:export', 'users:admin')
  export() {
    // Usuarios con users:export O users:admin
  }
}

// public.controller.ts
@Controller('api/v1/public')
export class PublicController {

  @Get('health')
  @Public()
  health() {
    // Sin autenticacion
  }
}
```

### Invalidacion de Cache

```typescript
// Al cambiar roles de usuario
async updateUserRoles(userId: string, roleIds: string[]) {
  await this.userRoleRepository.update(userId, roleIds);

  // Invalidar cache de permisos
  await this.cacheManager.del(`permissions:${userId}`);

  // Emitir evento para invalidar en otros servicios
  this.eventEmitter.emit('user.roles.changed', { userId });
}

// Al modificar permisos de un rol
async updateRolePermissions(roleId: string, permissionIds: string[]) {
  await this.rolePermissionRepository.update(roleId, permissionIds);

  // Obtener usuarios con este rol
  const users = await this.userRoleRepository.findUsersByRole(roleId);

  // Invalidar cache de todos
  for (const user of users) {
    await this.cacheManager.del(`permissions:${user.id}`);
  }
}
```

---

## Respuestas de Error

```typescript
// 401 Unauthorized - No autenticado
{
  "statusCode": 401,
  "message": "No autenticado",
  "error": "Unauthorized"
}

// 403 Forbidden - Sin permiso
{
  "statusCode": 403,
  "message": "No tienes permiso para realizar esta accion",
  "error": "Forbidden"
}
// NOTA: No revelar que permiso falta por seguridad

// Log interno (no expuesto)
{
  "userId": "user-uuid",
  "endpoint": "DELETE /api/v1/users/123",
  "requiredPermission": "users:delete",
  "userPermissions": ["users:read"],
  "result": "denied",
  "timestamp": "2025-12-05T10:00:00Z"
}
```

---

## Dependencias

| ID | Descripcion |
|----|-------------|
| RF-AUTH-002 | JWT para autenticacion |
| RF-ROLE-001 | Roles del sistema |
| RF-ROLE-002 | Permisos del sistema |
| RF-ROLE-003 | Asignacion roles-usuarios |
| Redis | Cache de permisos |

---

## Estimacion

| Tarea | Puntos |
|-------|--------|
| Backend: Decoradores | 2 |
| Backend: RbacGuard | 3 |
| Backend: OwnerGuard | 2 |
| Backend: Cache de permisos | 2 |
| Backend: Invalidacion cache | 2 |
| Backend: Tests | 3 |
| Documentacion | 1 |
| **Total** | **15 SP** |

---

## Historial

| Version | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-12-05 | System | Creacion inicial |
