Files
qwen3-6-lora/data/raw/sanitized/plans/quiero-que-implementemos-la-silly-hamster.md
T

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.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):

// 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() 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):

// 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: falseusers() y user() denegados
  3. Usuario con CreateUser: falseinsertUser() denegado
  4. Usuario con DeleteUser: falsedeleteUser() 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

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