# 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