# Plan: Etapa 3 — Enforcement de permisos

## Contexto

Ro-ut tiene un campo `permissions` en la tabla `User` (JSONB) que se guarda y retorna, pero **nunca se verifica** antes de ejecutar operaciones. Los resolvers de User/Company/Office llaman a `userExtractor` que inyecta `req.usuario` con solo el **username** (`decoded.sub` = string `"aleleba"`), pero no verifica permisos.

### Restricciones del CI

El CI usa dos archivos SQL:
- **Integración tests** (`.gitea/workflows/main-workflow.yml` línea 91): `src/server/sql/ro-ut_app_pg.sql` — schema sin usuario seed
- **E2E tests** (línea 137): `src/server/sql/ro-ut_app_pg_with_user.sql` — schema + admin user seed con `permissions` todos `true`

Los tests de integración (`src/server/tests/configTest.ts`) usan un JWT hardcodeado:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGVsZWJhIiwiaWF0IjoxNjczMzc0NDA0fQ...
```
Que decodifica a `{"sub":"aleleba","iat":1673374404}` — **sin campo `permissions`**.

Esto significa que los tests de integración **no usan BD** para autenticación — usan un JWT hardcodeado. Si implemento permission checking leyendo del JWT, los tests existentes fallarán porque `aleleba` no tiene `permissions` en el token.

## Plan de implementación

### 3.1 — Embed permisos en el JWT y actualizar `userExtractor`

**Archivo:** `src/server/controllers/controllerGraphQL/dataLogin.ts`

Al firmar el access token, incluir `permissions` en el payload (líneas 29 y 70):

```ts
// logIn (línea 29):
const sessionToken = jwt.sign(
    { sub: resultJson.user, permissions: parsedPermissions },
    config.authJwtSecret as Secret
);

// getLogIn (línea 70):
const sessionToken = jwt.sign(
    { sub: resultJson.user, permissions: parsedPermissions },
    config.authJwtSecret as Secret
);
```

Donde `parsedPermissions` es el objeto permissions parseado (JSON.parse si es string, o el objeto directo).

**Archivo:** `src/server/middlewares/userExtractor.ts`

Modificar para que `req.usuario` sea un objeto con `user` y `permissions`:

```ts
const usuario = decoded.sub;
const permissions = decoded.permissions ?? null;
req.usuario = { user: usuario, permissions };
```

### 3.2 — Actualizar el token de sesión para tests

**Archivo:** `src/server/tests/configTest.ts`

Actualizar `sessionToken` para incluir `permissions` en el JWT payload, de modo que los tests existentes sigan pasando. El token actual decodifica a `{"sub":"aleleba","iat":1673374404}` — se necesita `{"sub":"aleleba","permissions":{"adminUser":{"ViewUser":true,"CreateUser":true,"EditUser":true,"DeleteUser":true}},"iat":1673374404}` firmado con la misma secret (`7d84e455e8457d4a5c7c66562aca16b4a411ad6a743ea0f7b4f1dc56aa3c9eaf`).

### 3.3 — Crear función de verificación de permisos

**Nuevo archivo:** `src/server/middlewares/permissionChecker.ts`

```ts
export function checkPermission(
    usuario: { user: string; permissions: any },
    permission: string
): void {
    if (!usuario?.permissions?.adminUser?.[permission]) {
        throw new Error(`Permission denied: required ${permission}`);
    }
}
```

### 3.4 — Verificar permisos en los resolvers de User

**Archivo:** `src/server/GraphQL/schema/user.schema.ts`

En cada resolver, después de `userExtractor`, llamar `checkPermission`:

- `UsersQuery.user()` y `UsersQuery.users()` → `checkPermission(usuario, 'ViewUser')`
- `UserMutation.insertUser()` → `checkPermission(usuario, 'CreateUser')`
- `UserMutation.editUser()` → `checkPermission(usuario, 'EditUser')`
- `UserMutation.deleteUser()` → `checkPermission(usuario, 'DeleteUser')`

### 3.5 — Definir y verificar permisos para Company y Office

**Archivos:** `src/server/GraphQL/schema/company.schema.ts`, `src/server/GraphQL/schema/office.schema.ts`

Añadir campos en `AdminUser` (el tipo ya existe en user.schema.ts, se extiende):

```ts
// En AdminUser:
@Field() ViewCompany?: boolean;
@Field() CreateCompany?: boolean;
@Field() EditCompany?: boolean;
@Field() DeleteCompany?: boolean;
@Field() ViewOffice?: boolean;
@Field() CreateOffice?: boolean;
@Field() EditOffice?: boolean;
@Field() DeleteOffice?: boolean;
```

Verificar en cada resolver de Company/Office con `checkPermission`.

### 3.6 — Tests

**Nuevo archivo:** `src/server/tests/server/permissions/index.test.ts`

Tests que verifiquen:
1. Usuario con todos los permisos → todas las operaciones pasan (existing behavior)
2. Usuario con `ViewUser: false` → `users()` y `user()` denegados
3. Usuario con `CreateUser: false` → `insertUser()` denegado
4. Usuario con `DeleteUser: false` → `deleteUser()` denegado
5. Comportamiento similar para Company y Office

**Actualizar:** `src/server/tests/server/company/index.test.ts` y `src/server/tests/server/office/index.test.ts` — añadir tests de permisos.

## Archivos a modificar

| Archivo | Acción |
|---------|--------|
| `src/server/controllers/controllerGraphQL/dataLogin.ts` | Modificar: incluir `permissions` en JWT payload |
| `src/server/middlewares/userExtractor.ts` | Modificar: inyectar `permissions` en `req.usuario` |
| `src/server/middlewares/permissionChecker.ts` | **Nuevo**: función de verificación |
| `src/server/GraphQL/schema/user.schema.ts` | Modificar: llamar `checkPermission` en cada resolver |
| `src/server/GraphQL/schema/company.schema.ts` | Modificar: añadir permisos Company, llamar `checkPermission` |
| `src/server/GraphQL/schema/office.schema.ts` | Modificar: añadir permisos Office, llamar `checkPermission` |
| `src/server/tests/configTest.ts` | Modificar: actualizar sessionToken con permissions |
| `src/server/tests/server/permissions/index.test.ts` | **Nuevo**: tests de enforcement |

## Verificación local

```bash
npm run test:backend
```

Todos los tests existentes deben seguir pasando (el sessionToken actualizado tiene todos los permisos en `true`). Los nuevos tests verifican que sin permisos se deniega la operación.

## Verificación en CI

El workflow `.gitea/workflows/main-workflow.yml` ya ejecuta `npm run test:backend` en el job `integration-back-end-testing` (línea 95). La verificación de permisos se integra automáticamente:

1. **Job `integration-back-end-testing`** (línea 62-103): aplica schema con `ro-ut_app_pg.sql`, ejecuta `npm run test:backend`. Los tests de integración usan el JWT hardcodeado de `configTest.ts` — al actualizar el token con `permissions: {adminUser: {ViewUser: true, ...}}`, los tests existentes pasan.
2. **Job `end-to-end-testing`** (línea 106-166): aplica schema con `ro-ut_app_pg_with_user.sql` (seed con `permissions` todos `true`), build y corre Cypress E2E. El login real genera JWTs con `permissions` embebidos (dataLogin.ts), así que el enforcement funciona en producción.
3. **Job `unit-front-end-testing`** (línea 19-34): tests React — no afectados.
4. **Job `cypress-components-testing`** (línea 36-59): Cypress components — no afectados.

## Desarrollo del ticket con agent-orchestrator

**IMPORTANTE:** Este ticket debe desarrollarse usando la skill `agent-orchestrator` para orquestar un agente en background que ejecute la implementación completa. El flujo es:

1. Invocar la skill `agent-orchestrator`
2. Pasar este plan como instrucción al agente
3. El agente ejecutará todos los cambios de código en background
4. Verificar que `npm run test:backend` pase
5. Crear PR con los cambios
