Fase 2: 04_sanitize.py - scrubbing de secretos de skills/agentes/plans (10 secretos unicos, gate en verde)

This commit is contained in:
2026-07-29 00:35:59 +00:00
parent 21bc219f31
commit 837f91060f
79 changed files with 10153 additions and 0 deletions
@@ -0,0 +1,158 @@
# 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):
```ts
// 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`:
```ts
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`
```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):
```ts
// 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: false``users()` y `user()` denegados
3. Usuario con `CreateUser: false``insertUser()` denegado
4. Usuario con `DeleteUser: false``deleteUser()` 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
```bash
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