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

8.5 KiB

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:

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

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