metis-desk/docs/centralizacion-csrf-y-prop-drilling.md
2026-10-10 12:42:56 +02:00

74 lines
6.6 KiB
Markdown

# Centralización de CSRF y revisión de prop drilling
## Cambio aplicado
El token CSRF pertenece a la sesión HTTP, no a los formularios ni a las entidades.
`apps/web/src/main.tsx` configura una función que consulta la sesión actual en la caché
de TanStack Query (`identityKeys.session`). `modules/core/src/web/http/shared/api/csrf-fetch.ts`
lee esa función en cada petición de escritura y añade `x-csrf-token`.
No existe una segunda copia del token en React, localStorage o un contexto. La caché de
sesión sigue siendo la fuente única. GET/HEAD/OPTIONS permiten obtener la sesión sin token;
POST y las demás escrituras requieren el token hexadecimal de 64 caracteres que genera
el servidor. Un token ausente o inválido impide enviar la petición. El transporte solo
acepta rutas locales `/api/` y utiliza credenciales del mismo origen.
Se han eliminado `csrfToken` de props, controladores, callbacks y firmas de los clientes
HTTP de identity, companies, assets y resources. Las peticiones de fotos y logotipos usan
el mismo transporte, conservando su cuerpo binario. El contrato HTTP, las validaciones
de respuestas y errores de cada módulo y la protección del backend se mantienen.
La renovación de sesión tras login, configuración inicial, cambio de contraseña y logout
continúa utilizando las invalidaciones existentes. La siguiente petición lee el token que
haya en la caché en ese momento; no captura el token de un render anterior. Un 403 no se
reintenta automáticamente, para no repetir escrituras. Las pruebas aisladas configuran y
restauran su lector mediante la función de limpieza de `configureCsrfTokenReader`.
## Criterio de análisis
Se revisaron las props y su recorrido entre workspace, páginas, diálogos, formularios,
controles y hooks de los cinco módulos y la aplicación web. Una prop transmitida a un
hijo directo no constituye por sí sola un problema: importa si las capas intermedias solo
la retransmiten, si tiene una fuente compartida y si su alcance es local o global.
La corrección de las demás propiedades queda pendiente: esta tarea implementa CSRF y
analiza el resto, sin modificar sus contratos funcionales.
## Casos encontrados y recomendaciones
| Propiedades | Recorrido real representativo | Valoración y siguiente paso |
| --- | --- | --- |
| `company` / `companyId` | `CompanyDetail` → `CompanySection` → `WorkProfilesPage` → `WorkProfileEditorDialog` → `WorkProfileForm`; también las páginas de configuraciones y sus formularios | Prioridad alta. Un contexto limitado al espacio de la compañía podría aportar el identificador y los datos ya consultados. No crear estado duplicado ni hacer que formularios reutilizables dependan obligatoriamente de ese contexto. Conservar el identificador explícito en los clientes HTTP y en los listados globales. |
| `lookups` | `CompanyInventory` → `AssetInventoryPage` → `AssetEditorDialog` → `AssetForm`; también `WorkstationsPage` → `WorkstationEditorDialog` → `WorkstationForm` → `WorkstationAssetCreateDialog` | Prioridad media. Mezcla datos de sedes/personas con callbacks de alta rápida entre companies y assets. Separar ambos contratos antes de reducir su paso. Un proveedor limitado al inventario puede compartir datos; las acciones de alta deben seguir siendo aportadas por el módulo propietario. |
| `userName` | `App` → `CompaniesWorkspace` → `WorkspaceHeader` | Prioridad baja. Dato de sesión pasado a través de una capa. Un hook público de identidad puede ofrecer el usuario actual a la cabecera si aumenta su uso. Hoy solo hay una capa intermediaria: no justifica por sí sola un contexto adicional. |
| `scope`, `items`, plantillas y catálogos | Página de catálogo/inventario → diálogo de edición → formulario/controles | Datos y ámbito de la operación, en su mayoría consumidos también por las capas intermedias. Mantener explícitos; si varios consumidores consultan lo mismo, reutilizar hooks y claves de Query existentes. `scope` puede ser global o compañía: no convertirlo en una compañía implícita. |
| `onSaved`, `onClose`, `onCreated`, `onChange` | Página → diálogo → formulario/control | Contratos de comunicación local. Mantener: controlan cierre, selección del registro recién creado e invalidación. No reemplazarlos por un bus global. |
| `allowQuickCreate` | Formulario → control de marca/plantilla; formularios anidados establecen `false` | Opción local deliberada para limitar las altas anidadas. Mantener explícita para que el diálogo hijo no abra niveles adicionales por herencia accidental. |
| `accountNavigation`, `languageSwitcher`, `children`, `relatedContent` | `App` → `CompaniesWorkspace` → cabecera/configuración; render props de secciones | Composición de interfaz entre módulos. Son slots, no estado duplicado. Mantener para preservar independencia de módulos y evitar importar identity desde companies. |
| `initialId`, `initialCreate`, navegación y filtros iniciales | Workspace → sección/página → controlador | Traducción de ruta a estado de pantalla. La capa intermedia suele interpretar la ruta. Mantener la responsabilidad del routing; reducir solo retransmisiones innecesarias al trabajar en cada pantalla. |
## Orden sugerido
1. Revisar el alcance de compañía dentro de `CompanyDetail`, sin afectar pantallas globales.
2. Separar datos y acciones de `lookups` y evaluar un proveedor de inventario acotado.
3. Extraer un hook de usuario actual únicamente si más consumidores necesitan la sesión.
No crear un contexto global con todas las props: ocultaría dependencias de formularios,
complicaría las pruebas y podría mezclar ámbitos de compañías distintas.
## Verificación
- Pruebas del transporte: token leído de Query, rotación, borrado de caché, ausencia/invalidez,
lectura sin sesión, destino local, cuerpos JSON/binarios, headers, cancelación y ausencia
de reintentos de escrituras fallidas.
- Pruebas de contratos de identity, accesos, buzones y marcas adaptadas al transporte.
- Suite completa del monorepo: 277 pruebas correctas, incluida integración HTTP con MariaDB.
- Pruebas de espaciado: 3 correctas. Los reexports consecutivos pueden mantenerse agrupados
para que ESLint y la ordenación de exports de Biome no se contradigan.
- Build de producción: correcto.
- Biome y reglas de espaciado sobre ficheros modificados: correctos.
- Typecheck del módulo core: correcto. Las comprobaciones generales de tipos/lint/check
encuentran errores previos fuera del cambio:
`calendar.tsx` de vendor, `personId` antiguo en el fixture web y reglas de llaves en
código de fotos. No se modificó vendor ni se amplió el alcance del refactor para ellos.