# Etapa 3 — Enforcement de permisos

## Contexto

Ro-ut es una plataforma de logística. Las etapas 0-2 están completadas. La Etapa 3 es el siguiente paso en el orden de ejecución: hacer que el campo `permissions` realmente proteja las operaciones GraphQL.

**Estado actual:**
- `userExtractor` (middleware en `src/server/middlewares/userExtractor.ts`) decodifica el JWT y guarda `req.usuario = decoded.sub` (string, ej. `"aleleb"`). Solo valida autenticación, **no verifica permisos**.
- Los schemas GraphQL (`user.schema.ts`, `company.schema.ts`, `office.schema.ts`) llaman a `userExtractor` en cada resolver, pero nadie lee `req.usuario` ni verifica permisos.
- `AdminUser` en `user.schema.ts` tiene `ViewUser`, `CreateUser`, `EditUser`, `DeleteUser` como booleanos, pero nunca se consultan.
- La tabla `User` en PostgreSQL tiene una columna `permissions` (JSONB) que almacena los permisos del usuario.
- El token JWT del test lleva `sub: "aleleb"` — no lleva permisos embebidos.

**Problema:** Cualquiera autenticado puede ejecutar todas las operaciones sin restricciones de permiso.

---

## Diseño de la solución

### Estrategia

1. **Crear un helper `checkPermission`** que lea `req.usuario` (username), haga query a la BD para obtener los permisos del usuario, y verifique un permiso específico.
2. **Actualizar `userExtractor`** para que después de decodificar el JWT, llame al helper y guarde `req.usuario` como un objeto con `{ id, permissions }` en lugar de solo el string.
3. **Actualizar los schemas** de User, Company y Office para verificar permisos antes de ejecutar cada operación.
4. **Actualizar los tests** para que verifiquen que los permisos se aplican correctamente.

### Patrón de permisos propuesto

```
AdminUser:
  ViewUser: boolean    // puede ver usuarios
  CreateUser: boolean  // puede crear usuarios
  EditUser: boolean    // puede editar usuarios
  DeleteUser: boolean  // puede eliminar usuarios

AdminCompany: (nuevo)
  ViewCompany: boolean // puede ver empresas
  CreateCompany: boolean // puede crear empresas
  EditCompany: boolean   // puede editar empresas
  DeleteCompany: boolean // puede eliminar empresas

AdminOffice: (nuevo)
  ViewOffice: boolean    // puede ver oficinas
  CreateOffice: boolean  // puede crear oficinas
  EditOffice: boolean    // puede editar oficinas
  DeleteOffice: boolean  // puede eliminar oficinas
```

---

## Plan de implementación

### Tarea 3.1 — Verificar permisos en controllers de User

**Archivos a modificar:**

1. **`src/server/middlewares/userExtractor.ts`** — Después de `req.usuario = usuario`, hacer query a la BD para obtener los permisos del usuario y guardar `req.usuario = { id: usuario, permissions: ... }`.

2. **`src/server/GraphQL/schema/user.schema.ts`** — En cada query/mutation, verificar el permiso correspondiente antes de ejecutar:
   - `user()` y `users()` → verificar `adminUser.ViewUser`
   - `insertUser()` → verificar `adminUser.CreateUser`
   - `editUser()` → verificar `adminUser.EditUser`
   - `deleteUser()` → verificar `adminUser.DeleteUser`

**Detalle del helper `checkPermission`:**

```typescript
// src/server/middlewares/userExtractor.ts (o archivo nuevo checkPermission.ts)
import db from '@models/apiPostgresModel/config/apiPostgres-connection';

export const checkPermission = async (username: string, permissionPath: string): Promise<boolean> => {
  const { rows } = await db.query(
    `SELECT "permissions" FROM "User" WHERE "user" = $1`,
    [username]
  );
  if (!rows.length || !rows[0].permissions) return false;
  
  // permissionPath es algo como "adminUser.ViewUser"
  const [namespace, key] = permissionPath.split('.');
  return !!rows[0].permissions[namespace]?.[key];
};
```

**Detalle de las verificaciones en el schema:**

En cada campo async de los ObjectType, antes de llamar al controller:

```typescript
// Ejemplo para query user()
@Field( () => User, { nullable: true } )
async user(@Arg('id') id: string, @Ctx() context: { req: any; res: any; }){
  await userExtractor({req: context.req, res: context.res, isGraphQL: true});
  
  // Verificar permiso
  const hasPermission = await checkPermission(
    context.req.usuario.id || context.req.usuario,
    'adminUser.ViewUser'
  );
  if (!hasPermission) {
    throw new GraphQLError('No tienes permiso para ver usuarios');
  }
  
  return getUserController({ args: { id } });
}
```

### Tarea 3.2 — Verificar permisos en mutations de User

Ya cubierto por el patrón anterior en `user.schema.ts`:
- `insertUser()` → `adminUser.CreateUser`
- `editUser()` → `adminUser.EditUser`
- `deleteUser()` → `adminUser.DeleteUser`

### Tarea 3.3 — Definir permisos para Company y Office

**Archivos a modificar:**

1. **`src/server/GraphQL/schema/company.schema.ts`** — Agregar `AdminCompany` con `ViewCompany`, `CreateCompany`, `EditCompany`, `DeleteCompany`. Verificar en cada campo async.

2. **`src/server/GraphQL/schema/office.schema.ts`** — Agregar `AdminOffice` con `ViewOffice`, `CreateOffice`, `EditOffice`, `DeleteOffice`. Verificar en cada campo async.

3. **`src/server/GraphQL/schema/user.schema.ts`** — Agregar `AdminCompany` y `AdminOffice` al ObjectType `Permissions` para que el schema GraphQL los exponga.

**Detalle de las verificaciones:**

Para Company:
- `company()` y `companies()` → `adminCompany.ViewCompany`
- `insertCompany()` → `adminCompany.CreateCompany`
- `editCompany()` → `adminCompany.EditCompany`
- `deleteCompany()` → `adminCompany.DeleteCompany`

Para Office:
- `office()`, `offices()`, `officesByCompany()` → `adminOffice.ViewOffice`
- `insertOffice()` → `adminOffice.CreateOffice`
- `editOffice()` → `adminOffice.EditOffice`
- `deleteOffice()` → `adminOffice.DeleteOffice`

### Tarea 3.4 — Actualizar tests

**Archivos a crear/modificar:**

1. **`src/server/tests/server/user/index.test.ts`** — Agregar tests que verifiquen:
   - Que un usuario sin `ViewUser` no puede ejecutar `usersQuery`
   - Que un usuario sin `CreateUser` no puede ejecutar `insertUser`
   - Que un usuario sin `EditUser` no puede ejecutar `editUser`
   - Que un usuario sin `DeleteUser` no puede ejecutar `deleteUser`

2. **`src/server/tests/server/company/index.test.ts`** — Agregar tests similares para Company.

3. **`src/server/tests/server/office/index.test.ts`** — Agregar tests similares para Office.

**Nota crítica sobre tests y SQL:**

Los tests de integración (`npm run test:backend`) usan `src/server/sql/ro-ut_app_pg.sql` (sin usuario seed) con un token JWT fijo (`sub: "aleleb"`). Los tests E2E usan `src/server/sql/ro-ut_app_pg_with_user.sql` (con usuario seed admin con permisos completos).

Para que los tests de integración funcionen con el enforcement de permisos, necesitamos:

1. **Agregar un usuario seed a `ro-ut_app_pg.sql`** (o crear un archivo `ro-ut_app_pg_with_test_user.sql` dedicado para integration tests) con un usuario `aleleb` con permisos configurables. El CI workflow en `.gitea/workflows/main-workflow.yml` ejecuta `psql ... -f src/server/sql/ro-ut_app_pg.sql` en el job `integration-back-end-testing`.

2. **Crear un helper `createTestToken(username)`** en `src/server/tests/configTest.ts` que genere JWTs con diferentes `sub` claims, para poder probar escenarios de "sin permisos" vs "con permisos".

3. **Los tests de permisos denegados** no pueden usar la BD real (porque el usuario seed tendrá permisos). Se necesita:
   - O bien crear un usuario de test con `permissions: {}` vacío
   - O bien mockear la query de permisos en los tests que deben fallar

---

## Resumen de archivos a modificar

| Archivo | Acción | Tarea |
|---------|--------|-------|
| `src/server/middlewares/userExtractor.ts` | Modificar | 3.1 - Agregar query de permisos |
| `src/server/GraphQL/schema/user.schema.ts` | Modificar | 3.1, 3.2, 3.3 - Agregar verificaciones + AdminCompany/AdminOffice |
| `src/server/GraphQL/schema/company.schema.ts` | Modificar | 3.3 - Agregar verificaciones de permisos |
| `src/server/GraphQL/schema/office.schema.ts` | Modificar | 3.3 - Agregar verificaciones de permisos |
| `src/server/tests/server/user/index.test.ts` | Modificar | 3.1, 3.2 - Tests de permisos User |
| `src/server/tests/server/company/index.test.ts` | Modificar | 3.3 - Tests de permisos Company |
| `src/server/tests/server/office/index.test.ts` | Modificar | 3.3 - Tests de permisos Office |

---

## Verificación

1. `npm run test:backend` — Todos los tests existentes deben pasar (los nuevos tests verificarán el enforcement).
2. `npm run lint` — Sin errores de linting.
3. `npm run build` — Compilación limpia.
4. Manual: ejecutar un query GraphQL con un token de usuario sin permisos → debe devolver error de permisos.
