Files
qwen3-6-lora/data/raw/sanitized/plans/puedes-ver-en-elnespacio-lovely-squirrel.md
T

188 lines
8.5 KiB
Markdown

# 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.