7.2 KiB
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.ymllí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 conpermissionstodostrue
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):
// 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:
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
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()yUsersQuery.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):
// 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:
- Usuario con todos los permisos → todas las operaciones pasan (existing behavior)
- Usuario con
ViewUser: false→users()yuser()denegados - Usuario con
CreateUser: false→insertUser()denegado - Usuario con
DeleteUser: false→deleteUser()denegado - 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
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:
- Job
integration-back-end-testing(línea 62-103): aplica schema conro-ut_app_pg.sql, ejecutanpm run test:backend. Los tests de integración usan el JWT hardcodeado deconfigTest.ts— al actualizar el token conpermissions: {adminUser: {ViewUser: true, ...}}, los tests existentes pasan. - Job
end-to-end-testing(línea 106-166): aplica schema conro-ut_app_pg_with_user.sql(seed conpermissionstodostrue), build y corre Cypress E2E. El login real genera JWTs conpermissionsembebidos (dataLogin.ts), así que el enforcement funciona en producción. - Job
unit-front-end-testing(línea 19-34): tests React — no afectados. - 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:
- Invocar la skill
agent-orchestrator - Pasar este plan como instrucción al agente
- El agente ejecutará todos los cambios de código en background
- Verificar que
npm run test:backendpase - Crear PR con los cambios