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:
- Hacer
userExtractorasíncrono (ya lo es —await userExtractor(...)) - Después de decodificar el JWT, hacer un query a la DB para obtener el usuario completo con su campo
permissions - Setear
req.usuariocomo 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 →ViewUserusers()query →ViewUserinsertUser()mutation →CreateUsereditUser()mutation →EditUserdeleteUser()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:
- Tests de permisos positivos: crear un usuario con permisos limitados y verificar que puede hacer lo que tiene permiso
- 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 permisossrc/server/tests/server/company/index.test.ts— agregar tests de permisossrc/server/tests/server/office/index.test.ts— agregar tests de permisos
Estrategia de tests:
- Crear un usuario de prueba con
ViewUser: trueperoCreateUser: 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
npm run lint— sin erroresnpm run build— compila sin erroresnpm run test:backend— tests existentes pasan (admin tiene todos los permisos) + nuevos tests de permisos pasan- Verificar que un usuario sin permisos recibe un GraphQL error descriptivo al intentar operaciones restringidas