159 lines
7.2 KiB
Markdown
159 lines
7.2 KiB
Markdown
# 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
|