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