Files
qwen3-6-lora/data/raw/sanitized/plans/quiero-avanzar-a-la-unified-bumblebee.md

6.7 KiB

Plan: Etapa 3 — Enforcement de Permisos

Contexto

Ro-ut v2.x tiene el campo permissions en el modelo User guardado y retornado por GraphQL, pero nunca se verifica antes de ejecutar operaciones. Cualquier usuario autenticado puede realizar cualquier acción (crear, editar, eliminar usuarios, empresas, oficinas).

El campo req.usuario actualmente contiene solo el user ID (string), no el objeto completo del usuario con sus permisos.

Plan de Implementación

Paso 1: Mejorar userExtractor para incluir el usuario completo

Archivo: src/server/middlewares/userExtractor.ts

Actualmente userExtractor solo extrae el JWT y setea req.usuario = usuario (el ID). Necesitamos:

  1. Hacer userExtractor asíncrono (ya lo es — await userExtractor(...))
  2. Después de decodificar el JWT, hacer un query a la DB para obtener el usuario completo con su campo permissions
  3. Setear req.usuario como el objeto completo del usuario (no solo el ID)
// Después de jwt.verify:
const { rows } = await db.query(
  `SELECT "Id", "user", "email", "name", "last_names", "type", "permissions"
   FROM "User" WHERE "Id" = $1`,
  [parseInt(decoded.sub)]
);
if (rows.length === 0) throw new Error('Usuario no encontrado');
req.usuario = rows[0]; // objeto completo con permissions (JSON string)

Esto requiere importar el connection DB en el middleware (similar a como lo hacen los models).

Paso 2: Crear función de verificación de permisos

Archivo nuevo: src/server/middlewares/permissionCheck.ts

export const checkPermission = (
  usuario: any,
  requiredPermission: string,
  action: string
): void => {
  const perm = usuario?.permissions?.adminUser;
  if (!perm || !perm[requiredPermission]) {
    throw new GraphQLError(
      `Permiso requerido: ${action}. Contacte a un administrador.`
    );
  }
};

Paso 3: Agregar permisos de Company y Office al schema

Archivo: src/server/GraphQL/schema/user.schema.ts

Extender AdminUser para incluir permisos de Company y Office:

@ObjectType('CompanyPermissions')
@InputType('InputCompanyPermissions')
export class CompanyPermissions {
    @Field() ViewCompany?: boolean;
    @Field() CreateCompany?: boolean;
    @Field() EditCompany?: boolean;
    @Field() DeleteCompany?: boolean;
}

@ObjectType('OfficePermissions')
@InputType('InputOfficePermissions')
export class OfficePermissions {
    @Field() ViewOffice?: boolean;
    @Field() CreateOffice?: boolean;
    @Field() EditOffice?: boolean;
    @Field() DeleteOffice?: boolean;
}

// Extender AdminUser:
@ObjectType('AdminUser')
@InputType('InputAdminUser')
export class AdminUser {
    @Field() ViewUser?: boolean;
    @Field() CreateUser?: boolean;
    @Field() EditUser?: boolean;
    @Field() DeleteUser?: boolean;
    @Field()
    companyPermissions?: CompanyPermissions;
    @Field()
    officePermissions?: OfficePermissions;
}

Paso 4: Agregar verificación de permisos en el schema layer

Cada campo en los schema types (UsersQuery, UserMutation, CompaniesQuery, CompanyMutation, OfficesQuery, OfficeMutation) ya llama userExtractor. Después de eso, agregar verificación de permisos:

src/server/GraphQL/schema/user.schema.ts — en cada campo de UsersQuery y UserMutation:

// Ejemplo para users query:
@Field(() => [User], { nullable: true })
async users(@Arg('limit') limit: number, @Arg('cursor') cursor: number, @Ctx() context: { req: any; res: any; }) {
    await userExtractor({req: context.req, res: context.res, isGraphQL: true});
    checkPermission(context.req.usuario, 'ViewUser', 'ViewUser'); // lanzar si no tiene
    return getUsersController({ args: { limit, cursor } });
}

Aplicar patrón similar para:

  • user() query → ViewUser
  • users() query → ViewUser
  • insertUser() mutation → CreateUser
  • editUser() mutation → EditUser
  • deleteUser() mutation → DeleteUser

src/server/GraphQL/schema/company.schema.ts — en cada campo de CompaniesQuery y CompanyMutation:

// getCompany/getCompanies → ViewCompany
// insertCompany → CreateCompany
// editCompany → EditCompany
// deleteCompany → DeleteCompany

src/server/GraphQL/schema/office.schema.ts — en cada campo de OfficesQuery y OfficeMutation:

// getOffice/getOffices/officesByCompany → ViewOffice
// insertOffice → CreateOffice
// editOffice → EditOffice
// deleteOffice → DeleteOffice

Paso 5: Actualizar tests

Los tests actuales esperan 200 OK siempre. Necesitamos:

  1. Tests de permisos positivos: crear un usuario con permisos limitados y verificar que puede hacer lo que tiene permiso
  2. Tests de permisos negativos: crear un usuario sin permisos y verificar que recibe error GraphQL

Archivos a actualizar:

  • src/server/tests/server/user/index.test.ts — agregar tests de permisos
  • src/server/tests/server/company/index.test.ts — agregar tests de permisos
  • src/server/tests/server/office/index.test.ts — agregar tests de permisos

Estrategia de tests:

  • Crear un usuario de prueba con ViewUser: true pero CreateUser: false
  • Verificar que users() funciona (200)
  • Verificar que insertUser() falla (GraphQL error)
  • Lo mismo para Company y Office

Paso 6: Actualizar seed de tests

El test de insertUser crea un admin con todos los permisos. Asegurar que el seed de tests tenga un usuario admin con todos los permisos habilitados para que los tests existentes sigan funcionando.

Archivos Modificados

Archivo Acción
src/server/middlewares/userExtractor.ts Modificar — fetch user from DB after JWT decode
src/server/middlewares/permissionCheck.ts Nuevo — función checkPermission
src/server/GraphQL/schema/user.schema.ts Modificar — extender AdminUser, agregar CompanyPermissions, OfficePermissions, agregar checks
src/server/GraphQL/schema/company.schema.ts Modificar — agregar checks de permisos
src/server/GraphQL/schema/office.schema.ts Modificar — agregar checks de permisos
src/server/tests/server/user/index.test.ts Modificar — agregar tests de permisos
src/server/tests/server/company/index.test.ts Modificar — agregar tests de permisos
src/server/tests/server/office/index.test.ts Modificar — agregar tests de permisos

Ejecución

Este plan será ejecutado en background por un agente usando la skill agent-orchestrator.

Verificación

  1. npm run lint — sin errores
  2. npm run build — compila sin errores
  3. npm run test:backend — tests existentes pasan (admin tiene todos los permisos) + nuevos tests de permisos pasan
  4. Verificar que un usuario sin permisos recibe un GraphQL error descriptivo al intentar operaciones restringidas