From d8a09888e82d28ffdcb751139ca9ca8808db22e9 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 10 Oct 2026 12:42:56 +0200 Subject: [PATCH] csrfToken corregido --- .lintrc | 9 ++ apps/web/src/app.tsx | 1 - apps/web/src/main.tsx | 8 +- docs/centralizacion-csrf-y-prop-drilling.md | 73 ++++++++++++ .../api/create-asset-relationship.api.ts | 8 +- .../end/api/end-asset-relationship.api.ts | 4 +- .../use-end-relationship-dialog-controller.ts | 3 +- .../types/end-relationship-dialog-props.ts | 1 - .../list/types/asset-relationships-props.ts | 1 - .../list/ui/asset-relationships.tsx | 5 +- .../use-relationship-form-controller.ts | 4 +- .../shared/types/relationship-form-props.ts | 1 - .../shared/ui/relationship-editor-dialog.tsx | 9 +- .../shared/ui/relationship-form.tsx | 3 +- .../ui/workstation-asset-create-dialog.tsx | 3 - .../assets/detail/ui/asset-detail-dialog.tsx | 5 +- .../detail/ui/asset-selection-panel.tsx | 10 +- .../assets/list/types/inventory-page-props.ts | 1 - .../src/web/assets/list/ui/inventory-page.tsx | 5 +- .../web/assets/shared/api/save-asset.api.ts | 4 +- .../controllers/use-asset-form-controller.ts | 10 +- .../src/web/assets/shared/inventory.test.tsx | 30 ++--- .../assets/shared/types/asset-form-props.ts | 1 - .../assets/shared/ui/asset-editor-dialog.tsx | 3 - .../src/web/assets/shared/ui/asset-form.tsx | 4 +- .../use-delete-brand-controller.ts | 4 +- .../delete/types/delete-brand-dialog-props.ts | 1 - .../brands/list/types/brands-page-props.ts | 3 - .../assets/src/web/brands/list/types/index.ts | 1 - .../src/web/brands/list/ui/brands-page.tsx | 13 +-- .../src/web/brands/shared/api/brand-api.ts | 20 ++-- .../src/web/brands/shared/brands.test.tsx | 26 ++--- .../controllers/use-brand-form-controller.ts | 4 +- .../brands/shared/types/brand-form-props.ts | 1 - .../brands/shared/types/brand-select-props.ts | 1 - .../src/web/brands/shared/ui/brand-select.tsx | 13 +-- .../create/ui/catalog-field-create-dialog.tsx | 4 +- .../use-catalog-form-controller.ts | 9 +- .../shared/types/catalog-form-props.ts | 1 - .../ui/catalog-relation-create-dialog.tsx | 4 +- .../use-catalog-relation-form-controller.ts | 3 +- .../types/catalog-relation-form-props.ts | 1 - .../ui/catalog-template-create-dialog.tsx | 10 +- .../use-catalog-template-form-controller.ts | 3 +- .../types/catalog-template-form-props.ts | 1 - .../shared/ui/catalog-template-form.tsx | 3 +- .../use-catalog-page-controller.ts | 4 +- .../catalog/list/types/catalog-page-props.ts | 1 - .../src/web/catalog/list/ui/catalog-page.tsx | 3 +- .../src/web/catalog/shared/api/catalog-api.ts | 20 ++-- .../src/web/catalog/shared/catalog.test.tsx | 9 +- .../shared/controllers/use-catalog-save.ts | 9 +- .../shared/ui/catalog-editor-dialog.tsx | 17 +-- .../src/web/shared/api/delete-record.api.ts | 7 +- .../src/web/shared/api/inventory-request.ts | 10 +- .../src/web/shared/api/support-request.ts | 10 +- .../web/shared/ui/delete-record-dialog.tsx | 4 +- .../list/types/asset-support-props.ts | 1 - .../list/ui/asset-support.tsx | 6 +- .../shared/api/save-support-connection.api.ts | 3 +- .../use-support-form-controller.ts | 3 +- .../shared/support.test.tsx | 5 +- .../shared/types/support-form-props.ts | 1 - .../ui/support-connection-editor-dialog.tsx | 3 - .../shared/ui/support-form.tsx | 3 +- .../src/web/support-tools/list/types/index.ts | 1 - .../list/types/support-tools-props.ts | 3 - .../list/ui/support-tools-dialog.tsx | 4 +- .../support-tools/list/ui/support-tools.tsx | 5 +- .../shared/api/save-support-tool.api.ts | 4 +- .../use-support-tool-form-controller.ts | 4 +- .../shared/types/support-tool-form-props.ts | 1 - .../shared/ui/support-tool-editor-dialog.tsx | 3 - .../list/types/workstations-page-props.ts | 1 - .../list/ui/workstations-page.tsx | 3 +- .../shared/api/save-workstation.api.ts | 13 +-- .../use-workstation-form-controller.ts | 4 +- .../shared/types/workstation-form-props.ts | 1 - .../shared/ui/workstation-editor-dialog.tsx | 3 - .../shared/ui/workstation-form.tsx | 3 +- .../use-quick-create-dialog-controller.ts | 3 +- .../create/types/access-quick-editor-props.ts | 1 - .../create/types/quick-create-dialog-props.ts | 1 - .../access/create/ui/access-quick-editor.tsx | 6 +- .../list/types/access-workspace-props.ts | 1 - .../web/access/list/ui/access-workspace.tsx | 3 +- .../src/web/access/shared/access.test.tsx | 5 +- .../web/access/shared/api/access-request.ts | 12 +- .../web/access/shared/api/save-access.api.ts | 9 +- .../use-access-form-fields-controller.ts | 3 +- .../access/shared/types/access-form-props.ts | 1 - .../access/shared/ui/access-editor-dialog.tsx | 3 - .../access/shared/ui/access-form-fields.tsx | 3 +- .../src/web/access/shared/ui/access-form.tsx | 1 - .../create/ui/application-quick-editor.tsx | 4 - .../application-configurations-page-props.ts | 1 - .../ui/application-configurations-page.tsx | 4 +- .../shared/api/application-api.ts | 18 +-- .../shared/application.test.tsx | 4 +- ...plication-configuration-form-controller.ts | 3 +- .../application-configuration-form-props.ts | 1 - ...pplication-configuration-editor-dialog.tsx | 3 - .../ui/application-configuration-form.tsx | 3 +- .../shared/api/save-assignment.api.ts | 11 +- .../use-assignment-form-controller.ts | 21 ++-- .../shared/types/assignment-form-props.ts | 1 - .../shared/ui/assignment-editor-dialog.tsx | 3 - .../assignments/shared/ui/assignment-form.tsx | 3 +- .../create/ui/certificate-quick-editor.tsx | 4 - .../certificate-configurations-page-props.ts | 1 - .../ui/certificate-configurations-page.tsx | 4 +- .../shared/api/certificate-api.ts | 18 +-- .../shared/certificate.test.tsx | 4 +- ...rtificate-configuration-form-controller.ts | 3 +- .../certificate-configuration-form-props.ts | 1 - ...ertificate-configuration-editor-dialog.tsx | 3 - .../ui/certificate-configuration-form.tsx | 3 +- .../change-logo/api/company-logo-api.ts | 7 +- .../detail/types/company-detail-props.ts | 1 - .../companies/detail/ui/company-detail.tsx | 3 - .../companies/detail/ui/company-inventory.tsx | 4 - .../companies/detail/ui/company-overview.tsx | 9 +- .../companies/detail/ui/company-section.tsx | 64 +++------- .../ui/inventory-quick-create-dialog.tsx | 4 - .../list/types/companies-page-props.ts | 1 - .../web/companies/list/ui/companies-page.tsx | 10 +- .../companies/shared/api/company-request.ts | 11 +- .../companies/shared/api/save-company.api.ts | 4 +- .../companies/shared/companies-page.test.tsx | 77 +++++-------- .../use-company-form-controller.ts | 11 +- .../shared/types/company-form-props.ts | 1 - .../types/dashboard-quick-create-props.ts | 1 - .../create/ui/dashboard-quick-create.tsx | 9 +- .../web/dashboard/shared/api/dashboard-api.ts | 4 +- .../view/types/dashboard-page-props.ts | 1 - .../web/dashboard/view/ui/dashboard-page.tsx | 3 +- .../list/ui/department-section.tsx | 11 +- .../shared/api/save-department.api.ts | 10 +- .../use-department-form-controller.ts | 4 +- .../shared/types/department-form-props.ts | 1 - .../create/ui/folder-quick-editor.tsx | 4 - .../list/types/folder-mappings-page-props.ts | 1 - .../list/ui/folder-mappings-page.tsx | 4 +- .../folder-mappings/shared/api/folder-api.ts | 9 +- .../use-folder-mapping-form-controller.ts | 4 +- .../shared/types/folder-mapping-form-props.ts | 1 - .../ui/folder-mapping-editor-dialog.tsx | 3 - .../shared/ui/folder-mapping-form.tsx | 3 +- .../intervention-template-create-dialog.tsx | 6 +- .../list/types/templates-page-props.ts | 1 - .../list/ui/templates-page.tsx | 3 +- .../shared/api/template-api.ts | 35 +++--- .../use-template-form-controller.ts | 12 +- .../shared/templates.test.tsx | 10 +- .../shared/types/template-form-props.ts | 1 - .../intervention-template-editor-dialog.tsx | 3 - .../create/ui/ticket-intervention-dialog.tsx | 8 +- .../create/ui/ticket-intervention-form.tsx | 9 +- .../list/types/interventions-page-props.ts | 1 - .../list/ui/interventions-page.tsx | 3 +- .../shared/api/intervention-api.ts | 11 +- .../use-intervention-form-controller.ts | 2 - .../shared/interventions.test.tsx | 6 +- .../shared/types/intervention-form-props.ts | 1 - .../shared/ui/intervention-editor-dialog.tsx | 3 - .../shared/ui/intervention-form.tsx | 6 +- .../create/ui/mail-quick-editor.tsx | 5 - .../types/mail-configurations-page-props.ts | 1 - .../list/ui/mail-configurations-page.tsx | 4 +- .../shared/api/mail-api.ts | 16 +-- .../use-mail-configuration-form-controller.ts | 3 +- .../mail-configurations/shared/mail.test.tsx | 18 +-- .../types/mail-configuration-form-props.ts | 1 - .../ui/mail-configuration-editor-dialog.tsx | 3 - .../shared/ui/mail-configuration-form.tsx | 3 +- .../shared/api/save-mailbox-use.api.ts | 9 +- .../use-mailbox-use-form-controller.ts | 3 +- .../shared/types/mailbox-use-form-props.ts | 1 - .../shared/ui/mailbox-use-editor-dialog.tsx | 3 - .../shared/ui/mailbox-use-form.tsx | 3 - .../create/ui/mailbox-create-dialog.tsx | 9 +- .../mailboxes/shared/api/mailbox-request.ts | 12 +- .../shared/types/mailbox-relations-props.ts | 1 - .../web/mailboxes/shared/ui/mailbox-form.tsx | 3 - .../mailboxes/shared/ui/mailbox-relations.tsx | 3 +- .../web/people/detail/ui/person-details.tsx | 7 +- .../src/web/people/list/ui/people-section.tsx | 4 - .../web/people/shared/api/save-person.api.ts | 45 +++----- .../controllers/use-person-form-controller.ts | 15 +-- .../people/shared/types/person-form-props.ts | 1 - .../ui/printer-profile-create-dialog.tsx | 9 +- .../printer-configurations-page-props.ts | 1 - .../list/ui/printer-configurations-page.tsx | 4 +- .../shared/api/printer-api.ts | 16 +-- ...e-printer-configuration-form-controller.ts | 3 +- .../shared/printer.test.tsx | 7 +- .../types/printer-configuration-form-props.ts | 1 - .../printer-configuration-editor-dialog.tsx | 3 - .../shared/ui/printer-configuration-form.tsx | 4 +- .../web/process-templates/list/types/index.ts | 1 - .../types/process-templates-page-props.ts | 3 - .../list/ui/process-templates-page.tsx | 5 +- .../shared/api/save-process-template.api.ts | 8 +- .../use-process-template-form-controller.ts | 4 +- .../types/process-template-form-props.ts | 1 - .../ui/process-template-editor-dialog.tsx | 3 - .../create/ui/process-quick-create-dialog.tsx | 6 +- .../use-process-detail-controller.ts | 4 +- .../detail/types/process-detail-props.ts | 1 - .../processes/detail/ui/process-detail.tsx | 10 +- .../processes/list/api/get-processes.api.ts | 4 +- .../use-processes-page-controller.ts | 3 +- .../list/types/processes-page-props.ts | 1 - .../web/processes/list/ui/processes-page.tsx | 10 +- .../refresh/api/refresh-process.api.ts | 9 +- .../processes/shared/api/process-request.ts | 8 +- .../web/processes/shared/processes.test.tsx | 19 +-- .../processes/start/api/start-process.api.ts | 4 +- .../use-process-start-form-controller.ts | 8 +- .../start/types/process-start-form-props.ts | 1 - .../start/ui/process-start-dialog.tsx | 4 +- .../processes/start/ui/process-start-form.tsx | 3 +- .../update/api/update-process.api.ts | 11 +- .../use-process-edit-form-controller.ts | 9 +- .../update/types/process-edit-form-props.ts | 1 - .../update/ui/process-edit-dialog.tsx | 4 +- .../create/ui/remote-quick-editor.tsx | 17 +-- .../types/remote-connections-page-props.ts | 1 - .../list/ui/remote-connections-page.tsx | 3 +- .../shared/api/remote-api.ts | 27 +++-- .../use-remote-connection-form-controller.ts | 3 +- .../remote-connections/shared/remote.test.tsx | 7 +- .../types/remote-connection-form-props.ts | 1 - .../ui/remote-connection-editor-dialog.tsx | 3 - .../shared/ui/remote-connection-form.tsx | 3 +- .../src/web/shared/api/delete-record.api.ts | 7 +- .../src/web/shared/client-contracts.test.ts | 11 +- .../types/configuration-quick-editor-props.ts | 1 - .../shared/ui/configuration-quick-editor.tsx | 5 +- .../web/shared/ui/delete-record-dialog.tsx | 4 +- .../src/web/sites/list/ui/site-section.tsx | 4 +- .../src/web/sites/shared/api/save-site.api.ts | 9 +- .../controllers/use-site-form-controller.ts | 5 +- .../web/sites/shared/types/site-form-props.ts | 1 - .../telework-authorizations-page-props.ts | 1 - .../list/ui/telework-authorizations-page.tsx | 3 +- .../shared/api/telework-api.ts | 16 +-- ...-telework-authorization-form-controller.ts | 3 +- .../shared/telework.test.tsx | 13 +-- .../telework-authorization-form-props.ts | 1 - .../telework-authorization-editor-dialog.tsx | 3 - .../shared/ui/telework-authorization-form.tsx | 3 +- .../create/types/ticket-quick-editor-props.ts | 1 - .../ui/intervention-ticket-create-dialog.tsx | 3 - .../tickets/create/ui/ticket-quick-editor.tsx | 20 +--- .../tickets/create/ui/ticket-quick-forms.tsx | 10 +- .../create/ui/ticket-target-create-dialog.tsx | 11 +- .../tickets/list/types/tickets-page-props.ts | 1 - .../src/web/tickets/list/ui/tickets-page.tsx | 4 +- .../src/web/tickets/shared/api/ticket-api.ts | 11 +- .../controllers/use-ticket-form-controller.ts | 3 +- .../src/web/tickets/shared/tickets.test.tsx | 8 +- .../tickets/shared/types/ticket-form-props.ts | 1 - .../shared/ui/ticket-editor-dialog.tsx | 3 - .../src/web/tickets/shared/ui/ticket-form.tsx | 4 +- .../web/tickets/shared/ui/ticket-targets.tsx | 3 - .../detail/ui/environment-content.tsx | 3 - .../detail/ui/profile-configurations.tsx | 13 +-- .../detail/ui/work-environment-page.tsx | 3 - .../shared/work-environment.test.tsx | 1 - .../list/types/work-profiles-page-props.ts | 1 - .../list/ui/work-profiles-page.tsx | 4 +- .../shared/api/work-profile-api.ts | 11 +- .../use-work-profile-form-controller.ts | 10 +- .../web/work-profiles/shared/profile.test.tsx | 7 +- .../shared/types/work-profile-form-props.ts | 1 - .../shared/ui/work-profile-editor-dialog.tsx | 9 +- .../shared/ui/work-profile-form.tsx | 3 +- .../catalogs/ui/global-catalog-content.tsx | 20 ++-- .../view/types/companies-workspace-props.ts | 1 - .../workspace/view/ui/companies-workspace.tsx | 4 +- .../workspace/view/ui/workspace-content.tsx | 13 +-- modules/core/package.json | 2 +- .../web/http/shared/api/csrf-fetch.test.ts | 109 ++++++++++++++++++ .../src/web/http/shared/api/csrf-fetch.ts | 35 ++++++ modules/core/src/web/index.ts | 3 +- .../change/api/change-password.api.ts | 4 +- .../change/ui/account-password-dialog.tsx | 12 +- .../reset/api/reset-user-password.api.ts | 4 +- .../use-password-form-controller.ts | 7 +- .../shared/types/password-form-props.ts | 1 - .../sessions/shared/api/authenticate.api.ts | 4 +- .../src/web/sessions/shared/api/logout.api.ts | 3 +- .../use-credential-form-controller.ts | 5 +- .../shared/types/credential-form-props.ts | 1 - .../src/web/shared/api/identity-api.test.ts | 22 ++-- .../src/web/shared/api/identity-request.ts | 11 +- .../web/users/create/api/create-user.api.ts | 3 +- .../src/web/users/list/types/index.ts | 1 - .../users/list/types/user-management-props.ts | 3 - .../users/list/ui/user-management-dialog.tsx | 4 +- .../src/web/users/list/ui/user-management.tsx | 12 +- .../controllers/use-user-form-controller.ts | 8 +- .../web/users/shared/types/user-form-props.ts | 1 - .../users/shared/ui/user-editor-dialog.tsx | 3 - .../web/users/update/api/update-user.api.ts | 4 +- .../use-authenticated-workspace-controller.ts | 5 +- .../view/ui/authenticated-workspace.tsx | 6 +- .../workspace/view/ui/custom-workspace.tsx | 7 +- .../workspace/view/ui/identity-workspace.tsx | 1 - .../create/ui/catalog-field-create-dialog.tsx | 4 +- .../use-catalog-form-controller.ts | 9 +- .../shared/types/catalog-form-props.ts | 1 - .../ui/catalog-template-create-dialog.tsx | 10 +- .../use-catalog-template-form-controller.ts | 3 +- .../types/catalog-template-form-props.ts | 1 - .../shared/ui/catalog-template-form.tsx | 3 +- .../use-catalog-page-controller.ts | 4 +- .../catalog/list/types/catalog-page-props.ts | 1 - .../src/web/catalog/list/ui/catalog-page.tsx | 3 +- .../src/web/catalog/shared/api/catalog-api.ts | 20 ++-- .../src/web/catalog/shared/catalog.test.tsx | 9 +- .../shared/controllers/use-catalog-save.ts | 9 +- .../shared/ui/catalog-editor-dialog.tsx | 10 +- .../list/types/resources-page-props.ts | 1 - .../web/resources/list/ui/resources-page.tsx | 3 +- .../web/resources/shared/api/inventory-api.ts | 14 +-- .../use-resource-form-controller.ts | 4 +- .../web/resources/shared/resources.test.tsx | 6 +- .../shared/types/resource-editor-props.ts | 1 - .../shared/types/resource-form-props.ts | 1 - .../shared/ui/resource-editor-dialog.tsx | 10 +- .../resources/shared/ui/resource-editor.tsx | 2 - .../web/resources/shared/ui/resource-form.tsx | 3 +- 334 files changed, 783 insertions(+), 1416 deletions(-) create mode 100644 docs/centralizacion-csrf-y-prop-drilling.md delete mode 100644 modules/assets/src/web/brands/list/types/brands-page-props.ts delete mode 100644 modules/assets/src/web/brands/list/types/index.ts delete mode 100644 modules/assets/src/web/support-tools/list/types/index.ts delete mode 100644 modules/assets/src/web/support-tools/list/types/support-tools-props.ts delete mode 100644 modules/companies/src/web/process-templates/list/types/index.ts delete mode 100644 modules/companies/src/web/process-templates/list/types/process-templates-page-props.ts create mode 100644 modules/core/src/web/http/shared/api/csrf-fetch.test.ts create mode 100644 modules/core/src/web/http/shared/api/csrf-fetch.ts delete mode 100644 modules/identity/src/web/users/list/types/index.ts delete mode 100644 modules/identity/src/web/users/list/types/user-management-props.ts diff --git a/.lintrc b/.lintrc index 7ea55ff..8bee4a6 100644 --- a/.lintrc +++ b/.lintrc @@ -90,6 +90,15 @@ "function-overload", "function" ] + }, + { + "blankLine": "any", + "prev": { + "selector": "ExportNamedDeclaration[source], ExportAllDeclaration" + }, + "next": { + "selector": "ExportNamedDeclaration[source], ExportAllDeclaration" + } } ], "@stylistic/lines-between-class-members": [ diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx index ad630c5..41205b9 100644 --- a/apps/web/src/app.tsx +++ b/apps/web/src/app.tsx @@ -26,7 +26,6 @@ export function App() { renderWorkspace={(snapshot, accountNavigation) => ( } userName={snapshot.user?.name ?? ""} /> diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 6b3a4d6..d65a699 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -1,4 +1,6 @@ -import { coreSpanish } from "@metis/core/client"; +import { configureCsrfTokenReader, coreSpanish } from "@metis/core/client"; +import { identityKeys } from "@metis/identity/client"; +import type { SessionResponseDTO } from "@metis/identity/common"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; @@ -16,6 +18,10 @@ if (!rootElement) { const { instance, initialization } = createAppI18n(); const queryClient = new QueryClient(); +configureCsrfTokenReader( + () => queryClient.getQueryData(identityKeys.session)?.csrfToken +); + initialization .then(() => { createRoot(rootElement).render( diff --git a/docs/centralizacion-csrf-y-prop-drilling.md b/docs/centralizacion-csrf-y-prop-drilling.md new file mode 100644 index 0000000..30eff86 --- /dev/null +++ b/docs/centralizacion-csrf-y-prop-drilling.md @@ -0,0 +1,73 @@ +# 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. diff --git a/modules/assets/src/web/asset-relationships/create/api/create-asset-relationship.api.ts b/modules/assets/src/web/asset-relationships/create/api/create-asset-relationship.api.ts index b0e0c24..588cf90 100644 --- a/modules/assets/src/web/asset-relationships/create/api/create-asset-relationship.api.ts +++ b/modules/assets/src/web/asset-relationships/create/api/create-asset-relationship.api.ts @@ -2,12 +2,8 @@ import type { RelationshipInputDTO } from "../../../../common/relationships"; import { RelationshipResponseSchema } from "../../../../common/relationships"; import { inventoryRequest } from "../../../shared/api/inventory-request"; -export async function createAssetRelationship( - companyId: string, - values: RelationshipInputDTO, - token: string -) { +export async function createAssetRelationship(companyId: string, values: RelationshipInputDTO) { return RelationshipResponseSchema.parse( - await inventoryRequest(`${companyId}/relationships`, { values }, token) + await inventoryRequest(`${companyId}/relationships`, { values }) ); } diff --git a/modules/assets/src/web/asset-relationships/end/api/end-asset-relationship.api.ts b/modules/assets/src/web/asset-relationships/end/api/end-asset-relationship.api.ts index 78e3675..c976f23 100644 --- a/modules/assets/src/web/asset-relationships/end/api/end-asset-relationship.api.ts +++ b/modules/assets/src/web/asset-relationships/end/api/end-asset-relationship.api.ts @@ -1,8 +1,8 @@ import { RelationshipResponseSchema } from "../../../../common/relationships"; import { inventoryRequest } from "../../../shared/api/inventory-request"; -export async function endAssetRelationship(companyId: string, id: string, token: string) { +export async function endAssetRelationship(companyId: string, id: string) { return RelationshipResponseSchema.parse( - await inventoryRequest(`${companyId}/relationships/${id}/end`, {}, token) + await inventoryRequest(`${companyId}/relationships/${id}/end`, {}) ); } diff --git a/modules/assets/src/web/asset-relationships/end/controllers/use-end-relationship-dialog-controller.ts b/modules/assets/src/web/asset-relationships/end/controllers/use-end-relationship-dialog-controller.ts index ef5a4ae..8800030 100644 --- a/modules/assets/src/web/asset-relationships/end/controllers/use-end-relationship-dialog-controller.ts +++ b/modules/assets/src/web/asset-relationships/end/controllers/use-end-relationship-dialog-controller.ts @@ -7,11 +7,10 @@ import type { EndRelationshipDialogProps } from "../types"; export function useEndRelationshipDialogController({ relationship, companyId, - csrfToken, }: EndRelationshipDialogProps) { const client = useQueryClient(); const mutation = useMutation({ - mutationFn: () => endAssetRelationship(companyId, relationship.id, csrfToken), + mutationFn: () => endAssetRelationship(companyId, relationship.id), onSuccess: () => client.invalidateQueries({ queryKey: inventoryKeys.company(companyId) }), }); diff --git a/modules/assets/src/web/asset-relationships/end/types/end-relationship-dialog-props.ts b/modules/assets/src/web/asset-relationships/end/types/end-relationship-dialog-props.ts index ebb7740..106a1b4 100644 --- a/modules/assets/src/web/asset-relationships/end/types/end-relationship-dialog-props.ts +++ b/modules/assets/src/web/asset-relationships/end/types/end-relationship-dialog-props.ts @@ -3,7 +3,6 @@ import type { RelationshipDTO } from "../../../../common"; export type EndRelationshipDialogProps = { relationship: RelationshipDTO; companyId: string; - csrfToken: string; sourceName: string; targetName: string; onClose: () => void; diff --git a/modules/assets/src/web/asset-relationships/list/types/asset-relationships-props.ts b/modules/assets/src/web/asset-relationships/list/types/asset-relationships-props.ts index 44caa4f..59d3501 100644 --- a/modules/assets/src/web/asset-relationships/list/types/asset-relationships-props.ts +++ b/modules/assets/src/web/asset-relationships/list/types/asset-relationships-props.ts @@ -1,6 +1,5 @@ export type AssetRelationshipsProps = { compact?: boolean; companyId: string; - csrfToken: string; assetId: string; }; diff --git a/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx b/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx index d609ecb..92e4efc 100644 --- a/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx +++ b/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx @@ -11,7 +11,7 @@ import { useAssetRelationshipsController } from "../controllers"; import type { AssetRelationshipsProps } from "../types"; export function AssetRelationships(props: AssetRelationshipsProps) { - const { companyId, csrfToken, assetId, compact = false } = props; + const { companyId, assetId, compact = false } = props; const { query, adding, setAdding, setEndingId, ending, active, ended, dateOf } = useAssetRelationshipsController(props); const { t } = useTranslation("assets"); @@ -85,7 +85,6 @@ export function AssetRelationships(props: AssetRelationshipsProps) { )} setDeleteId(undefined)} path={deleteId ? `/api/assets/${companyId}/relationships/${deleteId}/delete` : undefined} queryKey={["inventory", companyId]} @@ -94,7 +93,6 @@ export function AssetRelationships(props: AssetRelationshipsProps) { setAdding(false)} /> @@ -102,7 +100,6 @@ export function AssetRelationships(props: AssetRelationshipsProps) { {ending && ( setEndingId(undefined)} relationship={ending} sourceName={nameOf(ending.values.sourceId)} diff --git a/modules/assets/src/web/asset-relationships/shared/controllers/use-relationship-form-controller.ts b/modules/assets/src/web/asset-relationships/shared/controllers/use-relationship-form-controller.ts index b3a34bd..8ccc043 100644 --- a/modules/assets/src/web/asset-relationships/shared/controllers/use-relationship-form-controller.ts +++ b/modules/assets/src/web/asset-relationships/shared/controllers/use-relationship-form-controller.ts @@ -11,7 +11,6 @@ import type { RelationshipFormProps } from "../types"; export function useRelationshipFormController({ companyId, - csrfToken, assetId, onSaved, }: RelationshipFormProps) { @@ -29,8 +28,7 @@ export function useRelationshipFormController({ queryFn: () => getAssetInventory(companyId), }); const mutation = useMutation({ - mutationFn: (values: RelationshipInputDTO) => - createAssetRelationship(companyId, values, csrfToken), + mutationFn: (values: RelationshipInputDTO) => createAssetRelationship(companyId, values), onSuccess: async () => { await client.invalidateQueries({ queryKey: inventoryKeys.company(companyId) }); onSaved(); diff --git a/modules/assets/src/web/asset-relationships/shared/types/relationship-form-props.ts b/modules/assets/src/web/asset-relationships/shared/types/relationship-form-props.ts index 47c5dcb..b90a76e 100644 --- a/modules/assets/src/web/asset-relationships/shared/types/relationship-form-props.ts +++ b/modules/assets/src/web/asset-relationships/shared/types/relationship-form-props.ts @@ -1,6 +1,5 @@ export type RelationshipFormProps = { companyId: string; - csrfToken: string; assetId: string; onSaved: () => void; }; diff --git a/modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx b/modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx index 3b366fd..5e37875 100644 --- a/modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx +++ b/modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx @@ -6,13 +6,11 @@ import { RelationshipForm } from "./relationship-form"; export function RelationshipEditorDialog({ companyId, assetId, - csrfToken, onSaved, onOpenChange, }: { companyId: string; assetId: string; - csrfToken: string; onSaved: () => void; onOpenChange: (open: boolean) => void; }) { @@ -26,12 +24,7 @@ export function RelationshipEditorDialog({ open title={t("linkAsset")} > - + ); } diff --git a/modules/assets/src/web/asset-relationships/shared/ui/relationship-form.tsx b/modules/assets/src/web/asset-relationships/shared/ui/relationship-form.tsx index b7f0118..bcefd0c 100644 --- a/modules/assets/src/web/asset-relationships/shared/ui/relationship-form.tsx +++ b/modules/assets/src/web/asset-relationships/shared/ui/relationship-form.tsx @@ -13,7 +13,7 @@ import { useRelationshipFormController } from "../controllers"; import type { RelationshipFormProps } from "../types"; export function RelationshipForm(props: RelationshipFormProps) { - const { companyId, csrfToken } = props; + const { companyId } = props; const { typeId, setTypeId, @@ -94,7 +94,6 @@ export function RelationshipForm(props: RelationshipFormProps) { {quick && ( { setTypeId(savedId); diff --git a/modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx b/modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx index 80f7852..e47ec8f 100644 --- a/modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx +++ b/modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx @@ -6,14 +6,12 @@ import { AssetForm } from "../../shared"; export function WorkstationAssetCreateDialog({ companyId, - csrfToken, items, lookups, onSaved, onOpenChange, }: { companyId: string; - csrfToken: string; items: ComponentProps["items"]; lookups: ComponentProps["lookups"]; onSaved: (id: string) => void; @@ -34,7 +32,6 @@ export function WorkstationAssetCreateDialog({ ["asset"]; lookups: ComponentProps["lookups"]; companyId: string; - csrfToken: string; onClose: () => void; }) { const { t } = useTranslation("assets"); @@ -35,11 +33,10 @@ export function AssetDetailDialog({ wide > - + diff --git a/modules/assets/src/web/assets/detail/ui/asset-selection-panel.tsx b/modules/assets/src/web/assets/detail/ui/asset-selection-panel.tsx index bae91f6..d576e9e 100644 --- a/modules/assets/src/web/assets/detail/ui/asset-selection-panel.tsx +++ b/modules/assets/src/web/assets/detail/ui/asset-selection-panel.tsx @@ -12,7 +12,6 @@ import { AssetDetail } from "./asset-detail"; export function AssetSelectionPanel({ companyId, - csrfToken, asset, open, lookups, @@ -22,7 +21,6 @@ export function AssetSelectionPanel({ onEdit, }: { companyId: string; - csrfToken: string; asset: AssetDTO | undefined; open: boolean; lookups: InventoryLookups; @@ -58,19 +56,13 @@ export function AssetSelectionPanel({ - + diff --git a/modules/assets/src/web/assets/list/types/inventory-page-props.ts b/modules/assets/src/web/assets/list/types/inventory-page-props.ts index f64918f..f822077 100644 --- a/modules/assets/src/web/assets/list/types/inventory-page-props.ts +++ b/modules/assets/src/web/assets/list/types/inventory-page-props.ts @@ -2,7 +2,6 @@ import type { InventoryLookups } from "../../shared/types"; export type AssetInventoryPageProps = { companyId: string; - csrfToken: string; lookups: InventoryLookups; initialId?: string | undefined; initialCreate?: boolean | undefined; diff --git a/modules/assets/src/web/assets/list/ui/inventory-page.tsx b/modules/assets/src/web/assets/list/ui/inventory-page.tsx index 2796cb5..3ce04e6 100644 --- a/modules/assets/src/web/assets/list/ui/inventory-page.tsx +++ b/modules/assets/src/web/assets/list/ui/inventory-page.tsx @@ -23,7 +23,7 @@ function selectedRowClass(id: string, selectedId: string | undefined, detailOpen } export function AssetInventoryPage(props: AssetInventoryPageProps) { - const { companyId, csrfToken, lookups } = props; + const { companyId, lookups } = props; const { query, catalog, @@ -177,7 +177,6 @@ export function AssetInventoryPage(props: AssetInventoryPageProps) { asset={selected} canEdit={catalog.isSuccess} companyId={companyId} - csrfToken={csrfToken} lookups={lookups} onClose={() => setDetailOpen(false)} onEdit={setEditor} @@ -188,7 +187,6 @@ export function AssetInventoryPage(props: AssetInventoryPageProps) { setEditor(undefined)} @@ -203,7 +201,6 @@ export function AssetInventoryPage(props: AssetInventoryPageProps) { setViewId(undefined)} /> diff --git a/modules/assets/src/web/assets/shared/api/save-asset.api.ts b/modules/assets/src/web/assets/shared/api/save-asset.api.ts index 1dde312..941193b 100644 --- a/modules/assets/src/web/assets/shared/api/save-asset.api.ts +++ b/modules/assets/src/web/assets/shared/api/save-asset.api.ts @@ -2,8 +2,8 @@ import type { AssetInput } from "../../../../common/inventory"; import { AssetResponseSchema } from "../../../../common/inventory"; import { inventoryRequest } from "../../../shared/api/inventory-request"; -export async function saveAsset(companyId: string, values: AssetInput, token: string, id?: string) { +export async function saveAsset(companyId: string, values: AssetInput, id?: string) { return AssetResponseSchema.parse( - await inventoryRequest(`${companyId}${id ? `/${id}/update` : ""}`, { values }, token) + await inventoryRequest(`${companyId}${id ? `/${id}/update` : ""}`, { values }) ); } diff --git a/modules/assets/src/web/assets/shared/controllers/use-asset-form-controller.ts b/modules/assets/src/web/assets/shared/controllers/use-asset-form-controller.ts index 4ee9e0d..74e6da9 100644 --- a/modules/assets/src/web/assets/shared/controllers/use-asset-form-controller.ts +++ b/modules/assets/src/web/assets/shared/controllers/use-asset-form-controller.ts @@ -9,13 +9,7 @@ import { inventoryKeys } from "../../../shared/queries"; import { saveAsset } from "../api"; import type { AssetFormProps } from "../types"; -export function useAssetFormController({ - companyId, - csrfToken, - asset, - items, - onSaved, -}: AssetFormProps) { +export function useAssetFormController({ companyId, asset, items, onSaved }: AssetFormProps) { const client = useQueryClient(); const [creatingTemplate, setCreatingTemplate] = useState(false); const mounted = useRef(true); @@ -68,7 +62,7 @@ export function useAssetFormController({ (template?.values.kind === "template" ? template.values.sections : undefined) ?? []; const mutation = useMutation({ - mutationFn: (input: AssetInput) => saveAsset(companyId, input, csrfToken, asset?.id), + mutationFn: (input: AssetInput) => saveAsset(companyId, input, asset?.id), onSuccess: async (response) => { await Promise.all([ client.invalidateQueries({ queryKey: inventoryKeys.company(companyId) }), diff --git a/modules/assets/src/web/assets/shared/inventory.test.tsx b/modules/assets/src/web/assets/shared/inventory.test.tsx index 3c5913b..de35579 100644 --- a/modules/assets/src/web/assets/shared/inventory.test.tsx +++ b/modules/assets/src/web/assets/shared/inventory.test.tsx @@ -67,7 +67,7 @@ test("Inventario traducido con alta de cabecera y sin formulario junto al listad for (const language of ["es", "en"]) { const html = await render( language, - + ); assert.equal(html.includes(" { const html = await render( "es", - undefined} - /> + undefined} /> ); for (const label of ["Nueva plantilla", "Nueva sede"]) { @@ -102,7 +96,6 @@ test("El activo ofrece altas relacionadas, ayuda inferior y no encadena una terc "es", undefined} scope={companyId} @@ -136,12 +129,12 @@ test("Las fichas leen ambos sentidos y conservan la auditoría de relaciones ret }; const source = await render( "es", - , + , [link] ); const target = await render( "es", - , + , [link] ); @@ -151,7 +144,7 @@ test("Las fichas leen ambos sentidos y conservan la auditoría de relaciones ret assert.ok(source.includes("Retirar relación")); const ended = await render( "es", - , + , [ { ...link, @@ -170,7 +163,7 @@ test("El formulario de tipo de relación presenta ayuda justo antes de sus opera for (const language of ["es", "en"]) { const html = await render( language, - undefined} scope={companyId} /> + undefined} scope={companyId} /> ); assert.ok(html.indexOf('name="description"') < html.indexOf(" + ); assert.ok(html.includes(language === "es" ? "Nuevo puesto" : "New workstation")); @@ -204,12 +197,7 @@ test("Puestos de trabajo: listado traducido, alta en cabecera y formulario modal test("Puestos de trabajo: altas rápidas y ayuda inferior; un activo anidado no ofrece terceras altas", async () => { const html = await render( "es", - undefined} - /> + undefined} /> ); for (const label of ["Nueva sede", "Nueva persona", "Nuevo activo"]) { @@ -222,7 +210,6 @@ test("Puestos de trabajo: altas rápidas y ayuda inferior; un activo anidado no undefined} @@ -279,7 +266,6 @@ test("la ficha lateral agrupa información y ofrece acciones superiores y enlace asset={asset} canEdit companyId={companyId} - csrfToken="test" lookups={{ ...lookups, sites: [{ id: siteId, name: "Fábrica" }], diff --git a/modules/assets/src/web/assets/shared/types/asset-form-props.ts b/modules/assets/src/web/assets/shared/types/asset-form-props.ts index bf251d8..5c265e5 100644 --- a/modules/assets/src/web/assets/shared/types/asset-form-props.ts +++ b/modules/assets/src/web/assets/shared/types/asset-form-props.ts @@ -17,7 +17,6 @@ export type InventoryLookups = { export type AssetFormProps = { companyId: string; - csrfToken: string; asset?: AssetDTO | undefined; items: readonly CatalogItemDTO[]; lookups: InventoryLookups; diff --git a/modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx b/modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx index 3971e07..68bedd8 100644 --- a/modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx +++ b/modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx @@ -7,7 +7,6 @@ import { AssetForm } from "./asset-form"; export function AssetEditorDialog({ asset, companyId, - csrfToken, items, lookups, onSaved, @@ -15,7 +14,6 @@ export function AssetEditorDialog({ }: { asset: ComponentProps["asset"]; companyId: string; - csrfToken: string; items: ComponentProps["items"]; lookups: ComponentProps["lookups"]; onSaved: (id: string) => void; @@ -39,7 +37,6 @@ export function AssetEditorDialog({ {creatingTemplate && ( { if (!open) { diff --git a/modules/assets/src/web/brands/delete/controllers/use-delete-brand-controller.ts b/modules/assets/src/web/brands/delete/controllers/use-delete-brand-controller.ts index 987cee1..d94ac19 100644 --- a/modules/assets/src/web/brands/delete/controllers/use-delete-brand-controller.ts +++ b/modules/assets/src/web/brands/delete/controllers/use-delete-brand-controller.ts @@ -5,11 +5,11 @@ import type { BrandsResponseSchema } from "../../../../common/brands"; import { brandApi, brandKeys } from "../../shared/api"; import type { DeleteBrandDialogProps } from "../types"; -export function useDeleteBrandController({ brand, csrfToken }: DeleteBrandDialogProps) { +export function useDeleteBrandController({ brand }: DeleteBrandDialogProps) { const client = useQueryClient(); return useMutation({ - mutationFn: () => brandApi.remove(brand.id, csrfToken), + mutationFn: () => brandApi.remove(brand.id), onSuccess: async (response) => { client.setQueryData>(brandKeys.all, (previous) => previous diff --git a/modules/assets/src/web/brands/delete/types/delete-brand-dialog-props.ts b/modules/assets/src/web/brands/delete/types/delete-brand-dialog-props.ts index 5639bf7..70c7efe 100644 --- a/modules/assets/src/web/brands/delete/types/delete-brand-dialog-props.ts +++ b/modules/assets/src/web/brands/delete/types/delete-brand-dialog-props.ts @@ -2,6 +2,5 @@ import type { BrandDTO } from "../../../../common/brands"; export type DeleteBrandDialogProps = { brand: BrandDTO; - csrfToken: string; onClose: () => void; }; diff --git a/modules/assets/src/web/brands/list/types/brands-page-props.ts b/modules/assets/src/web/brands/list/types/brands-page-props.ts deleted file mode 100644 index 2cb3414..0000000 --- a/modules/assets/src/web/brands/list/types/brands-page-props.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type BrandCatalogPageProps = { - csrfToken: string; -}; diff --git a/modules/assets/src/web/brands/list/types/index.ts b/modules/assets/src/web/brands/list/types/index.ts deleted file mode 100644 index 2013a74..0000000 --- a/modules/assets/src/web/brands/list/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { BrandCatalogPageProps } from "./brands-page-props"; diff --git a/modules/assets/src/web/brands/list/ui/brands-page.tsx b/modules/assets/src/web/brands/list/ui/brands-page.tsx index fa6ada6..ab64837 100644 --- a/modules/assets/src/web/brands/list/ui/brands-page.tsx +++ b/modules/assets/src/web/brands/list/ui/brands-page.tsx @@ -14,10 +14,8 @@ import { DeleteBrandDialog } from "../../delete"; import { BrandEditorDialog } from "../../shared"; import { BrandLogo } from "../../shared/ui"; import { useBrandCatalogPageController } from "../controllers"; -import type { BrandCatalogPageProps } from "../types"; -export function BrandCatalogPage(props: BrandCatalogPageProps) { - const { csrfToken } = props; +export function BrandCatalogPage() { const { query, edit, setEdit, open, setOpen, deleting, setDeleting, status, setStatus, items } = useBrandCatalogPageController(); const { t, i18n } = useTranslation("assets"); @@ -137,19 +135,12 @@ export function BrandCatalogPage(props: BrandCatalogPageProps) { unavailable={query.isError} /> setOpen(false)} /> - {deleting && ( - setDeleting(undefined)} - /> - )} + {deleting && setDeleting(undefined)} />} ); } diff --git a/modules/assets/src/web/brands/shared/api/brand-api.ts b/modules/assets/src/web/brands/shared/api/brand-api.ts index 1539b27..ab8c77d 100644 --- a/modules/assets/src/web/brands/shared/api/brand-api.ts +++ b/modules/assets/src/web/brands/shared/api/brand-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { BrandInput } from "../../../../common/brands"; @@ -11,16 +12,11 @@ export class BrandApiError extends Error { } } -async function request( - path = "", - body?: { values: BrandInput } | Record, - token?: string -) { - const response = await fetch(`/api/asset-brands${path}`, { +async function request(path = "", body?: { values: BrandInput } | Record) { + const response = await csrfFetch(`/api/asset-brands${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined ? {} : { "content-type": "application/json", "x-csrf-token": token ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const data: unknown = await response.json(); @@ -38,10 +34,10 @@ export const brandApi = { async list() { return BrandsResponseSchema.parse(await request()); }, - async save(values: BrandInput, token: string, id?: string) { - return BrandResponseSchema.parse(await request(id ? `/${id}/update` : "", { values }, token)); + async save(values: BrandInput, id?: string) { + return BrandResponseSchema.parse(await request(id ? `/${id}/update` : "", { values })); }, - async remove(id: string, token: string) { - return BrandResponseSchema.parse(await request(`/${id}/delete`, {}, token)); + async remove(id: string) { + return BrandResponseSchema.parse(await request(`/${id}/delete`, {})); }, }; diff --git a/modules/assets/src/web/brands/shared/brands.test.tsx b/modules/assets/src/web/brands/shared/brands.test.tsx index 56601f3..e3d1f66 100644 --- a/modules/assets/src/web/brands/shared/brands.test.tsx +++ b/modules/assets/src/web/brands/shared/brands.test.tsx @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import { test } from "node:test"; +import { configureCsrfTokenReader } from "@metis/core/client"; import { UniqueID } from "@repo/rdx-ddd"; import { Maybe } from "@repo/rdx-utils"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; @@ -51,7 +52,7 @@ async function render(language: string, node: ReactNode, active = true) { test("Marcas: listado común traducido, filtros y alta modal sin formulario junto a la tabla", async () => { for (const language of ["es", "en"]) { - const html = await render(language, ); + const html = await render(language, ); assert.equal(html.includes(" { - const html = await render( - "es", - undefined} /> - ); + const html = await render("es", undefined} />); assert.ok(html.includes('readOnly=""')); assert.ok(html.includes('accept="image/svg+xml,.svg"')); assert.ok(html.includes("Quitar logotipo")); assert.ok(html.indexOf("Una marca identifica") < html.lastIndexOf("Guardar")); - const quick = await render( - "en", - undefined} quickCreate /> - ); + const quick = await render("en", undefined} quickCreate />); assert.equal(quick.includes('type="checkbox"'), false); assert.ok(quick.includes("Global scope")); }); -test("Marcas: eliminar envía CSRF y cuerpo vacío, valida la respuesta y propaga conflictos", async () => { +test("Marcas: eliminar envía CSRF y cuerpo vacío, valida la respuesta y propaga conflictos", async (context) => { + context.after(configureCsrfTokenReader(() => "a".repeat(64))); const originalFetch = globalThis.fetch; let status = 200; let response: unknown = { brand: { ...brand, builtin: false } }; @@ -93,7 +89,7 @@ test("Marcas: eliminar envía CSRF y cuerpo vacío, valida la respuesta y propag assert.equal(options?.body, "{}"); const headers = new Headers(options?.headers); - assert.equal(headers.get("x-csrf-token"), "test"); + assert.equal(headers.get("x-csrf-token"), "a".repeat(64)); assert.equal(headers.get("content-type"), "application/json"); return new Response(JSON.stringify(response), { @@ -102,14 +98,14 @@ test("Marcas: eliminar envía CSRF y cuerpo vacío, valida la respuesta y propag }); }; try { - assert.deepEqual(await brandApi.remove(brand.id, "test"), response); + assert.deepEqual(await brandApi.remove(brand.id), response); response = { brand: { id: "invalid" } }; - await assert.rejects(() => brandApi.remove(brand.id, "test")); + await assert.rejects(() => brandApi.remove(brand.id)); for (const code of ["BRAND_IN_USE", "BUILTIN_PROTECTED", "BRAND_NOT_FOUND"]) { status = code === "BRAND_NOT_FOUND" ? 404 : 409; response = { code }; await assert.rejects( - () => brandApi.remove(brand.id, "test"), + () => brandApi.remove(brand.id), (error: unknown) => error instanceof BrandApiError && error.code === code ); } @@ -123,7 +119,6 @@ test("Marcas: selector mantiene valores antiguos e inactivos y respeta el límit label: "Marca", brandId: brand.id, legacyName: "LG", - csrfToken: "test", disabled: false, required: false, allowQuickCreate: true, @@ -183,7 +178,6 @@ test("Marcas: el campo de serie se identifica por referencia de catálogo, no po { @@ -75,7 +75,7 @@ export function useBrandFormController({ csrfToken, brand, onSaved }: BrandFormP }; const mutation = useMutation({ - mutationFn: (values: BrandInput) => brandApi.save(values, csrfToken, brand?.id), + mutationFn: (values: BrandInput) => brandApi.save(values, brand?.id), onSuccess: async (response) => { // Publicar primero garantiza que el selector pueda mostrar y seleccionar el nuevo valor. client.setQueryData<{ diff --git a/modules/assets/src/web/brands/shared/types/brand-form-props.ts b/modules/assets/src/web/brands/shared/types/brand-form-props.ts index 5977d7d..21177a2 100644 --- a/modules/assets/src/web/brands/shared/types/brand-form-props.ts +++ b/modules/assets/src/web/brands/shared/types/brand-form-props.ts @@ -1,7 +1,6 @@ import type { BrandDTO } from "../../../../common/brands"; export type BrandFormProps = { - csrfToken: string; brand?: BrandDTO; onSaved: (brand: BrandDTO) => void; quickCreate?: boolean; diff --git a/modules/assets/src/web/brands/shared/types/brand-select-props.ts b/modules/assets/src/web/brands/shared/types/brand-select-props.ts index 8f44988..9ecfb08 100644 --- a/modules/assets/src/web/brands/shared/types/brand-select-props.ts +++ b/modules/assets/src/web/brands/shared/types/brand-select-props.ts @@ -5,7 +5,6 @@ export type BrandSelectProps = { label: string; brandId: string | null; legacyName: string; - csrfToken: string; disabled: boolean; required: boolean; allowQuickCreate: boolean; diff --git a/modules/assets/src/web/brands/shared/ui/brand-select.tsx b/modules/assets/src/web/brands/shared/ui/brand-select.tsx index 75b02e9..3993f08 100644 --- a/modules/assets/src/web/brands/shared/ui/brand-select.tsx +++ b/modules/assets/src/web/brands/shared/ui/brand-select.tsx @@ -11,17 +11,7 @@ import { BrandEditorDialog } from "./brand-editor-dialog"; const LEGACY = "legacy"; export function BrandSelect(props: BrandSelectProps) { - const { - id, - label, - brandId, - legacyName, - csrfToken, - disabled, - required, - allowQuickCreate, - onChange, - } = props; + const { id, label, brandId, legacyName, disabled, required, allowQuickCreate, onChange } = props; const { query, open, setOpen, brands, legacy, options } = useBrandSelectController(props); const { t } = useTranslation("assets"); @@ -73,7 +63,6 @@ export function BrandSelect(props: BrandSelectProps) { )} {open && ( { onChange(brand); diff --git a/modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx b/modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx index cd96c21..42cd526 100644 --- a/modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx +++ b/modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx @@ -5,12 +5,10 @@ import { CatalogFieldForm } from "../../shared"; export function CatalogFieldCreateDialog({ scope, - csrfToken, onSaved, onOpenChange, }: { scope: string; - csrfToken: string; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; }) { @@ -25,7 +23,7 @@ export function CatalogFieldCreateDialog({ title={t("newField")} >

{t("quickNotice")}

- + ); } diff --git a/modules/assets/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts b/modules/assets/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts index f832fc9..2fa51de 100644 --- a/modules/assets/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts +++ b/modules/assets/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts @@ -5,12 +5,7 @@ import { useCatalogSave } from "../../../catalog/shared/controllers"; import { FieldFormSchema } from "../schemas"; import type { CatalogFieldFormProps, FieldFormValues } from "../types"; -export function useCatalogFieldFormController({ - scope, - csrfToken, - item, - onSaved, -}: CatalogFieldFormProps) { +export function useCatalogFieldFormController({ scope, item, onSaved }: CatalogFieldFormProps) { const current = item?.values.kind === "field" ? item.values : undefined; const form = useForm({ resolver: zodResolver(FieldFormSchema), @@ -23,7 +18,7 @@ export function useCatalogFieldFormController({ choicesText: current?.choices.join("\n") ?? "", }, }); - const mutation = useCatalogSave(scope, csrfToken, onSaved, item?.id); + const mutation = useCatalogSave(scope, onSaved, item?.id); const type = form.watch("type"); const submit = form.handleSubmit(({ choicesText, ...values }) => mutation.mutate({ diff --git a/modules/assets/src/web/catalog-fields/shared/types/catalog-form-props.ts b/modules/assets/src/web/catalog-fields/shared/types/catalog-form-props.ts index 840378b..06149b8 100644 --- a/modules/assets/src/web/catalog-fields/shared/types/catalog-form-props.ts +++ b/modules/assets/src/web/catalog-fields/shared/types/catalog-form-props.ts @@ -8,7 +8,6 @@ export type FieldFormValues = z.infer; export type CatalogFieldFormProps = { scope: string; - csrfToken: string; item?: CatalogItemDTO | undefined; onSaved: CatalogSaved; }; diff --git a/modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx b/modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx index f89632d..eed650e 100644 --- a/modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx +++ b/modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx @@ -5,12 +5,10 @@ import { CatalogRelationForm } from "../../shared"; export function CatalogRelationCreateDialog({ companyId, - csrfToken, onSaved, onOpenChange, }: { companyId: string; - csrfToken: string; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; }) { @@ -25,7 +23,7 @@ export function CatalogRelationCreateDialog({ title={t("newRelation")} >

{t("quickRelationNotice")}

- + ); } diff --git a/modules/assets/src/web/catalog-relations/shared/controllers/use-catalog-relation-form-controller.ts b/modules/assets/src/web/catalog-relations/shared/controllers/use-catalog-relation-form-controller.ts index 322fbc9..da7ae61 100644 --- a/modules/assets/src/web/catalog-relations/shared/controllers/use-catalog-relation-form-controller.ts +++ b/modules/assets/src/web/catalog-relations/shared/controllers/use-catalog-relation-form-controller.ts @@ -8,7 +8,6 @@ import type { CatalogRelationFormProps } from "../types"; export function useCatalogRelationFormController({ scope, - csrfToken, item, onSaved, }: CatalogRelationFormProps) { @@ -23,7 +22,7 @@ export function useCatalogRelationFormController({ symmetric: false, }, }); - const mutation = useCatalogSave(scope, csrfToken, onSaved, item?.id); + const mutation = useCatalogSave(scope, onSaved, item?.id); const symmetric = form.watch("symmetric"); const submit = form.handleSubmit((values) => mutation.mutate(values)); diff --git a/modules/assets/src/web/catalog-relations/shared/types/catalog-relation-form-props.ts b/modules/assets/src/web/catalog-relations/shared/types/catalog-relation-form-props.ts index 1a832e1..b78a4ad 100644 --- a/modules/assets/src/web/catalog-relations/shared/types/catalog-relation-form-props.ts +++ b/modules/assets/src/web/catalog-relations/shared/types/catalog-relation-form-props.ts @@ -3,7 +3,6 @@ import type { CatalogSaved } from "../../../catalog/shared/controllers"; export type CatalogRelationFormProps = { scope: string; - csrfToken: string; item?: CatalogItemDTO | undefined; onSaved: CatalogSaved; }; diff --git a/modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx b/modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx index b794da5..3851269 100644 --- a/modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx +++ b/modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx @@ -6,13 +6,11 @@ import { CatalogTemplateForm } from "../../shared"; export function CatalogTemplateCreateDialog({ scope, - csrfToken, items, onSaved, onOpenChange, }: { scope: string; - csrfToken: string; items: ComponentProps["items"]; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; @@ -29,13 +27,7 @@ export function CatalogTemplateCreateDialog({ wide >

{t("relatedNotice")}

- + ); } diff --git a/modules/assets/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts b/modules/assets/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts index 3c34025..56ee4b7 100644 --- a/modules/assets/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts +++ b/modules/assets/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts @@ -11,7 +11,6 @@ import type { CatalogTemplateFormProps, TemplateFormValues } from "../types"; export function useCatalogTemplateFormController({ scope, - csrfToken, item, copy, items, @@ -36,7 +35,7 @@ export function useCatalogTemplateFormController({ sections: layout.sections, }, }); - const mutation = useCatalogSave(scope, csrfToken, onSaved, item?.id); + const mutation = useCatalogSave(scope, onSaved, item?.id); const selected = form.watch("fields"); const fields = items.filter((entry) => entry.values.kind === "field"); diff --git a/modules/assets/src/web/catalog-templates/shared/types/catalog-template-form-props.ts b/modules/assets/src/web/catalog-templates/shared/types/catalog-template-form-props.ts index f009f9e..afd7e40 100644 --- a/modules/assets/src/web/catalog-templates/shared/types/catalog-template-form-props.ts +++ b/modules/assets/src/web/catalog-templates/shared/types/catalog-template-form-props.ts @@ -8,7 +8,6 @@ export type TemplateFormValues = z.infer; export type CatalogTemplateFormProps = { scope: string; - csrfToken: string; item?: CatalogItemDTO | undefined; copy?: CatalogItemDTO | undefined; items: readonly CatalogItemDTO[]; diff --git a/modules/assets/src/web/catalog-templates/shared/ui/catalog-template-form.tsx b/modules/assets/src/web/catalog-templates/shared/ui/catalog-template-form.tsx index 47f8222..772907a 100644 --- a/modules/assets/src/web/catalog-templates/shared/ui/catalog-template-form.tsx +++ b/modules/assets/src/web/catalog-templates/shared/ui/catalog-template-form.tsx @@ -17,7 +17,7 @@ import { useCatalogTemplateFormController } from "../controllers"; import type { CatalogTemplateFormProps } from "../types"; export function CatalogTemplateForm(props: CatalogTemplateFormProps) { - const { scope, csrfToken, allowQuickCreate = true } = props; + const { scope, allowQuickCreate = true } = props; const { creatingField, setCreatingField, form, mutation, selected, fields, add, submit } = useCatalogTemplateFormController(props); const { t } = useTranslation("assets"); @@ -117,7 +117,6 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) { {creatingField && ( { if (!open) { setCreatingField(false); diff --git a/modules/assets/src/web/catalog/list/controllers/use-catalog-page-controller.ts b/modules/assets/src/web/catalog/list/controllers/use-catalog-page-controller.ts index 67b40d6..65330c2 100644 --- a/modules/assets/src/web/catalog/list/controllers/use-catalog-page-controller.ts +++ b/modules/assets/src/web/catalog/list/controllers/use-catalog-page-controller.ts @@ -7,7 +7,7 @@ import type { CatalogEditor } from "../../shared"; import { catalogApi, catalogKeys } from "../../shared/api"; import type { AssetCatalogPageProps } from "../types"; -export function useAssetCatalogPageController({ scope, csrfToken }: AssetCatalogPageProps) { +export function useAssetCatalogPageController({ scope }: AssetCatalogPageProps) { const client = useQueryClient(); const { searchParams, setSearchParams } = useBrowserSearchParams(); const requestedTab = searchParams.get("tab"); @@ -42,7 +42,7 @@ export function useAssetCatalogPageController({ scope, csrfToken }: AssetCatalog }); const mutation = useMutation({ mutationFn: ({ item, active }: { item: CatalogItemDTO; active: boolean | null }) => - catalogApi.status(scope, item.id, active, csrfToken), + catalogApi.status(scope, item.id, active), onSuccess: async (response) => { await client.invalidateQueries({ queryKey: ["asset-catalog"] }); client.setQueryData(catalogKeys.scope(scope), response); diff --git a/modules/assets/src/web/catalog/list/types/catalog-page-props.ts b/modules/assets/src/web/catalog/list/types/catalog-page-props.ts index 57ac9f9..b7068e7 100644 --- a/modules/assets/src/web/catalog/list/types/catalog-page-props.ts +++ b/modules/assets/src/web/catalog/list/types/catalog-page-props.ts @@ -1,4 +1,3 @@ export type AssetCatalogPageProps = { scope: string; - csrfToken: string; }; diff --git a/modules/assets/src/web/catalog/list/ui/catalog-page.tsx b/modules/assets/src/web/catalog/list/ui/catalog-page.tsx index 09a8723..6ed3f64 100644 --- a/modules/assets/src/web/catalog/list/ui/catalog-page.tsx +++ b/modules/assets/src/web/catalog/list/ui/catalog-page.tsx @@ -10,7 +10,7 @@ import type { AssetCatalogPageProps } from "../types"; import { CatalogCell } from "./catalog-cell"; export function AssetCatalogPage(props: AssetCatalogPageProps) { - const { scope, csrfToken } = props; + const { scope } = props; const { tab, setTab, @@ -177,7 +177,6 @@ export function AssetCatalogPage(props: AssetCatalogPageProps) { setViewId(undefined)} /> {editor && ( setEditor(undefined)} diff --git a/modules/assets/src/web/catalog/shared/api/catalog-api.ts b/modules/assets/src/web/catalog/shared/api/catalog-api.ts index 8703c7e..50b9eb8 100644 --- a/modules/assets/src/web/catalog/shared/api/catalog-api.ts +++ b/modules/assets/src/web/catalog/shared/api/catalog-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { AssetDefinitionSpec } from "../../../../common"; @@ -11,14 +12,11 @@ export class CatalogApiError extends Error { } } -async function request(path: string, body?: unknown, csrfToken?: string) { - const response = await fetch(`/api/asset-catalog/${path}`, { +async function request(path: string, body?: unknown) { + const response = await csrfFetch(`/api/asset-catalog/${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": csrfToken ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const json: unknown = await response.json(); @@ -36,14 +34,12 @@ export const catalogApi = { async get(scope: string) { return CatalogResponseSchema.parse(await request(scope)); }, - async save(scope: string, values: AssetDefinitionSpec, csrfToken: string, id?: string) { + async save(scope: string, values: AssetDefinitionSpec, id?: string) { return SaveDefinitionResponseSchema.parse( - await request(`${scope}${id ? `/${id}/update` : ""}`, { values }, csrfToken) + await request(`${scope}${id ? `/${id}/update` : ""}`, { values }) ); }, - async status(scope: string, id: string, active: boolean | null, csrfToken: string) { - return CatalogResponseSchema.parse( - await request(`${scope}/${id}/status`, { active }, csrfToken) - ); + async status(scope: string, id: string, active: boolean | null) { + return CatalogResponseSchema.parse(await request(`${scope}/${id}/status`, { active })); }, }; diff --git a/modules/assets/src/web/catalog/shared/catalog.test.tsx b/modules/assets/src/web/catalog/shared/catalog.test.tsx index 3c5bf39..46215b8 100644 --- a/modules/assets/src/web/catalog/shared/catalog.test.tsx +++ b/modules/assets/src/web/catalog/shared/catalog.test.tsx @@ -48,7 +48,7 @@ test("el catálogo muestra plantillas de serie y acción de alta sin formularios ["en", "New template", "Actions"], ]) { assert.ok(language && create && actions); - const html = await render(language, ); + const html = await render(language, ); assert.ok(html.includes("Portátil")); assert.ok(html.includes(create)); @@ -58,17 +58,14 @@ test("el catálogo muestra plantillas de serie y acción de alta sin formularios } }); test("los formularios tienen ayuda inferior y selector de campo con alta integrada", async () => { - const field = await render( - "es", - undefined} scope="global" /> - ); + const field = await render("es", undefined} scope="global" />); assert.ok(field.indexOf('name="description"') < field.indexOf(" undefined} scope="global" /> + undefined} scope="global" /> ); assert.ok(template.includes('aria-label="Nuevo campo"')); diff --git a/modules/assets/src/web/catalog/shared/controllers/use-catalog-save.ts b/modules/assets/src/web/catalog/shared/controllers/use-catalog-save.ts index eec76b0..0fd789f 100644 --- a/modules/assets/src/web/catalog/shared/controllers/use-catalog-save.ts +++ b/modules/assets/src/web/catalog/shared/controllers/use-catalog-save.ts @@ -6,12 +6,7 @@ import { catalogApi, catalogKeys } from "../api"; export type CatalogSaved = (id: string, data: SaveDefinitionResponseDTO) => void; -export function useCatalogSave( - scope: string, - csrfToken: string, - onSaved: CatalogSaved, - id?: string -) { +export function useCatalogSave(scope: string, onSaved: CatalogSaved, id?: string) { const client = useQueryClient(); const mounted = useRef(true); @@ -24,7 +19,7 @@ export function useCatalogSave( }, []); return useMutation({ - mutationFn: (values: AssetDefinitionSpec) => catalogApi.save(scope, values, csrfToken, id), + mutationFn: (values: AssetDefinitionSpec) => catalogApi.save(scope, values, id), onSuccess: async (response) => { await client.invalidateQueries({ queryKey: ["asset-catalog"] }); client.setQueryData(catalogKeys.scope(scope), { items: response.items }); diff --git a/modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx b/modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx index 003c55e..0605606 100644 --- a/modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx +++ b/modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx @@ -27,13 +27,11 @@ export function CatalogEditorDialog({ editor, items, scope, - csrfToken, onClose, }: { editor: CatalogEditor; items: readonly CatalogItemDTO[]; scope: string; - csrfToken: string; onClose: () => void; }) { const { t } = useTranslation("assets"); @@ -52,23 +50,12 @@ export function CatalogEditorDialog({ wide={editor.kind === "template"} > {editor.kind === "field" ? ( - onClose()} - scope={scope} - /> + onClose()} scope={scope} /> ) : editor.kind === "relation" ? ( - onClose()} - scope={scope} - /> + onClose()} scope={scope} /> ) : ( onClose()} diff --git a/modules/assets/src/web/shared/api/delete-record.api.ts b/modules/assets/src/web/shared/api/delete-record.api.ts index fa88214..5be1e42 100644 --- a/modules/assets/src/web/shared/api/delete-record.api.ts +++ b/modules/assets/src/web/shared/api/delete-record.api.ts @@ -1,10 +1,11 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; -export async function deleteRecord(path: string, token: string) { - const response = await fetch(path, { +export async function deleteRecord(path: string) { + const response = await csrfFetch(path, { method: "POST", credentials: "same-origin", - headers: { "content-type": "application/json", "x-csrf-token": token }, + headers: { "content-type": "application/json" }, body: "{}", }); diff --git a/modules/assets/src/web/shared/api/inventory-request.ts b/modules/assets/src/web/shared/api/inventory-request.ts index be0b5d9..3a339db 100644 --- a/modules/assets/src/web/shared/api/inventory-request.ts +++ b/modules/assets/src/web/shared/api/inventory-request.ts @@ -1,15 +1,13 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import { InventoryApiError } from "./inventory-api-error"; -export async function inventoryRequest(path: string, body?: unknown, csrfToken?: string) { - const response = await fetch(`/api/assets/${path}`, { +export async function inventoryRequest(path: string, body?: unknown) { + const response = await csrfFetch(`/api/assets/${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": csrfToken ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const data: unknown = await response.json(); diff --git a/modules/assets/src/web/shared/api/support-request.ts b/modules/assets/src/web/shared/api/support-request.ts index 9f200c2..3bd538b 100644 --- a/modules/assets/src/web/shared/api/support-request.ts +++ b/modules/assets/src/web/shared/api/support-request.ts @@ -1,15 +1,13 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import { SupportApiError } from "./support-api-error"; -export async function supportRequest(path: string, values?: unknown, token?: string) { - const response = await fetch(`/api/asset-support/${path}`, { +export async function supportRequest(path: string, values?: unknown) { + const response = await csrfFetch(`/api/asset-support/${path}`, { method: values === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - values === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": token ?? "" }, + headers: values === undefined ? {} : { "content-type": "application/json" }, ...(values === undefined ? {} : { body: JSON.stringify({ values }) }), }); const data: unknown = await response.json(); diff --git a/modules/assets/src/web/shared/ui/delete-record-dialog.tsx b/modules/assets/src/web/shared/ui/delete-record-dialog.tsx index 8fe96ea..f0c3521 100644 --- a/modules/assets/src/web/shared/ui/delete-record-dialog.tsx +++ b/modules/assets/src/web/shared/ui/delete-record-dialog.tsx @@ -6,13 +6,11 @@ import { deleteRecord } from "../api/delete-record.api"; export function DeleteRecordDialog({ path, - csrfToken, queryKey, onClose, companyId, }: { path: string | undefined; - csrfToken: string; queryKey: readonly string[]; onClose: () => void; companyId: string; @@ -30,7 +28,7 @@ export function DeleteRecordDialog({ if (!path) { return; } - await deleteRecord(path, csrfToken); + await deleteRecord(path); await Promise.all([ client.invalidateQueries({ queryKey }), client.invalidateQueries({ queryKey: ["person-processes", companyId] }), diff --git a/modules/assets/src/web/support-connections/list/types/asset-support-props.ts b/modules/assets/src/web/support-connections/list/types/asset-support-props.ts index 7a26f29..1700dc8 100644 --- a/modules/assets/src/web/support-connections/list/types/asset-support-props.ts +++ b/modules/assets/src/web/support-connections/list/types/asset-support-props.ts @@ -2,6 +2,5 @@ export type AssetSupportProps = { compact?: boolean; companyId: string; assetId: string; - csrfToken: string; retired?: boolean; }; diff --git a/modules/assets/src/web/support-connections/list/ui/asset-support.tsx b/modules/assets/src/web/support-connections/list/ui/asset-support.tsx index 1570470..cfd8546 100644 --- a/modules/assets/src/web/support-connections/list/ui/asset-support.tsx +++ b/modules/assets/src/web/support-connections/list/ui/asset-support.tsx @@ -11,7 +11,7 @@ import { useAssetSupportController } from "../controllers"; import type { AssetSupportProps } from "../types"; export function AssetSupport(props: AssetSupportProps) { - const { companyId, assetId, csrfToken, retired = false, compact = false } = props; + const { companyId, assetId, retired = false, compact = false } = props; const { query, open, @@ -110,7 +110,6 @@ export function AssetSupport(props: AssetSupportProps) { )} setDeleteId(undefined)} path={ deleteId ? `/api/asset-support/${companyId}/${assetId}/${deleteId}/delete` : undefined @@ -120,14 +119,13 @@ export function AssetSupport(props: AssetSupportProps) { setOpen(false)} open={open} tools={tools} /> - + { diff --git a/modules/assets/src/web/support-connections/shared/api/save-support-connection.api.ts b/modules/assets/src/web/support-connections/shared/api/save-support-connection.api.ts index 25dc317..a0a0003 100644 --- a/modules/assets/src/web/support-connections/shared/api/save-support-connection.api.ts +++ b/modules/assets/src/web/support-connections/shared/api/save-support-connection.api.ts @@ -6,10 +6,9 @@ export async function saveSupportConnection( companyId: string, assetId: string, values: SupportInput, - token: string, id?: string ) { return SupportResponseSchema.parse( - await supportRequest(`${companyId}/${assetId}${id ? `/${id}/update` : ""}`, values, token) + await supportRequest(`${companyId}/${assetId}${id ? `/${id}/update` : ""}`, values) ); } diff --git a/modules/assets/src/web/support-connections/shared/controllers/use-support-form-controller.ts b/modules/assets/src/web/support-connections/shared/controllers/use-support-form-controller.ts index 12a03d9..b14cf96 100644 --- a/modules/assets/src/web/support-connections/shared/controllers/use-support-form-controller.ts +++ b/modules/assets/src/web/support-connections/shared/controllers/use-support-form-controller.ts @@ -12,7 +12,6 @@ import type { SupportFormProps } from "../types"; export function useSupportFormController({ companyId, assetId, - csrfToken, connection, onSaved, }: SupportFormProps) { @@ -32,7 +31,7 @@ export function useSupportFormController({ const values = form.watch(); const mutation = useMutation({ mutationFn: (input: SupportInput) => - saveSupportConnection(companyId, assetId, input, csrfToken, connection?.id), + saveSupportConnection(companyId, assetId, input, connection?.id), onSuccess: async () => { await client.invalidateQueries({ queryKey: supportKeys.asset(companyId, assetId) }); onSaved(); diff --git a/modules/assets/src/web/support-connections/shared/support.test.tsx b/modules/assets/src/web/support-connections/shared/support.test.tsx index 73f8c84..d2ffefb 100644 --- a/modules/assets/src/web/support-connections/shared/support.test.tsx +++ b/modules/assets/src/web/support-connections/shared/support.test.tsx @@ -46,7 +46,6 @@ test("asistencia: formulario con alta rápida y ayuda antes de guardar", async ( undefined} tools={[tool]} /> @@ -81,9 +80,7 @@ test("asistencia: traducciones y resumen de método", async () => { } }); test("herramienta: nombre de serie protegido y ayuda inferior", async () => { - const html = await render( - undefined} tool={tool} /> - ); + const html = await render( undefined} tool={tool} />); assert.ok(html.includes("readOnly")); assert.ok(html.indexOf("¿Para qué sirve una herramienta?") < html.indexOf("Guardar")); diff --git a/modules/assets/src/web/support-connections/shared/types/support-form-props.ts b/modules/assets/src/web/support-connections/shared/types/support-form-props.ts index 6c12297..1d28cbf 100644 --- a/modules/assets/src/web/support-connections/shared/types/support-form-props.ts +++ b/modules/assets/src/web/support-connections/shared/types/support-form-props.ts @@ -3,7 +3,6 @@ import type { SupportConnectionDTO, SupportToolDTO } from "../../../../common/su export type SupportFormProps = { companyId: string; assetId: string; - csrfToken: string; tools: readonly SupportToolDTO[]; connection?: SupportConnectionDTO; onSaved: () => void; diff --git a/modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx b/modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx index dbb439a..edc17d5 100644 --- a/modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx +++ b/modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx @@ -9,7 +9,6 @@ export function SupportConnectionEditorDialog({ edit, companyId, assetId, - csrfToken, tools, onSaved, onOpenChange, @@ -18,7 +17,6 @@ export function SupportConnectionEditorDialog({ edit: ComponentProps["connection"]; companyId: string; assetId: string; - csrfToken: string; tools: ComponentProps["tools"]; onSaved: () => void; onOpenChange: (open: boolean) => void; @@ -36,7 +34,6 @@ export function SupportConnectionEditorDialog({ { diff --git a/modules/assets/src/web/support-tools/list/types/index.ts b/modules/assets/src/web/support-tools/list/types/index.ts deleted file mode 100644 index 0d65640..0000000 --- a/modules/assets/src/web/support-tools/list/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { SupportToolsProps } from "./support-tools-props"; diff --git a/modules/assets/src/web/support-tools/list/types/support-tools-props.ts b/modules/assets/src/web/support-tools/list/types/support-tools-props.ts deleted file mode 100644 index 2fce8e5..0000000 --- a/modules/assets/src/web/support-tools/list/types/support-tools-props.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type SupportToolsProps = { - csrfToken: string; -}; diff --git a/modules/assets/src/web/support-tools/list/ui/support-tools-dialog.tsx b/modules/assets/src/web/support-tools/list/ui/support-tools-dialog.tsx index c1ea4c3..b950229 100644 --- a/modules/assets/src/web/support-tools/list/ui/support-tools-dialog.tsx +++ b/modules/assets/src/web/support-tools/list/ui/support-tools-dialog.tsx @@ -5,11 +5,9 @@ import { SupportTools } from "./support-tools"; export function SupportToolsDialog({ open, - csrfToken, onOpenChange, }: { open: boolean; - csrfToken: string; onOpenChange: (open: boolean) => void; }) { const { t } = useTranslation("assets"); @@ -21,7 +19,7 @@ export function SupportToolsDialog({ open={open} title={t("support.tools")} > - + ); } diff --git a/modules/assets/src/web/support-tools/list/ui/support-tools.tsx b/modules/assets/src/web/support-tools/list/ui/support-tools.tsx index 87f62c7..c79c7fe 100644 --- a/modules/assets/src/web/support-tools/list/ui/support-tools.tsx +++ b/modules/assets/src/web/support-tools/list/ui/support-tools.tsx @@ -10,10 +10,8 @@ import { useTranslation } from "react-i18next"; import { SupportToolEditorDialog } from "../../shared"; import { useSupportToolsController } from "../controllers"; -import type { SupportToolsProps } from "../types"; -export function SupportTools(props: SupportToolsProps) { - const { csrfToken } = props; +export function SupportTools() { const { query, edit, setEdit, open, setOpen } = useSupportToolsController(); const { t, i18n } = useTranslation("assets"); @@ -100,7 +98,6 @@ export function SupportTools(props: SupportToolsProps) { unavailable={query.isError} /> setOpen(false)} diff --git a/modules/assets/src/web/support-tools/shared/api/save-support-tool.api.ts b/modules/assets/src/web/support-tools/shared/api/save-support-tool.api.ts index cc9b5d1..59005ec 100644 --- a/modules/assets/src/web/support-tools/shared/api/save-support-tool.api.ts +++ b/modules/assets/src/web/support-tools/shared/api/save-support-tool.api.ts @@ -2,8 +2,8 @@ import type { SupportToolInput } from "../../../../common/support"; import { SupportToolResponseSchema } from "../../../../common/support"; import { supportRequest } from "../../../shared/api/support-request"; -export async function saveSupportTool(values: SupportToolInput, token: string, id?: string) { +export async function saveSupportTool(values: SupportToolInput, id?: string) { return SupportToolResponseSchema.parse( - await supportRequest(`tools${id ? `/${id}/update` : ""}`, values, token) + await supportRequest(`tools${id ? `/${id}/update` : ""}`, values) ); } diff --git a/modules/assets/src/web/support-tools/shared/controllers/use-support-tool-form-controller.ts b/modules/assets/src/web/support-tools/shared/controllers/use-support-tool-form-controller.ts index d1c6371..c4c372a 100644 --- a/modules/assets/src/web/support-tools/shared/controllers/use-support-tool-form-controller.ts +++ b/modules/assets/src/web/support-tools/shared/controllers/use-support-tool-form-controller.ts @@ -8,14 +8,14 @@ import { supportKeys } from "../../../shared/queries"; import { saveSupportTool } from "../api"; import type { SupportToolFormProps } from "../types"; -export function useSupportToolFormController({ csrfToken, tool, onSaved }: SupportToolFormProps) { +export function useSupportToolFormController({ tool, onSaved }: SupportToolFormProps) { const client = useQueryClient(); const form = useForm({ resolver: zodResolver(SupportToolInputSchema), defaultValues: tool ? { name: tool.name, active: tool.active } : { name: "", active: true }, }); const mutation = useMutation({ - mutationFn: (values: SupportToolInput) => saveSupportTool(values, csrfToken, tool?.id), + mutationFn: (values: SupportToolInput) => saveSupportTool(values, tool?.id), onSuccess: async (response) => { await Promise.all([ client.invalidateQueries({ queryKey: supportKeys.tools }), diff --git a/modules/assets/src/web/support-tools/shared/types/support-tool-form-props.ts b/modules/assets/src/web/support-tools/shared/types/support-tool-form-props.ts index 64b2378..ceb65a4 100644 --- a/modules/assets/src/web/support-tools/shared/types/support-tool-form-props.ts +++ b/modules/assets/src/web/support-tools/shared/types/support-tool-form-props.ts @@ -1,7 +1,6 @@ import type { SupportToolDTO } from "../../../../common/support"; export type SupportToolFormProps = { - csrfToken: string; tool?: SupportToolDTO; onSaved: (id: string) => void; }; diff --git a/modules/assets/src/web/support-tools/shared/ui/support-tool-editor-dialog.tsx b/modules/assets/src/web/support-tools/shared/ui/support-tool-editor-dialog.tsx index a585a35..5e13e87 100644 --- a/modules/assets/src/web/support-tools/shared/ui/support-tool-editor-dialog.tsx +++ b/modules/assets/src/web/support-tools/shared/ui/support-tool-editor-dialog.tsx @@ -7,13 +7,11 @@ import { SupportToolForm } from "./support-tool-form"; export function SupportToolEditorDialog({ open, edit, - csrfToken, onSaved, onOpenChange, }: { open: boolean; edit: ComponentProps["tool"]; - csrfToken: string; onSaved: ComponentProps["onSaved"]; onOpenChange: (open: boolean) => void; }) { @@ -28,7 +26,6 @@ export function SupportToolEditorDialog({ title={t(edit ? "support.editTool" : "support.newTool")} > setEditor(undefined)} workstation={edited} diff --git a/modules/assets/src/web/workstations/shared/api/save-workstation.api.ts b/modules/assets/src/web/workstations/shared/api/save-workstation.api.ts index e5973c6..a8b40a4 100644 --- a/modules/assets/src/web/workstations/shared/api/save-workstation.api.ts +++ b/modules/assets/src/web/workstations/shared/api/save-workstation.api.ts @@ -2,17 +2,8 @@ import type { WorkstationInput } from "../../../../common/workstations"; import { WorkstationResponseSchema } from "../../../../common/workstations"; import { inventoryRequest } from "../../../shared/api/inventory-request"; -export async function saveWorkstation( - companyId: string, - values: WorkstationInput, - token: string, - id?: string -) { +export async function saveWorkstation(companyId: string, values: WorkstationInput, id?: string) { return WorkstationResponseSchema.parse( - await inventoryRequest( - `${companyId}/workstations${id ? `/${id}/update` : ""}`, - { values }, - token - ) + await inventoryRequest(`${companyId}/workstations${id ? `/${id}/update` : ""}`, { values }) ); } diff --git a/modules/assets/src/web/workstations/shared/controllers/use-workstation-form-controller.ts b/modules/assets/src/web/workstations/shared/controllers/use-workstation-form-controller.ts index 0659461..ffa3b59 100644 --- a/modules/assets/src/web/workstations/shared/controllers/use-workstation-form-controller.ts +++ b/modules/assets/src/web/workstations/shared/controllers/use-workstation-form-controller.ts @@ -13,7 +13,6 @@ import type { WorkstationFormProps } from "../types"; export function useWorkstationFormController({ companyId, - csrfToken, workstation, onSaved, }: WorkstationFormProps) { @@ -76,8 +75,7 @@ export function useWorkstationFormController({ }; const mutation = useMutation({ - mutationFn: (input: WorkstationInput) => - saveWorkstation(companyId, input, csrfToken, workstation?.id), + mutationFn: (input: WorkstationInput) => saveWorkstation(companyId, input, workstation?.id), onSuccess: async (response) => { await client.invalidateQueries({ queryKey: inventoryKeys.company(companyId) }); if (mounted.current) { diff --git a/modules/assets/src/web/workstations/shared/types/workstation-form-props.ts b/modules/assets/src/web/workstations/shared/types/workstation-form-props.ts index e8569fc..843dd67 100644 --- a/modules/assets/src/web/workstations/shared/types/workstation-form-props.ts +++ b/modules/assets/src/web/workstations/shared/types/workstation-form-props.ts @@ -3,7 +3,6 @@ import type { InventoryLookups } from "../../../assets/shared/types"; export type WorkstationFormProps = { companyId: string; - csrfToken: string; lookups: InventoryLookups; workstation?: WorkstationDTO | undefined; onSaved: (id: string) => void; diff --git a/modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx b/modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx index 7a61c6c..602edc9 100644 --- a/modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx +++ b/modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx @@ -7,13 +7,11 @@ import { WorkstationForm } from "./workstation-form"; export function WorkstationEditorDialog({ workstation, companyId, - csrfToken, lookups, onClose, }: { workstation: ComponentProps["workstation"]; companyId: string; - csrfToken: string; lookups: ComponentProps["lookups"]; onClose: () => void; }) { @@ -34,7 +32,6 @@ export function WorkstationEditorDialog({ > onClose()} workstation={workstation} diff --git a/modules/assets/src/web/workstations/shared/ui/workstation-form.tsx b/modules/assets/src/web/workstations/shared/ui/workstation-form.tsx index f42cc8c..05bd70c 100644 --- a/modules/assets/src/web/workstations/shared/ui/workstation-form.tsx +++ b/modules/assets/src/web/workstations/shared/ui/workstation-form.tsx @@ -14,7 +14,7 @@ import { useWorkstationFormController } from "../controllers"; import type { WorkstationFormProps } from "../types"; export function WorkstationForm(props: WorkstationFormProps) { - const { companyId, csrfToken, lookups, workstation } = props; + const { companyId, lookups, workstation } = props; const { form, values, @@ -193,7 +193,6 @@ export function WorkstationForm(props: WorkstationFormProps) { {quick && catalog.data && ( item.companyId === (scope === "global" ? null : scope) && matches(item.values, values) diff --git a/modules/companies/src/web/access/create/types/access-quick-editor-props.ts b/modules/companies/src/web/access/create/types/access-quick-editor-props.ts index a24a9cf..a1dca0b 100644 --- a/modules/companies/src/web/access/create/types/access-quick-editor-props.ts +++ b/modules/companies/src/web/access/create/types/access-quick-editor-props.ts @@ -9,7 +9,6 @@ export type AccessQuickEditorProps = { scope: string; data: AccessResponseDTO; people: readonly PersonDTO[]; - csrfToken: string; onCreated: (id?: string, values?: AccessFieldsDTO) => void; onClose: () => void; nestedAccessForm: ComponentType; diff --git a/modules/companies/src/web/access/create/types/quick-create-dialog-props.ts b/modules/companies/src/web/access/create/types/quick-create-dialog-props.ts index 73f56d8..cf03493 100644 --- a/modules/companies/src/web/access/create/types/quick-create-dialog-props.ts +++ b/modules/companies/src/web/access/create/types/quick-create-dialog-props.ts @@ -13,7 +13,6 @@ export type QuickCreateTarget = export type QuickCreateDialogProps = { target: QuickCreateTarget; scope: string; - csrfToken: string; onClose: () => void; onCreated: (id: string) => void; items: readonly AccessItemDTO[]; diff --git a/modules/companies/src/web/access/create/ui/access-quick-editor.tsx b/modules/companies/src/web/access/create/ui/access-quick-editor.tsx index ef93dd0..87af3ee 100644 --- a/modules/companies/src/web/access/create/ui/access-quick-editor.tsx +++ b/modules/companies/src/web/access/create/ui/access-quick-editor.tsx @@ -9,14 +9,13 @@ import type { AccessQuickEditorProps } from "../types"; import { QuickCreateDialog } from "./quick-create-dialog"; export function AccessQuickEditor(props: AccessQuickEditorProps) { - const { plan, scope, data, people, csrfToken, nestedAccessForm: NestedAccessForm } = props; + const { plan, scope, data, people, nestedAccessForm: NestedAccessForm } = props; const { close, created, globalQuery } = useAccessQuickEditorController(props); const { t } = useTranslation("access"); if (plan.type === "simple") { return (

{t("quickCreateNotice")}

- + ) : ( <> @@ -54,7 +53,6 @@ export function AccessQuickEditor(props: AccessQuickEditorProps) { {childData ? ( - + ); @@ -51,7 +51,6 @@ test("cuentas ofrecen caducidad opcional y campos sin contraseña", async () => undefined} @@ -141,7 +140,6 @@ test("los ejemplos del sistema orientan sin rellenar datos del alta", async () = undefined} @@ -273,7 +271,6 @@ test("los formularios ofrecen altas relacionadas y el segundo modal evita altas undefined} diff --git a/modules/companies/src/web/access/shared/api/access-request.ts b/modules/companies/src/web/access/shared/api/access-request.ts index 7a0d42b..8a7f1a5 100644 --- a/modules/companies/src/web/access/shared/api/access-request.ts +++ b/modules/companies/src/web/access/shared/api/access-request.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { AccessFieldsDTO } from "../../../../common"; @@ -5,16 +6,11 @@ import { AccessResponseSchema } from "../../../../common"; import { AccessApiError } from "./access-api-error"; -export async function accessRequest( - scope: string, - values?: AccessFieldsDTO, - id?: string, - token?: string -) { - const response = await fetch(`/api/access/${scope}${id ? `/${id}/update` : ""}`, { +export async function accessRequest(scope: string, values?: AccessFieldsDTO, id?: string) { + const response = await csrfFetch(`/api/access/${scope}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), }); const json: unknown = await response.json(); diff --git a/modules/companies/src/web/access/shared/api/save-access.api.ts b/modules/companies/src/web/access/shared/api/save-access.api.ts index 7d181dd..1093b25 100644 --- a/modules/companies/src/web/access/shared/api/save-access.api.ts +++ b/modules/companies/src/web/access/shared/api/save-access.api.ts @@ -2,11 +2,6 @@ import type { AccessFieldsDTO } from "../../../../common"; import { accessRequest } from "./access-request"; -export function saveAccess( - scope: string, - values: AccessFieldsDTO, - id: string | undefined, - token: string -) { - return accessRequest(scope, values, id, token); +export function saveAccess(scope: string, values: AccessFieldsDTO, id: string | undefined) { + return accessRequest(scope, values, id); } diff --git a/modules/companies/src/web/access/shared/controllers/use-access-form-fields-controller.ts b/modules/companies/src/web/access/shared/controllers/use-access-form-fields-controller.ts index cd637de..b4a47e1 100644 --- a/modules/companies/src/web/access/shared/controllers/use-access-form-fields-controller.ts +++ b/modules/companies/src/web/access/shared/controllers/use-access-form-fields-controller.ts @@ -20,7 +20,6 @@ export function useAccessFormFieldsController({ item, data, people, - csrfToken, onSaved, allowQuickCreate = true, initialValues = {}, @@ -56,7 +55,7 @@ export function useAccessFormFieldsController({ throw new AccessApiError("INVALID_REQUEST"); } - return saveAccess(scope, parsed.data, item?.id, csrfToken); + return saveAccess(scope, parsed.data, item?.id); }, onSuccess: async (result, submitted) => { const savedId = diff --git a/modules/companies/src/web/access/shared/types/access-form-props.ts b/modules/companies/src/web/access/shared/types/access-form-props.ts index a41b421..c5a290b 100644 --- a/modules/companies/src/web/access/shared/types/access-form-props.ts +++ b/modules/companies/src/web/access/shared/types/access-form-props.ts @@ -13,7 +13,6 @@ export type AccessFormProps = { item?: AccessItemDTO | undefined; data: AccessResponseDTO; people: readonly PersonDTO[]; - csrfToken: string; onSaved: (id?: string, values?: AccessFieldsDTO) => void; allowQuickCreate?: boolean; initialValues?: AccessFormValues; diff --git a/modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx b/modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx index 4d2ac91..5147612 100644 --- a/modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx +++ b/modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx @@ -12,7 +12,6 @@ export function AccessEditorDialog({ selected, kind, scope, - csrfToken, data, people, complex, @@ -23,7 +22,6 @@ export function AccessEditorDialog({ selected: ComponentProps["item"]; kind: AccessKind; scope: string; - csrfToken: string; data: ComponentProps["data"]; people: ComponentProps["people"]; complex: boolean; @@ -42,7 +40,6 @@ export function AccessEditorDialog({ wide={complex} > ; } ) { - const { scope, kind, item, data, people, csrfToken, lockedFields = [], nestedAccessForm } = props; + const { scope, kind, item, data, people, lockedFields = [], nestedAccessForm } = props; const { validation, quickCreate, @@ -94,7 +94,6 @@ export function AccessFormFields( {quickCreate && ( setQuickCreate(undefined)} diff --git a/modules/companies/src/web/access/shared/ui/access-form.tsx b/modules/companies/src/web/access/shared/ui/access-form.tsx index febbed9..11889e6 100644 --- a/modules/companies/src/web/access/shared/ui/access-form.tsx +++ b/modules/companies/src/web/access/shared/ui/access-form.tsx @@ -12,7 +12,6 @@ export function AccessForm(props: AccessFormProps) { void; onSaved: (field: "profileId" | "resourceId", id: string) => void; }) { @@ -37,14 +35,12 @@ export function ApplicationQuickEditor({ onSaved("profileId", id)} /> ) : ( onSaved("resourceId", id)} /> diff --git a/modules/companies/src/web/application-configurations/list/types/application-configurations-page-props.ts b/modules/companies/src/web/application-configurations/list/types/application-configurations-page-props.ts index fd3896b..442c8a1 100644 --- a/modules/companies/src/web/application-configurations/list/types/application-configurations-page-props.ts +++ b/modules/companies/src/web/application-configurations/list/types/application-configurations-page-props.ts @@ -2,6 +2,5 @@ import type { CompanyDTO } from "../../../../common"; export type ApplicationConfigurationsPageProps = { company: CompanyDTO; - csrfToken: string; initialProfileId?: string; }; diff --git a/modules/companies/src/web/application-configurations/list/ui/application-configurations-page.tsx b/modules/companies/src/web/application-configurations/list/ui/application-configurations-page.tsx index 3d73c5d..56e8adb 100644 --- a/modules/companies/src/web/application-configurations/list/ui/application-configurations-page.tsx +++ b/modules/companies/src/web/application-configurations/list/ui/application-configurations-page.tsx @@ -16,7 +16,7 @@ import { useApplicationConfigurationsPageController } from "../controllers"; import type { ApplicationConfigurationsPageProps } from "../types"; export function ApplicationConfigurationsPage(props: ApplicationConfigurationsPageProps) { - const { company, csrfToken, initialProfileId } = props; + const { company, initialProfileId } = props; const { editor, setEditor, @@ -42,7 +42,6 @@ export function ApplicationConfigurationsPage(props: ApplicationConfigurationsPa
setDeleteId(undefined)} path={ deleteId ? `/api/application-configurations/${company.id}/${deleteId}/delete` : undefined @@ -170,7 +169,6 @@ export function ApplicationConfigurationsPage(props: ApplicationConfigurationsPa setEditor(undefined)} /> diff --git a/modules/companies/src/web/application-configurations/shared/api/application-api.ts b/modules/companies/src/web/application-configurations/shared/api/application-api.ts index bd9f35d..42440aa 100644 --- a/modules/companies/src/web/application-configurations/shared/api/application-api.ts +++ b/modules/companies/src/web/application-configurations/shared/api/application-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { ApplicationConfigurationInput } from "../../../../common/application-configurations"; @@ -14,18 +15,13 @@ export class ApplicationApiError extends Error { } } -async function request( - companyId: string, - values?: ApplicationConfigurationInput, - token?: string, - id?: string -) { - const response = await fetch( +async function request(companyId: string, values?: ApplicationConfigurationInput, id?: string) { + const response = await csrfFetch( `/api/application-configurations/${companyId}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), } ); @@ -44,9 +40,7 @@ export const applicationApi = { async get(companyId: string) { return ApplicationConfigurationsResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: ApplicationConfigurationInput, token: string, id?: string) { - return ApplicationConfigurationResponseSchema.parse( - await request(companyId, values, token, id) - ); + async save(companyId: string, values: ApplicationConfigurationInput, id?: string) { + return ApplicationConfigurationResponseSchema.parse(await request(companyId, values, id)); }, }; diff --git a/modules/companies/src/web/application-configurations/shared/application.test.tsx b/modules/companies/src/web/application-configurations/shared/application.test.tsx index 3c1b107..2d93a41 100644 --- a/modules/companies/src/web/application-configurations/shared/application.test.tsx +++ b/modules/companies/src/web/application-configurations/shared/application.test.tsx @@ -65,7 +65,7 @@ test("aplicaciones: listado sin formularios, alta en cabecera y traducciones", a ]) { const html = await render( language ?? "es", - + ); assert.ok(html.includes(title ?? "")); @@ -78,7 +78,7 @@ test("aplicaciones: selectores con alta rápida y ayuda inferior antes de guarda for (const language of ["es", "en"]) { const html = await render( language, - undefined} /> + undefined} /> ); const text = language === "es" ? companiesSpanish : companiesEnglish; diff --git a/modules/companies/src/web/application-configurations/shared/controllers/use-application-configuration-form-controller.ts b/modules/companies/src/web/application-configurations/shared/controllers/use-application-configuration-form-controller.ts index 02861d4..979f9cf 100644 --- a/modules/companies/src/web/application-configurations/shared/controllers/use-application-configuration-form-controller.ts +++ b/modules/companies/src/web/application-configurations/shared/controllers/use-application-configuration-form-controller.ts @@ -27,7 +27,6 @@ function applicationStates( export function useApplicationConfigurationFormController({ company, - csrfToken, configuration, initialProfileId = "", onSaved, @@ -65,7 +64,7 @@ export function useApplicationConfigurationFormController({ const states = applicationStates(configuration); const mutation = useMutation({ mutationFn: (input: ApplicationConfigurationInput) => - applicationApi.save(company.id, input, csrfToken, configuration?.id), + applicationApi.save(company.id, input, configuration?.id), onSuccess: async () => { await client.invalidateQueries({ queryKey: applicationKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/application-configurations/shared/types/application-configuration-form-props.ts b/modules/companies/src/web/application-configurations/shared/types/application-configuration-form-props.ts index 8362f4b..c4f55a2 100644 --- a/modules/companies/src/web/application-configurations/shared/types/application-configuration-form-props.ts +++ b/modules/companies/src/web/application-configurations/shared/types/application-configuration-form-props.ts @@ -3,7 +3,6 @@ import type { ApplicationConfigurationDTO } from "../../../../common/application export type ApplicationConfigurationFormProps = { company: CompanyDTO; - csrfToken: string; configuration?: ApplicationConfigurationDTO | undefined; initialProfileId?: string; onSaved: () => void; diff --git a/modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx b/modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx index ae4917d..d97f69a 100644 --- a/modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx +++ b/modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx @@ -9,13 +9,11 @@ import { ApplicationConfigurationForm } from "./application-configuration-form"; export function ApplicationConfigurationEditorDialog({ configuration, company, - csrfToken, initialProfileId, onClose, }: { configuration: ComponentProps["configuration"]; company: CompanyDTO; - csrfToken: string; initialProfileId: ComponentProps["initialProfileId"]; onClose: () => void; }) { @@ -37,7 +35,6 @@ export function ApplicationConfigurationEditorDialog({ onClose()} /> diff --git a/modules/companies/src/web/application-configurations/shared/ui/application-configuration-form.tsx b/modules/companies/src/web/application-configurations/shared/ui/application-configuration-form.tsx index 257fd62..d403e7d 100644 --- a/modules/companies/src/web/application-configurations/shared/ui/application-configuration-form.tsx +++ b/modules/companies/src/web/application-configurations/shared/ui/application-configuration-form.tsx @@ -23,7 +23,7 @@ function applicationProfiles(selected: string, profiles: readonly WorkProfileDTO } export function ApplicationConfigurationForm(props: ApplicationConfigurationFormProps) { - const { company, csrfToken, configuration } = props; + const { company, configuration } = props; const { quick, setQuick, @@ -172,7 +172,6 @@ export function ApplicationConfigurationForm(props: ApplicationConfigurationForm {quick && ( setQuick(undefined)} onSaved={quickCreated} diff --git a/modules/companies/src/web/assignments/shared/api/save-assignment.api.ts b/modules/companies/src/web/assignments/shared/api/save-assignment.api.ts index 47a0e54..d23a0aa 100644 --- a/modules/companies/src/web/assignments/shared/api/save-assignment.api.ts +++ b/modules/companies/src/web/assignments/shared/api/save-assignment.api.ts @@ -1,24 +1,21 @@ import { CompanyResponseSchema } from "../../../../common"; import { companyRequest } from "../../../companies/shared/api/company-request"; -export const deleteAssignment = (companyId: string, personId: string, id: string, token: string) => +export const deleteAssignment = (companyId: string, personId: string, id: string) => companyRequest( `/${companyId}/people/${personId}/assignments/${id}/delete`, CompanyResponseSchema, - {}, - token + {} ); export const saveAssignment = ( companyId: string, personId: string, id: string | undefined, - body: unknown, - token: string + body: unknown ) => companyRequest( `/${companyId}/people/${personId}/assignments${id ? `/${id}/update` : ""}`, CompanyResponseSchema, - body, - token + body ); diff --git a/modules/companies/src/web/assignments/shared/controllers/use-assignment-form-controller.ts b/modules/companies/src/web/assignments/shared/controllers/use-assignment-form-controller.ts index c3cd600..d3bde15 100644 --- a/modules/companies/src/web/assignments/shared/controllers/use-assignment-form-controller.ts +++ b/modules/companies/src/web/assignments/shared/controllers/use-assignment-form-controller.ts @@ -13,7 +13,6 @@ export function useAssignmentFormController({ company, personId, assignment, - csrfToken, onSaved, }: AssignmentFormProps) { const [creatingDepartment, setCreatingDepartment] = useState(false); @@ -30,19 +29,13 @@ export function useAssignmentFormController({ }); const mutation = useMutation({ mutationFn: (values: FormValues) => - saveAssignment( - company.id, - personId, - assignment?.id, - { - ...(assignment ? {} : { departmentId: values.departmentId }), - position: values.position.trim() || null, - startDate: values.startDate, - endDate: values.endDate || null, - isPrimary: values.endDate ? false : values.isPrimary, - }, - csrfToken - ), + saveAssignment(company.id, personId, assignment?.id, { + ...(assignment ? {} : { departmentId: values.departmentId }), + position: values.position.trim() || null, + startDate: values.startDate, + endDate: values.endDate || null, + isPrimary: values.endDate ? false : values.isPrimary, + }), onSuccess: (response) => { client.setQueryData(companyKeys.detail(company.id), response); onSaved(); diff --git a/modules/companies/src/web/assignments/shared/types/assignment-form-props.ts b/modules/companies/src/web/assignments/shared/types/assignment-form-props.ts index f372b32..2c407f4 100644 --- a/modules/companies/src/web/assignments/shared/types/assignment-form-props.ts +++ b/modules/companies/src/web/assignments/shared/types/assignment-form-props.ts @@ -9,6 +9,5 @@ export type AssignmentFormProps = { company: CompanyDTO; personId: string; assignment?: DepartmentAssignmentDTO | undefined; - csrfToken: string; onSaved: () => void; }; diff --git a/modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx b/modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx index 79365f4..67d2b46 100644 --- a/modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx +++ b/modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx @@ -10,7 +10,6 @@ export function AssignmentEditorDialog({ editor, selected, company, - csrfToken, personId, onSaved, onOpenChange, @@ -18,7 +17,6 @@ export function AssignmentEditorDialog({ editor: string | undefined; selected: ComponentProps["assignment"]; company: CompanyDTO; - csrfToken: string; personId: string; onSaved: () => void; onOpenChange: (open: boolean) => void; @@ -36,7 +34,6 @@ export function AssignmentEditorDialog({ { if (!open) { setCreatingDepartment(false); diff --git a/modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx b/modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx index fbd26eb..119895b 100644 --- a/modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx +++ b/modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx @@ -8,13 +8,11 @@ import { WorkProfileForm } from "../../../work-profiles/shared"; export function CertificateQuickEditor({ kind, company, - csrfToken, onClose, onSaved, }: { kind: "profile" | "certificate"; company: CompanyDTO; - csrfToken: string; onClose: () => void; onSaved: (field: "profileId" | "resourceId", id: string) => void; }) { @@ -37,14 +35,12 @@ export function CertificateQuickEditor({ onSaved("profileId", id)} /> ) : ( onSaved("resourceId", id)} /> diff --git a/modules/companies/src/web/certificate-configurations/list/types/certificate-configurations-page-props.ts b/modules/companies/src/web/certificate-configurations/list/types/certificate-configurations-page-props.ts index f987e19..fac4f73 100644 --- a/modules/companies/src/web/certificate-configurations/list/types/certificate-configurations-page-props.ts +++ b/modules/companies/src/web/certificate-configurations/list/types/certificate-configurations-page-props.ts @@ -2,6 +2,5 @@ import type { CompanyDTO } from "../../../../common"; export type CertificateConfigurationsPageProps = { company: CompanyDTO; - csrfToken: string; initialProfileId?: string; }; diff --git a/modules/companies/src/web/certificate-configurations/list/ui/certificate-configurations-page.tsx b/modules/companies/src/web/certificate-configurations/list/ui/certificate-configurations-page.tsx index c12ef3f..c6d8b20 100644 --- a/modules/companies/src/web/certificate-configurations/list/ui/certificate-configurations-page.tsx +++ b/modules/companies/src/web/certificate-configurations/list/ui/certificate-configurations-page.tsx @@ -16,7 +16,7 @@ import { useCertificateConfigurationsPageController } from "../controllers"; import type { CertificateConfigurationsPageProps } from "../types"; export function CertificateConfigurationsPage(props: CertificateConfigurationsPageProps) { - const { company, csrfToken, initialProfileId } = props; + const { company, initialProfileId } = props; const { editor, setEditor, @@ -42,7 +42,6 @@ export function CertificateConfigurationsPage(props: CertificateConfigurationsPa
setDeleteId(undefined)} path={ deleteId ? `/api/certificate-configurations/${company.id}/${deleteId}/delete` : undefined @@ -177,7 +176,6 @@ export function CertificateConfigurationsPage(props: CertificateConfigurationsPa setEditor(undefined)} /> diff --git a/modules/companies/src/web/certificate-configurations/shared/api/certificate-api.ts b/modules/companies/src/web/certificate-configurations/shared/api/certificate-api.ts index 54e09fb..f9a7a33 100644 --- a/modules/companies/src/web/certificate-configurations/shared/api/certificate-api.ts +++ b/modules/companies/src/web/certificate-configurations/shared/api/certificate-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { CertificateConfigurationInput } from "../../../../common/certificate-configurations"; @@ -14,18 +15,13 @@ export class CertificateApiError extends Error { } } -async function request( - companyId: string, - values?: CertificateConfigurationInput, - token?: string, - id?: string -) { - const response = await fetch( +async function request(companyId: string, values?: CertificateConfigurationInput, id?: string) { + const response = await csrfFetch( `/api/certificate-configurations/${companyId}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), } ); @@ -44,9 +40,7 @@ export const certificateApi = { async get(companyId: string) { return CertificateConfigurationsResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: CertificateConfigurationInput, token: string, id?: string) { - return CertificateConfigurationResponseSchema.parse( - await request(companyId, values, token, id) - ); + async save(companyId: string, values: CertificateConfigurationInput, id?: string) { + return CertificateConfigurationResponseSchema.parse(await request(companyId, values, id)); }, }; diff --git a/modules/companies/src/web/certificate-configurations/shared/certificate.test.tsx b/modules/companies/src/web/certificate-configurations/shared/certificate.test.tsx index c0c7814..f3c4e74 100644 --- a/modules/companies/src/web/certificate-configurations/shared/certificate.test.tsx +++ b/modules/companies/src/web/certificate-configurations/shared/certificate.test.tsx @@ -65,7 +65,7 @@ test("certificados: listado sin formularios, alta en cabecera y traducciones", a ]) { const html = await render( language ?? "es", - + ); assert.ok(html.includes(title ?? "")); @@ -78,7 +78,7 @@ test("certificados: selectores con alta rápida y ayuda inferior antes de guarda for (const language of ["es", "en"]) { const html = await render( language, - undefined} /> + undefined} /> ); const text = language === "es" ? companiesSpanish : companiesEnglish; diff --git a/modules/companies/src/web/certificate-configurations/shared/controllers/use-certificate-configuration-form-controller.ts b/modules/companies/src/web/certificate-configurations/shared/controllers/use-certificate-configuration-form-controller.ts index 6124322..c3e0613 100644 --- a/modules/companies/src/web/certificate-configurations/shared/controllers/use-certificate-configuration-form-controller.ts +++ b/modules/companies/src/web/certificate-configurations/shared/controllers/use-certificate-configuration-form-controller.ts @@ -27,7 +27,6 @@ function certificateStates( export function useCertificateConfigurationFormController({ company, - csrfToken, configuration, initialProfileId = "", onSaved, @@ -65,7 +64,7 @@ export function useCertificateConfigurationFormController({ const states = certificateStates(configuration); const mutation = useMutation({ mutationFn: (input: CertificateConfigurationInput) => - certificateApi.save(company.id, input, csrfToken, configuration?.id), + certificateApi.save(company.id, input, configuration?.id), onSuccess: async () => { await client.invalidateQueries({ queryKey: certificateKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/certificate-configurations/shared/types/certificate-configuration-form-props.ts b/modules/companies/src/web/certificate-configurations/shared/types/certificate-configuration-form-props.ts index a9b02b5..4707db2 100644 --- a/modules/companies/src/web/certificate-configurations/shared/types/certificate-configuration-form-props.ts +++ b/modules/companies/src/web/certificate-configurations/shared/types/certificate-configuration-form-props.ts @@ -3,7 +3,6 @@ import type { CertificateConfigurationDTO } from "../../../../common/certificate export type CertificateConfigurationFormProps = { company: CompanyDTO; - csrfToken: string; configuration?: CertificateConfigurationDTO | undefined; initialProfileId?: string; onSaved: () => void; diff --git a/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx b/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx index 07e75ed..464c8a2 100644 --- a/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx +++ b/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx @@ -9,13 +9,11 @@ import { CertificateConfigurationForm } from "./certificate-configuration-form"; export function CertificateConfigurationEditorDialog({ configuration, company, - csrfToken, initialProfileId, onClose, }: { configuration: ComponentProps["configuration"]; company: CompanyDTO; - csrfToken: string; initialProfileId: ComponentProps["initialProfileId"]; onClose: () => void; }) { @@ -37,7 +35,6 @@ export function CertificateConfigurationEditorDialog({ onClose()} /> diff --git a/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-form.tsx b/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-form.tsx index 7730396..99c44ca 100644 --- a/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-form.tsx +++ b/modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-form.tsx @@ -23,7 +23,7 @@ function certificateProfiles(selected: string, profiles: readonly WorkProfileDTO } export function CertificateConfigurationForm(props: CertificateConfigurationFormProps) { - const { company, csrfToken, configuration } = props; + const { company, configuration } = props; const { quick, setQuick, @@ -154,7 +154,6 @@ export function CertificateConfigurationForm(props: CertificateConfigurationForm {quick && ( setQuick(undefined)} onSaved={quickCreated} diff --git a/modules/companies/src/web/companies/change-logo/api/company-logo-api.ts b/modules/companies/src/web/companies/change-logo/api/company-logo-api.ts index d47aa0d..b10782d 100644 --- a/modules/companies/src/web/companies/change-logo/api/company-logo-api.ts +++ b/modules/companies/src/web/companies/change-logo/api/company-logo-api.ts @@ -1,12 +1,13 @@ +import { csrfFetch } from "@metis/core/client"; + import { CompanyErrorResponseSchema, CompanyLogoResponseSchema } from "../../../../common"; import { CompanyApiError } from "../../shared/api/company-api-error"; -export async function saveCompanyLogo(companyId: string, logo: File | null, csrfToken: string) { - const response = await fetch(`/api/companies/${companyId}/logo${logo ? "" : "/remove"}`, { +export async function saveCompanyLogo(companyId: string, logo: File | null) { + const response = await csrfFetch(`/api/companies/${companyId}/logo${logo ? "" : "/remove"}`, { method: "POST", credentials: "same-origin", headers: { - "x-csrf-token": csrfToken, "content-type": logo ? "application/octet-stream" : "application/json", }, body: logo ?? "{}", diff --git a/modules/companies/src/web/companies/detail/types/company-detail-props.ts b/modules/companies/src/web/companies/detail/types/company-detail-props.ts index 00bbe72..edb72df 100644 --- a/modules/companies/src/web/companies/detail/types/company-detail-props.ts +++ b/modules/companies/src/web/companies/detail/types/company-detail-props.ts @@ -2,7 +2,6 @@ import type { CompanyTab } from "../../shared/types"; export type CompanyDetailProps = { id: string; - csrfToken: string; onBack: () => void; embedded?: boolean; initialSection?: CompanyTab; diff --git a/modules/companies/src/web/companies/detail/ui/company-detail.tsx b/modules/companies/src/web/companies/detail/ui/company-detail.tsx index 4ddcfb2..5edfd33 100644 --- a/modules/companies/src/web/companies/detail/ui/company-detail.tsx +++ b/modules/companies/src/web/companies/detail/ui/company-detail.tsx @@ -28,7 +28,6 @@ import { CompanySection } from "./company-section"; export function CompanyDetail(props: CompanyDetailProps) { const { - csrfToken, onBack, embedded = false, initialSection, @@ -100,7 +99,6 @@ export function CompanyDetail(props: CompanyDetailProps) {
setEditOpen(false)} open={editOpen} @@ -188,7 +186,6 @@ export function CompanyDetail(props: CompanyDetailProps) { )} setQuick(undefined)} onCreated={quick.onCreated} diff --git a/modules/companies/src/web/companies/detail/ui/company-overview.tsx b/modules/companies/src/web/companies/detail/ui/company-overview.tsx index 02bad3c..90aad46 100644 --- a/modules/companies/src/web/companies/detail/ui/company-overview.tsx +++ b/modules/companies/src/web/companies/detail/ui/company-overview.tsx @@ -16,13 +16,7 @@ import { workspaceHref } from "../../../workspace/shared/helpers"; import { CompanyOverviewStatistics } from "./company-overview-statistics"; /** Composición propia de compañía; comparte bloques y consultas, no la pantalla Inicio. */ -export function CompanyOverview({ - company, - csrfToken, -}: { - company: CompanyDTO; - csrfToken: string; -}) { +export function CompanyOverview({ company }: { company: CompanyDTO }) { const { t } = useTranslation("companies"); const { summary, details, setDetails, quick, setQuick } = useDashboardPageController(company.id); @@ -93,7 +87,6 @@ export function CompanyOverview({ {quick && ( setQuick(undefined)} onNavigate={onNavigate} diff --git a/modules/companies/src/web/companies/detail/ui/company-section.tsx b/modules/companies/src/web/companies/detail/ui/company-section.tsx index 10208fd..9208bc8 100644 --- a/modules/companies/src/web/companies/detail/ui/company-section.tsx +++ b/modules/companies/src/web/companies/detail/ui/company-section.tsx @@ -28,14 +28,12 @@ import { CompanyOverview } from "./company-overview"; export function CompanySection({ company, - csrfToken, tab, initialEntityId, initialCreate, initialConfiguration, }: { company: CompanyDTO; - csrfToken: string; tab: CompanyTab; initialEntityId?: string | undefined; initialCreate?: boolean | undefined; @@ -54,52 +52,33 @@ export function CompanySection({ switch (tab) { case "overview": - return ; + return ; case "catalogs": return ; case "intervention-templates": - return ; + return ; case "interventions": - return ( - - ); + return ; case "tickets": - return ( - - ); + return ; case "processes": - return ; + return ; case "remote": - return ( - - ); + return ; case "telework": - return ( - - ); + return ; case "people": - return ; + return ; case "departments": - return ; + return ; case "sites": - return ; + return ; case "access": - return ( - - ); + return ; case "profiles": return ( @@ -108,7 +87,6 @@ export function CompanySection({ return ( ); @@ -116,7 +94,6 @@ export function CompanySection({ return ( ); @@ -124,7 +101,6 @@ export function CompanySection({ return ( ); @@ -132,7 +108,6 @@ export function CompanySection({ return ( ); @@ -140,38 +115,31 @@ export function CompanySection({ return ( ); case "workstations": - return ; + return ; case "inventory": return ( - + ); case "resources": return ( resource.values.integration?.kind === "mailbox" ? ( - + ) : null } /> ); case "resource-catalog": - return ; + return ; case "assets": - return ; + return ; } } diff --git a/modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx b/modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx index a1ff8df..f490d18 100644 --- a/modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx +++ b/modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx @@ -6,13 +6,11 @@ import { SiteForm } from "../../../sites"; export function InventoryQuickCreateDialog({ companyId, - csrfToken, kind, onCreated, onClose, }: { companyId: string; - csrfToken: string; kind: "site" | "person"; onCreated: (id: string) => void; onClose: () => void; @@ -36,7 +34,6 @@ export function InventoryQuickCreateDialog({ {kind === "site" ? ( { onCreated(id); onClose(); @@ -46,7 +43,6 @@ export function InventoryQuickCreateDialog({ ) : ( { onCreated(id); onClose(); diff --git a/modules/companies/src/web/companies/list/types/companies-page-props.ts b/modules/companies/src/web/companies/list/types/companies-page-props.ts index 8536ff5..478f09a 100644 --- a/modules/companies/src/web/companies/list/types/companies-page-props.ts +++ b/modules/companies/src/web/companies/list/types/companies-page-props.ts @@ -1,5 +1,4 @@ export type CompaniesPageProps = { - csrfToken: string; onSelectCompany?: (id: string) => void; initialActiveOnly?: boolean | undefined; initialCompanyId?: string | undefined; diff --git a/modules/companies/src/web/companies/list/ui/companies-page.tsx b/modules/companies/src/web/companies/list/ui/companies-page.tsx index c106338..8d8480f 100644 --- a/modules/companies/src/web/companies/list/ui/companies-page.tsx +++ b/modules/companies/src/web/companies/list/ui/companies-page.tsx @@ -16,7 +16,7 @@ import { useCompaniesPageController } from "../controllers"; import type { CompaniesPageProps } from "../types"; export function CompaniesPage(props: CompaniesPageProps) { - const { csrfToken, onSelectCompany } = props; + const { onSelectCompany } = props; const { query, selectedId, @@ -36,12 +36,7 @@ export function CompaniesPage(props: CompaniesPageProps) { if (selectedId && !onSelectCompany) { return ( - setSelectedId(undefined)} - /> + setSelectedId(undefined)} /> ); } const companies = table @@ -153,7 +148,6 @@ export function CompaniesPage(props: CompaniesPageProps) { /> )} { setCreateOpen(false); selectCompany(companyId); diff --git a/modules/companies/src/web/companies/shared/api/company-request.ts b/modules/companies/src/web/companies/shared/api/company-request.ts index 81492aa..3bc74ff 100644 --- a/modules/companies/src/web/companies/shared/api/company-request.ts +++ b/modules/companies/src/web/companies/shared/api/company-request.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import type { z } from "zod/v4"; import { CompanyErrorResponseSchema } from "../../../../common"; @@ -7,16 +8,12 @@ import { CompanyApiError } from "./company-api-error"; export async function companyRequest( path: string, schema: z.ZodType, - body?: unknown, - csrfToken?: string + body?: unknown ): Promise { - const response = await fetch(`/api/companies${path}`, { + const response = await csrfFetch(`/api/companies${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": csrfToken ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const json: unknown = await response.json(); diff --git a/modules/companies/src/web/companies/shared/api/save-company.api.ts b/modules/companies/src/web/companies/shared/api/save-company.api.ts index 66b12c6..211cb81 100644 --- a/modules/companies/src/web/companies/shared/api/save-company.api.ts +++ b/modules/companies/src/web/companies/shared/api/save-company.api.ts @@ -2,5 +2,5 @@ import { CompanyResponseSchema } from "../../../../common"; import { companyRequest } from "./company-request"; -export const saveCompany = (id: string | undefined, body: unknown, token: string) => - companyRequest(id ? `/${id}/update` : "", CompanyResponseSchema, body, token); +export const saveCompany = (id: string | undefined, body: unknown) => + companyRequest(id ? `/${id}/update` : "", CompanyResponseSchema, body); diff --git a/modules/companies/src/web/companies/shared/companies-page.test.tsx b/modules/companies/src/web/companies/shared/companies-page.test.tsx index dc5b320..6addb0e 100644 --- a/modules/companies/src/web/companies/shared/companies-page.test.tsx +++ b/modules/companies/src/web/companies/shared/companies-page.test.tsx @@ -60,11 +60,7 @@ test("el dashboard no ofrece selector de compañía y conserva las métricas de for (const scope of [undefined, companyId]) { const markup = await renderPage( language, - undefined} - />, + undefined} />, (client) => { for (const summaryScope of [undefined, companyId]) { client.setQueryData(dashboardKeys.block("summary", summaryScope), { @@ -89,7 +85,7 @@ test("el resumen de compañía es la entrada por defecto y ofrece destinos reale for (const language of ["es", "en"]) { const markup = await renderPage( language, - undefined} />, + undefined} />, (client) => client.setQueryData(companyKeys.detail(companyId), { company: { @@ -145,7 +141,7 @@ test("el listado presenta estado vacío y acceso al alta sin formularios visible ["es", "Compañías", "Todavía no hay compañías"], ["en", "Companies", "No companies yet"], ] as const) { - const markup = await renderPage(language, , (client) => + const markup = await renderPage(language, , (client) => client.setQueryData(companyKeys.list, { companies: [] }) ); @@ -159,12 +155,7 @@ test("el listado presenta estado vacío y acceso al alta sin formularios visible test("la ficha presenta datos de la compañía, contactos y acceso a sedes", async () => { const markup = await renderPage( "es", - undefined} - />, + undefined} />, (client) => client.setQueryData(companyKeys.detail(companyId), { company: { @@ -200,11 +191,7 @@ test("la ficha presenta datos de la compañía, contactos y acceso a sedes", asy assert.equal(markup.includes(" { - const markup = await renderPage( - "es", - , - () => undefined - ); + const markup = await renderPage("es", , () => undefined); assert.ok(markup.includes("Dirección (opcional)")); assert.match(markup, /]*rows="6"/); @@ -218,7 +205,7 @@ test("el formulario de sede ofrece dirección multilínea optativa", async () => test("el formulario de persona distingue contactos de cuentas de acceso y permite datos opcionales", async () => { const markup = await renderPage( "es", - undefined} />, + undefined} />, () => undefined ); @@ -246,12 +233,7 @@ test("asignar un departamento permite añadir el primero y conserva el selector }; const markup = await renderPage( "es", - undefined} - personId={companyId} - />, + undefined} personId={companyId} />, () => undefined ); @@ -273,7 +255,6 @@ test("asignar un departamento permite añadir el primero y conserva el selector isPrimary: false, }} company={company} - csrfToken="test" onSaved={() => undefined} personId={companyId} />, @@ -296,29 +277,25 @@ test("el tablero de compañía tiene composición propia, acciones arriba y mét }; for (const language of ["es", "en"]) { - const html = await renderPage( - language, - , - (client) => { - client.setQueryData(dashboardKeys.block("summary"), { - companies: 999, - tickets: 999, - interventions: 999, - assets: 999, - resources: 999, - }); - client.setQueryData(dashboardKeys.block("summary", companyId), { - companies: 1, - tickets: 7, - interventions: 3, - assets: 24, - resources: 11, - }); - client.setQueryData(dashboardKeys.block("work", companyId), { items: [], total: 0 }); - client.setQueryData(dashboardKeys.block("activity", companyId), { items: [], total: 0 }); - client.setQueryData(dashboardKeys.block("attention", companyId), { items: [] }); - } - ); + const html = await renderPage(language, , (client) => { + client.setQueryData(dashboardKeys.block("summary"), { + companies: 999, + tickets: 999, + interventions: 999, + assets: 999, + resources: 999, + }); + client.setQueryData(dashboardKeys.block("summary", companyId), { + companies: 1, + tickets: 7, + interventions: 3, + assets: 24, + resources: 11, + }); + client.setQueryData(dashboardKeys.block("work", companyId), { items: [], total: 0 }); + client.setQueryData(dashboardKeys.block("activity", companyId), { items: [], total: 0 }); + client.setQueryData(dashboardKeys.block("attention", companyId), { items: [] }); + }); assert.ok(html.includes('data-slot="company-overview"')); assert.equal(/>999<\//.test(html), false); @@ -347,7 +324,7 @@ test("el tablero de compañía tiene composición propia, acciones arriba y mét test("el menú de compañía ordena trabajo y personas por grupos sin repetir las acciones de usuario", async () => { const html = await renderPage( "es", - undefined} />, + undefined} />, (client) => client.setQueryData(companyKeys.detail(companyId), { company: { diff --git a/modules/companies/src/web/companies/shared/controllers/use-company-form-controller.ts b/modules/companies/src/web/companies/shared/controllers/use-company-form-controller.ts index 0998a32..45d34f7 100644 --- a/modules/companies/src/web/companies/shared/controllers/use-company-form-controller.ts +++ b/modules/companies/src/web/companies/shared/controllers/use-company-form-controller.ts @@ -10,12 +10,7 @@ import { saveCompany } from "../api"; import { companyKeys } from "../queries"; import type { CompanyFormProps } from "../types"; -export function useCompanyFormController({ - company, - csrfToken, - onCreated, - onSaved, -}: CompanyFormProps) { +export function useCompanyFormController({ company, onCreated, onSaved }: CompanyFormProps) { const client = useQueryClient(); const [savedCompanyId, setSavedCompanyId] = useState(company?.id); const [partialSave, setPartialSave] = useState(false); @@ -27,13 +22,13 @@ export function useCompanyFormController({ const mutation = useMutation({ mutationFn: async (values: CompanyRequestDTO) => { setPartialSave(false); - const response = await saveCompany(savedCompanyId, values, csrfToken); + const response = await saveCompany(savedCompanyId, values); setSavedCompanyId(response.company.id); client.setQueryData(companyKeys.detail(response.company.id), response); if (logoDraft !== undefined) { setPartialSave(true); - const logo = await saveCompanyLogo(response.company.id, logoDraft, csrfToken); + const logo = await saveCompanyLogo(response.company.id, logoDraft); return { ...response, company: { ...response.company, logoVersion: logo.logoVersion } }; } diff --git a/modules/companies/src/web/companies/shared/types/company-form-props.ts b/modules/companies/src/web/companies/shared/types/company-form-props.ts index 492912f..a937725 100644 --- a/modules/companies/src/web/companies/shared/types/company-form-props.ts +++ b/modules/companies/src/web/companies/shared/types/company-form-props.ts @@ -2,7 +2,6 @@ import type { CompanySummaryDTO } from "../../../../common"; export type CompanyFormProps = { company?: CompanySummaryDTO; - csrfToken: string; onCreated?: (id: string) => void; onSaved?: () => void; showTitle?: boolean; diff --git a/modules/companies/src/web/dashboard/create/types/dashboard-quick-create-props.ts b/modules/companies/src/web/dashboard/create/types/dashboard-quick-create-props.ts index 6f41bb1..f568b88 100644 --- a/modules/companies/src/web/dashboard/create/types/dashboard-quick-create-props.ts +++ b/modules/companies/src/web/dashboard/create/types/dashboard-quick-create-props.ts @@ -3,7 +3,6 @@ import type { DashboardNavigation } from "../../shared/helpers"; export type DashboardQuickCreateProps = { kind: "company" | "ticket" | "intervention" | "asset" | "resource"; companyId?: string | undefined; - csrfToken: string; onClose: () => void; onNavigate: (destination: DashboardNavigation) => void; }; diff --git a/modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx b/modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx index e699e29..8094cba 100644 --- a/modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx +++ b/modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx @@ -12,7 +12,7 @@ import { useDashboardQuickCreateController } from "../controllers"; import type { DashboardQuickCreateProps } from "../types"; export function DashboardQuickCreate(props: DashboardQuickCreateProps) { - const { kind, companyId, csrfToken, onClose, onNavigate } = props; + const { kind, companyId, onClose, onNavigate } = props; const { selectedCompany, setSelectedCompany, @@ -39,14 +39,11 @@ export function DashboardQuickCreate(props: DashboardQuickCreateProps) { title={t(`dashboard.create.${kind}`)} wide={kind === "ticket" || kind === "intervention"} > - {kind === "company" && ( - - )} + {kind === "company" && } {kind === "ticket" && ( {tickets.data && ( {interventions.data && ( {addingCompany && ( { setSelectedCompany(id); setAddingCompany(false); diff --git a/modules/companies/src/web/dashboard/shared/api/dashboard-api.ts b/modules/companies/src/web/dashboard/shared/api/dashboard-api.ts index bb327b2..88019ac 100644 --- a/modules/companies/src/web/dashboard/shared/api/dashboard-api.ts +++ b/modules/companies/src/web/dashboard/shared/api/dashboard-api.ts @@ -1,3 +1,5 @@ +import { csrfFetch } from "@metis/core/client"; + import type { DashboardDetailsQuery } from "../../../../common/dashboard"; import { DashboardAttentionSchema, @@ -30,7 +32,7 @@ async function request( parameters.set(key, String(value)); } } - const response = await fetch(`/api/dashboard/${block}?${parameters}`, { + const response = await csrfFetch(`/api/dashboard/${block}?${parameters}`, { signal, credentials: "same-origin", }); diff --git a/modules/companies/src/web/dashboard/view/types/dashboard-page-props.ts b/modules/companies/src/web/dashboard/view/types/dashboard-page-props.ts index db9b7b7..edf20d0 100644 --- a/modules/companies/src/web/dashboard/view/types/dashboard-page-props.ts +++ b/modules/companies/src/web/dashboard/view/types/dashboard-page-props.ts @@ -2,7 +2,6 @@ import type { DashboardNavigation } from "../../shared/helpers"; export type DashboardPageProps = { onNavigate: (destination: DashboardNavigation) => void; - csrfToken: string; companyId?: string; title?: string; description?: string; diff --git a/modules/companies/src/web/dashboard/view/ui/dashboard-page.tsx b/modules/companies/src/web/dashboard/view/ui/dashboard-page.tsx index 8bae761..6878281 100644 --- a/modules/companies/src/web/dashboard/view/ui/dashboard-page.tsx +++ b/modules/companies/src/web/dashboard/view/ui/dashboard-page.tsx @@ -16,7 +16,7 @@ import { DashboardStatistics } from "./dashboard-statistics"; import { DashboardWorkPanel } from "./dashboard-work-panel"; export function DashboardPage(props: DashboardPageProps) { - const { onNavigate, csrfToken } = props; + const { onNavigate } = props; const { scope, details, setDetails, quick, setQuick, summary } = useDashboardPageController( props.companyId ); @@ -91,7 +91,6 @@ export function DashboardPage(props: DashboardPageProps) { {quick && ( setQuick(undefined)} onNavigate={onNavigate} diff --git a/modules/companies/src/web/departments/list/ui/department-section.tsx b/modules/companies/src/web/departments/list/ui/department-section.tsx index 13579ae..7a83ca0 100644 --- a/modules/companies/src/web/departments/list/ui/department-section.tsx +++ b/modules/companies/src/web/departments/list/ui/department-section.tsx @@ -10,16 +10,10 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import type { CompanyDTO } from "../../../../common"; -import { DepartmentEditorDialog } from "../../shared"; import { PersonAvatarGroup } from "../../../people"; +import { DepartmentEditorDialog } from "../../shared"; -export function DepartmentSection({ - company, - csrfToken, -}: { - company: CompanyDTO; - csrfToken: string; -}) { +export function DepartmentSection({ company }: { company: CompanyDTO }) { const { t, i18n } = useTranslation("companies"); const [editor, setEditor] = useState(); const selected = company.departments.find((item) => item.id === editor); @@ -90,7 +84,6 @@ export function DepartmentSection({ /> { if (!open) { diff --git a/modules/companies/src/web/departments/shared/api/save-department.api.ts b/modules/companies/src/web/departments/shared/api/save-department.api.ts index ace1c7e..04910a2 100644 --- a/modules/companies/src/web/departments/shared/api/save-department.api.ts +++ b/modules/companies/src/web/departments/shared/api/save-department.api.ts @@ -1,15 +1,9 @@ import { CompanyResponseSchema } from "../../../../common"; import { companyRequest } from "../../../companies/shared/api/company-request"; -export const saveDepartment = ( - companyId: string, - id: string | undefined, - body: unknown, - token: string -) => +export const saveDepartment = (companyId: string, id: string | undefined, body: unknown) => companyRequest( `/${companyId}/departments${id ? `/${id}/update` : ""}`, CompanyResponseSchema, - body, - token + body ); diff --git a/modules/companies/src/web/departments/shared/controllers/use-department-form-controller.ts b/modules/companies/src/web/departments/shared/controllers/use-department-form-controller.ts index 12b3f3f..116c310 100644 --- a/modules/companies/src/web/departments/shared/controllers/use-department-form-controller.ts +++ b/modules/companies/src/web/departments/shared/controllers/use-department-form-controller.ts @@ -12,7 +12,6 @@ import type { DepartmentFormProps } from "../types"; export function useDepartmentFormController({ companyId, department, - csrfToken, onSaved, }: DepartmentFormProps) { const client = useQueryClient(); @@ -30,8 +29,7 @@ export function useDepartmentFormController({ defaultValues: { name: department?.name ?? "", active: department?.active ?? true }, }); const mutation = useMutation({ - mutationFn: (values: DepartmentRequestDTO) => - saveDepartment(companyId, department?.id, values, csrfToken), + mutationFn: (values: DepartmentRequestDTO) => saveDepartment(companyId, department?.id, values), onSuccess: (response) => { client.setQueryData(companyKeys.detail(companyId), response); const saved = department diff --git a/modules/companies/src/web/departments/shared/types/department-form-props.ts b/modules/companies/src/web/departments/shared/types/department-form-props.ts index b4d711c..2ff846d 100644 --- a/modules/companies/src/web/departments/shared/types/department-form-props.ts +++ b/modules/companies/src/web/departments/shared/types/department-form-props.ts @@ -3,7 +3,6 @@ import type { DepartmentDTO } from "../../../../common"; export type DepartmentFormProps = { companyId: string; department?: DepartmentDTO | undefined; - csrfToken: string; onSaved: (id: string) => void; requireActive?: boolean; }; diff --git a/modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx b/modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx index 7a2f5c1..75d2d5d 100644 --- a/modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx +++ b/modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx @@ -8,7 +8,6 @@ import { WorkProfileForm } from "../../../work-profiles/shared"; export function FolderQuickEditor({ kind, company, - csrfToken, data, profile, onClose, @@ -16,7 +15,6 @@ export function FolderQuickEditor({ }: { kind: "profile" | "need"; company: CompanyDTO; - csrfToken: string; data: AccessResponseDTO | undefined; profile: WorkProfileDTO | undefined; onClose: () => void; @@ -41,14 +39,12 @@ export function FolderQuickEditor({ onCreated("profileId", id)} /> )} {kind === "need" && data && profile && ( setDeleteId(undefined)} path={deleteId ? `/api/folder-mappings/${company.id}/${deleteId}/delete` : undefined} queryKey={["folder-mappings", company.id]} @@ -175,7 +174,6 @@ export function FolderMappingsPage(props: FolderMappingsPageProps) { {editor && ( setEditor(undefined)} diff --git a/modules/companies/src/web/folder-mappings/shared/api/folder-api.ts b/modules/companies/src/web/folder-mappings/shared/api/folder-api.ts index 8d9d53f..253222c 100644 --- a/modules/companies/src/web/folder-mappings/shared/api/folder-api.ts +++ b/modules/companies/src/web/folder-mappings/shared/api/folder-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { FolderMappingInput } from "../../../../common/folder-mappings"; @@ -30,17 +31,17 @@ export const folderApi = { async get(companyId: string) { return FolderMappingsResponseSchema.parse( await responseData( - await fetch(`/api/folder-mappings/${companyId}`, { credentials: "same-origin" }) + await csrfFetch(`/api/folder-mappings/${companyId}`, { credentials: "same-origin" }) ) ); }, - async save(companyId: string, values: FolderMappingInput, csrfToken: string, id?: string) { + async save(companyId: string, values: FolderMappingInput, id?: string) { return FolderMappingResponseSchema.parse( await responseData( - await fetch(`/api/folder-mappings/${companyId}${id ? `/${id}/update` : ""}`, { + await csrfFetch(`/api/folder-mappings/${companyId}${id ? `/${id}/update` : ""}`, { method: "POST", credentials: "same-origin", - headers: { "content-type": "application/json", "x-csrf-token": csrfToken }, + headers: { "content-type": "application/json" }, body: JSON.stringify({ values }), }) ) diff --git a/modules/companies/src/web/folder-mappings/shared/controllers/use-folder-mapping-form-controller.ts b/modules/companies/src/web/folder-mappings/shared/controllers/use-folder-mapping-form-controller.ts index 62a815a..bc1c457 100644 --- a/modules/companies/src/web/folder-mappings/shared/controllers/use-folder-mapping-form-controller.ts +++ b/modules/companies/src/web/folder-mappings/shared/controllers/use-folder-mapping-form-controller.ts @@ -18,7 +18,6 @@ import type { FolderMappingFormProps } from "../types"; export function useFolderMappingFormController({ company, - csrfToken, mapping, onSaved, initialProfileId = "", @@ -64,8 +63,7 @@ export function useFolderMappingFormController({ }; const mutation = useMutation({ - mutationFn: (input: FolderMappingInput) => - folderApi.save(company.id, input, csrfToken, mapping?.id), + mutationFn: (input: FolderMappingInput) => folderApi.save(company.id, input, mapping?.id), onSuccess: async () => { await client.invalidateQueries({ queryKey: folderKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/folder-mappings/shared/types/folder-mapping-form-props.ts b/modules/companies/src/web/folder-mappings/shared/types/folder-mapping-form-props.ts index 13438db..78d2db3 100644 --- a/modules/companies/src/web/folder-mappings/shared/types/folder-mapping-form-props.ts +++ b/modules/companies/src/web/folder-mappings/shared/types/folder-mapping-form-props.ts @@ -3,7 +3,6 @@ import type { FolderMappingDTO } from "../../../../common/folder-mappings"; export type FolderMappingFormProps = { company: CompanyDTO; - csrfToken: string; mapping?: FolderMappingDTO | undefined; onSaved: () => void; initialProfileId?: string; diff --git a/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx b/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx index e27c1e9..18f1a00 100644 --- a/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx +++ b/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx @@ -9,13 +9,11 @@ import { FolderMappingForm } from "./folder-mapping-form"; export function FolderMappingEditorDialog({ mapping, company, - csrfToken, initialProfileId, onClose, }: { mapping: ComponentProps["mapping"]; company: CompanyDTO; - csrfToken: string; initialProfileId: ComponentProps["initialProfileId"]; onClose: () => void; }) { @@ -36,7 +34,6 @@ export function FolderMappingEditorDialog({ > onClose()} diff --git a/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-form.tsx b/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-form.tsx index fcd0983..1e56768 100644 --- a/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-form.tsx +++ b/modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-form.tsx @@ -17,7 +17,7 @@ import { FolderFeedback } from "./folder-feedback"; import { FolderMappingTargets } from "./folder-mapping-targets"; export function FolderMappingForm(props: FolderMappingFormProps) { - const { company, csrfToken, mapping, initialProfileId = "" } = props; + const { company, mapping, initialProfileId = "" } = props; const { quick, setQuick, @@ -140,7 +140,6 @@ export function FolderMappingForm(props: FolderMappingFormProps) { {quick && ( setQuick(undefined)} diff --git a/modules/companies/src/web/intervention-templates/create/ui/intervention-template-create-dialog.tsx b/modules/companies/src/web/intervention-templates/create/ui/intervention-template-create-dialog.tsx index b646f54..523f5f9 100644 --- a/modules/companies/src/web/intervention-templates/create/ui/intervention-template-create-dialog.tsx +++ b/modules/companies/src/web/intervention-templates/create/ui/intervention-template-create-dialog.tsx @@ -6,13 +6,11 @@ import { InterventionTemplateForm } from "../../shared"; export function InterventionTemplateCreateDialog({ open, companyId, - csrfToken, onSaved, onOpenChange, }: { open: boolean; companyId: string; - csrfToken: string; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; }) { @@ -27,9 +25,7 @@ export function InterventionTemplateCreateDialog({ title={t("interventions.newTemplate")} wide > - {open && ( - - )} + {open && } ); } diff --git a/modules/companies/src/web/intervention-templates/list/types/templates-page-props.ts b/modules/companies/src/web/intervention-templates/list/types/templates-page-props.ts index 19aa92e..46b4f9a 100644 --- a/modules/companies/src/web/intervention-templates/list/types/templates-page-props.ts +++ b/modules/companies/src/web/intervention-templates/list/types/templates-page-props.ts @@ -1,4 +1,3 @@ export type InterventionTemplatesPageProps = { scope?: string; - csrfToken: string; }; diff --git a/modules/companies/src/web/intervention-templates/list/ui/templates-page.tsx b/modules/companies/src/web/intervention-templates/list/ui/templates-page.tsx index ff9468e..36a43de 100644 --- a/modules/companies/src/web/intervention-templates/list/ui/templates-page.tsx +++ b/modules/companies/src/web/intervention-templates/list/ui/templates-page.tsx @@ -18,7 +18,7 @@ import { useInterventionTemplatesPageController } from "../controllers"; import type { InterventionTemplatesPageProps } from "../types"; export function InterventionTemplatesPage(props: InterventionTemplatesPageProps) { - const { scope = "global", csrfToken } = props; + const { scope = "global" } = props; const { query, editor, @@ -170,7 +170,6 @@ export function InterventionTemplatesPage(props: InterventionTemplatesPageProps) )} { if (!open) { diff --git a/modules/companies/src/web/intervention-templates/shared/api/template-api.ts b/modules/companies/src/web/intervention-templates/shared/api/template-api.ts index ec28f88..e085282 100644 --- a/modules/companies/src/web/intervention-templates/shared/api/template-api.ts +++ b/modules/companies/src/web/intervention-templates/shared/api/template-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { SaveInterventionTemplateInput } from "../../../../common/intervention-templates"; @@ -16,21 +17,16 @@ export class InterventionTemplateApiError extends Error { } } -async function request( - scope: string, - id?: string, - body?: SaveInterventionTemplateInput, - csrfToken?: string -) { - const response = await fetch(`/api/intervention-templates/${scope}${id ? `/${id}/update` : ""}`, { - method: body === undefined ? "GET" : "POST", - credentials: "same-origin", - headers: - body === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": csrfToken ?? "" }, - ...(body === undefined ? {} : { body: JSON.stringify(body) }), - }); +async function request(scope: string, id?: string, body?: SaveInterventionTemplateInput) { + const response = await csrfFetch( + `/api/intervention-templates/${scope}${id ? `/${id}/update` : ""}`, + { + method: body === undefined ? "GET" : "POST", + credentials: "same-origin", + headers: body === undefined ? {} : { "content-type": "application/json" }, + ...(body === undefined ? {} : { body: JSON.stringify(body) }), + } + ); const data: unknown = await response.json(); if (!response.ok) { @@ -46,12 +42,7 @@ export const interventionTemplateApi = { async list(scope: string) { return InterventionTemplatesSchema.parse(await request(scope)); }, - async save( - scope: string, - id: string | undefined, - input: SaveInterventionTemplateInput, - csrfToken: string - ) { - return InterventionTemplateResponseSchema.parse(await request(scope, id, input, csrfToken)); + async save(scope: string, id: string | undefined, input: SaveInterventionTemplateInput) { + return InterventionTemplateResponseSchema.parse(await request(scope, id, input)); }, }; diff --git a/modules/companies/src/web/intervention-templates/shared/controllers/use-template-form-controller.ts b/modules/companies/src/web/intervention-templates/shared/controllers/use-template-form-controller.ts index e0e1f60..9ef482a 100644 --- a/modules/companies/src/web/intervention-templates/shared/controllers/use-template-form-controller.ts +++ b/modules/companies/src/web/intervention-templates/shared/controllers/use-template-form-controller.ts @@ -14,7 +14,6 @@ export function useInterventionTemplateFormController({ scope, template, initialValues, - csrfToken, onSaved, }: InterventionTemplateFormProps) { const client = useQueryClient(); @@ -36,12 +35,11 @@ export function useInterventionTemplateFormController({ const steps = useFieldArray({ control: form.control, name: "steps", keyName: "fieldKey" }); const mutation = useMutation({ mutationFn: (values: InterventionTemplateInput) => - interventionTemplateApi.save( - scope, - template?.id, - { values, requestKey, expectedRevisionId: template?.history.at(-1)?.id ?? null }, - csrfToken - ), + interventionTemplateApi.save(scope, template?.id, { + values, + requestKey, + expectedRevisionId: template?.history.at(-1)?.id ?? null, + }), onError: async (error) => { if (error instanceof InterventionTemplateApiError && error.code === "TEMPLATE_CHANGED") { await client.invalidateQueries({ queryKey: ["intervention-templates"] }); diff --git a/modules/companies/src/web/intervention-templates/shared/templates.test.tsx b/modules/companies/src/web/intervention-templates/shared/templates.test.tsx index 817f3ec..6c65103 100644 --- a/modules/companies/src/web/intervention-templates/shared/templates.test.tsx +++ b/modules/companies/src/web/intervention-templates/shared/templates.test.tsx @@ -75,23 +75,21 @@ async function render(node: ReactNode, language = "es") { } test("plantillas de intervención: listado sin formulario, es/en y catálogo global junto al propio de compañía", async () => { - const global = await render(); + const global = await render(); assert.ok(global.includes("Plantillas de intervención")); assert.equal(global.includes("); + const scoped = await render(); assert.equal((scoped.match(/aria-label="Acciones"/g) ?? []).length, 2); assert.ok(scoped.includes(companiesSpanish.interventionTemplates.global)); assert.ok(scoped.includes(companiesSpanish.interventionTemplates.company)); - const english = await render(, "en"); + const english = await render(, "en"); assert.ok(english.includes("Intervention templates")); }); test("plantillas de intervención: ayuda antes del pie y controles de pasos", async () => { - const html = await render( - undefined} scope="global" /> - ); + const html = await render( undefined} scope="global" />); assert.ok(html.includes("Añadir paso")); assert.ok(html.includes("Subir paso")); diff --git a/modules/companies/src/web/intervention-templates/shared/types/template-form-props.ts b/modules/companies/src/web/intervention-templates/shared/types/template-form-props.ts index b338079..9242531 100644 --- a/modules/companies/src/web/intervention-templates/shared/types/template-form-props.ts +++ b/modules/companies/src/web/intervention-templates/shared/types/template-form-props.ts @@ -7,6 +7,5 @@ export type InterventionTemplateFormProps = { scope: string; template?: InterventionTemplateDTO | undefined; initialValues?: InterventionTemplateInput | undefined; - csrfToken: string; onSaved: (id: string) => void; }; diff --git a/modules/companies/src/web/intervention-templates/shared/ui/intervention-template-editor-dialog.tsx b/modules/companies/src/web/intervention-templates/shared/ui/intervention-template-editor-dialog.tsx index de4ad2b..1e281bb 100644 --- a/modules/companies/src/web/intervention-templates/shared/ui/intervention-template-editor-dialog.tsx +++ b/modules/companies/src/web/intervention-templates/shared/ui/intervention-template-editor-dialog.tsx @@ -11,7 +11,6 @@ export function InterventionTemplateEditorDialog({ selected, copied, scope, - csrfToken, onSaved, onOpenChange, }: { @@ -19,7 +18,6 @@ export function InterventionTemplateEditorDialog({ selected: ComponentProps["template"]; copied: ComponentProps["template"]; scope: string; - csrfToken: string; onSaved: () => void; onOpenChange: (open: boolean) => void; }) { @@ -36,7 +34,6 @@ export function InterventionTemplateEditorDialog({ > {editor && ( ["ticket"] | undefined; - csrfToken: string; onClose: () => void; }) { const { t } = useTranslation("companies"); @@ -29,11 +27,7 @@ export function TicketInterventionDialog({ wide > {interventionTicket && ( - onClose()} - ticket={interventionTicket} - /> + onClose()} ticket={interventionTicket} /> )} ); diff --git a/modules/companies/src/web/interventions/create/ui/ticket-intervention-form.tsx b/modules/companies/src/web/interventions/create/ui/ticket-intervention-form.tsx index 3d1cb00..df7b702 100644 --- a/modules/companies/src/web/interventions/create/ui/ticket-intervention-form.tsx +++ b/modules/companies/src/web/interventions/create/ui/ticket-intervention-form.tsx @@ -8,11 +8,9 @@ import { interventionApi, interventionKeys } from "../../shared/api"; export function TicketInterventionForm({ ticket, - csrfToken, onSaved, }: { ticket: TicketDTO; - csrfToken: string; onSaved: () => void; }) { const { t } = useTranslation("companies"); @@ -37,12 +35,7 @@ export function TicketInterventionForm({ )} {query.data && !query.isError && ( - + )} ); diff --git a/modules/companies/src/web/interventions/list/types/interventions-page-props.ts b/modules/companies/src/web/interventions/list/types/interventions-page-props.ts index f2dd60b..8c68350 100644 --- a/modules/companies/src/web/interventions/list/types/interventions-page-props.ts +++ b/modules/companies/src/web/interventions/list/types/interventions-page-props.ts @@ -9,7 +9,6 @@ export type InterventionFilters = { export type InterventionsPageProps = { companyId?: string | undefined; - csrfToken: string; initialId?: string | undefined; initialCreate?: boolean | undefined; initialPendingOnly?: boolean | undefined; diff --git a/modules/companies/src/web/interventions/list/ui/interventions-page.tsx b/modules/companies/src/web/interventions/list/ui/interventions-page.tsx index c6e2e2e..69734bd 100644 --- a/modules/companies/src/web/interventions/list/ui/interventions-page.tsx +++ b/modules/companies/src/web/interventions/list/ui/interventions-page.tsx @@ -18,7 +18,7 @@ import { useInterventionsPageController } from "../controllers"; import type { InterventionsPageProps } from "../types"; export function InterventionsPage(props: InterventionsPageProps) { - const { companyId, csrfToken } = props; + const { companyId } = props; const { query, filters, @@ -277,7 +277,6 @@ export function InterventionsPage(props: InterventionsPageProps) { /> { diff --git a/modules/companies/src/web/interventions/shared/interventions.test.tsx b/modules/companies/src/web/interventions/shared/interventions.test.tsx index 61c232e..f935875 100644 --- a/modules/companies/src/web/interventions/shared/interventions.test.tsx +++ b/modules/companies/src/web/interventions/shared/interventions.test.tsx @@ -104,17 +104,17 @@ test("intervenciones: filtros por compañía, técnico, estado y fecha prevista" assert.equal(filterInterventions([intervention], { ...filters, company: "other" }).length, 0); }); test("intervenciones: vistas es/en sin formularios junto al listado", async () => { - const es = await render(); + const es = await render(); assert.ok(es.includes("Todas las intervenciones")); assert.equal(es.includes(", "en"); + const en = await render(, "en"); assert.ok(en.includes("All interventions")); }); test("intervenciones: modal sencillo, pasos opcionales, altas rápidas y ayuda inferior", async () => { const html = await render( - undefined} scope={companyId} /> + undefined} scope={companyId} /> ); assert.ok(html.includes("Empresa test")); diff --git a/modules/companies/src/web/interventions/shared/types/intervention-form-props.ts b/modules/companies/src/web/interventions/shared/types/intervention-form-props.ts index 9c2cbce..6e5dc69 100644 --- a/modules/companies/src/web/interventions/shared/types/intervention-form-props.ts +++ b/modules/companies/src/web/interventions/shared/types/intervention-form-props.ts @@ -6,6 +6,5 @@ export type InterventionFormProps = { intervention?: InterventionDTO; initialTicket?: TicketDTO; data: InterventionListDTO; - csrfToken: string; onSaved: () => void; }; diff --git a/modules/companies/src/web/interventions/shared/ui/intervention-editor-dialog.tsx b/modules/companies/src/web/interventions/shared/ui/intervention-editor-dialog.tsx index ba76293..df114df 100644 --- a/modules/companies/src/web/interventions/shared/ui/intervention-editor-dialog.tsx +++ b/modules/companies/src/web/interventions/shared/ui/intervention-editor-dialog.tsx @@ -8,14 +8,12 @@ export function InterventionEditorDialog({ open, edit, companyId, - csrfToken, data, onOpenChange, }: { open: boolean; edit: ComponentProps["intervention"]; companyId: string | undefined; - csrfToken: string; data: ComponentProps["data"] | undefined; onOpenChange: (open: boolean) => void; }) { @@ -35,7 +33,6 @@ export function InterventionEditorDialog({ key={edit?.id ?? "new"} {...(companyId ? { scope: companyId } : {})} {...(edit ? { intervention: edit } : {})} - csrfToken={csrfToken} data={data} onSaved={() => onOpenChange(false)} /> diff --git a/modules/companies/src/web/interventions/shared/ui/intervention-form.tsx b/modules/companies/src/web/interventions/shared/ui/intervention-form.tsx index 7f42560..2416c57 100644 --- a/modules/companies/src/web/interventions/shared/ui/intervention-form.tsx +++ b/modules/companies/src/web/interventions/shared/ui/intervention-form.tsx @@ -31,7 +31,7 @@ function isCompanyFixed( } export function InterventionForm(props: InterventionFormProps) { - const { scope, intervention, initialTicket, data, csrfToken } = props; + const { scope, intervention, initialTicket, data } = props; const { client, companyId, @@ -128,7 +128,6 @@ export function InterventionForm(props: InterventionFormProps) { {lookups.data && ( form.setValue("affected", affected)} @@ -164,7 +163,6 @@ export function InterventionForm(props: InterventionFormProps) { setQuick(undefined)} onCompany={(id) => { @@ -181,7 +179,6 @@ export function InterventionForm(props: InterventionFormProps) { { if (!open) { setQuick(undefined); @@ -202,7 +199,6 @@ export function InterventionForm(props: InterventionFormProps) { /> { if (!open) { setQuick(undefined); diff --git a/modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx b/modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx index 58f1258..cb2e060 100644 --- a/modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx +++ b/modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx @@ -11,7 +11,6 @@ import { WorkProfileForm } from "../../../work-profiles/shared"; export function MailQuickEditor({ kind, company, - csrfToken, profile, mailbox, initialAddress, @@ -20,7 +19,6 @@ export function MailQuickEditor({ }: { kind: "profile" | "mailbox" | "use"; company: CompanyDTO; - csrfToken: string; profile: WorkProfileDTO | undefined; mailbox: MailboxDTO | undefined; initialAddress: string; @@ -47,7 +45,6 @@ export function MailQuickEditor({ onCreated("mailboxId", id)} /> @@ -56,7 +53,6 @@ export function MailQuickEditor({ onCreated("profileId", id)} /> )} @@ -64,7 +60,6 @@ export function MailQuickEditor({ setDeleteId(undefined)} path={deleteId ? `/api/mail-configurations/${company.id}/${deleteId}/delete` : undefined} queryKey={["mail-configurations", company.id]} @@ -167,7 +166,6 @@ export function MailConfigurationsPage(props: MailConfigurationsPageProps) { setEditor(undefined)} /> diff --git a/modules/companies/src/web/mail-configurations/shared/api/mail-api.ts b/modules/companies/src/web/mail-configurations/shared/api/mail-api.ts index 2c652d1..bf69623 100644 --- a/modules/companies/src/web/mail-configurations/shared/api/mail-api.ts +++ b/modules/companies/src/web/mail-configurations/shared/api/mail-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { MailConfigurationInput } from "../../../../common/mail-configurations"; @@ -14,18 +15,13 @@ export class MailApiError extends Error { } } -async function request( - companyId: string, - values?: MailConfigurationInput, - token?: string, - id?: string -) { - const response = await fetch( +async function request(companyId: string, values?: MailConfigurationInput, id?: string) { + const response = await csrfFetch( `/api/mail-configurations/${companyId}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), } ); @@ -44,7 +40,7 @@ export const mailApi = { async get(companyId: string) { return MailConfigurationsResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: MailConfigurationInput, token: string, id?: string) { - return MailConfigurationResponseSchema.parse(await request(companyId, values, token, id)); + async save(companyId: string, values: MailConfigurationInput, id?: string) { + return MailConfigurationResponseSchema.parse(await request(companyId, values, id)); }, }; diff --git a/modules/companies/src/web/mail-configurations/shared/controllers/use-mail-configuration-form-controller.ts b/modules/companies/src/web/mail-configurations/shared/controllers/use-mail-configuration-form-controller.ts index e1b5f91..d56b148 100644 --- a/modules/companies/src/web/mail-configurations/shared/controllers/use-mail-configuration-form-controller.ts +++ b/modules/companies/src/web/mail-configurations/shared/controllers/use-mail-configuration-form-controller.ts @@ -27,7 +27,6 @@ function configurationStates(configuration: MailConfigurationDTO | undefined) { export function useMailConfigurationFormController({ company, - csrfToken, configuration, onSaved, initialProfileId = "", @@ -73,7 +72,7 @@ export function useMailConfigurationFormController({ ); const mutation = useMutation({ mutationFn: (input: MailConfigurationInput) => - mailApi.save(company.id, input, csrfToken, configuration?.id), + mailApi.save(company.id, input, configuration?.id), onSuccess: async () => { await client.invalidateQueries({ queryKey: mailKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/mail-configurations/shared/mail.test.tsx b/modules/companies/src/web/mail-configurations/shared/mail.test.tsx index 7dcd1a4..3d939ed 100644 --- a/modules/companies/src/web/mail-configurations/shared/mail.test.tsx +++ b/modules/companies/src/web/mail-configurations/shared/mail.test.tsx @@ -104,10 +104,7 @@ async function render(language: string, node: ReactNode) { test("correo: listado traducido, alta en cabecera y formularios ocultos", async () => { for (const language of ["es", "en"]) { - const html = await render( - language, - - ); + const html = await render(language, ); assert.equal(html.includes(" { const html = await render( "es", - undefined} /> + undefined} /> ); for (const label of ["Nuevo perfil", "Nuevo buzón"]) { @@ -138,20 +135,13 @@ test("Buzones: alta en modal, cuenta opcional y ayuda al final", async () => { for (const language of ["es", "en"]) { const page = await render( language, - + ); assert.equal(page.includes(" undefined} /> - ); + const form = await render("es", undefined} />); assert.ok(form.includes("Cuenta relacionada (opcional)")); assert.ok(form.indexOf("¿Qué es y para qué sirve?") < form.indexOf("Guardar")); diff --git a/modules/companies/src/web/mail-configurations/shared/types/mail-configuration-form-props.ts b/modules/companies/src/web/mail-configurations/shared/types/mail-configuration-form-props.ts index 7454a68..4dccd2e 100644 --- a/modules/companies/src/web/mail-configurations/shared/types/mail-configuration-form-props.ts +++ b/modules/companies/src/web/mail-configurations/shared/types/mail-configuration-form-props.ts @@ -3,7 +3,6 @@ import type { MailConfigurationDTO } from "../../../../common/mail-configuration export type MailConfigurationFormProps = { company: CompanyDTO; - csrfToken: string; configuration?: MailConfigurationDTO | undefined; onSaved: () => void; initialProfileId?: string; diff --git a/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx b/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx index 3a5e2a8..e84a5d1 100644 --- a/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx +++ b/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx @@ -9,13 +9,11 @@ import { MailConfigurationForm } from "./mail-configuration-form"; export function MailConfigurationEditorDialog({ configuration, company, - csrfToken, initialProfileId, onClose, }: { configuration: ComponentProps["configuration"]; company: CompanyDTO; - csrfToken: string; initialProfileId: ComponentProps["initialProfileId"]; onClose: () => void; }) { @@ -37,7 +35,6 @@ export function MailConfigurationEditorDialog({ onClose()} /> diff --git a/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-form.tsx b/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-form.tsx index 1cfda7e..0cc64ba 100644 --- a/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-form.tsx +++ b/modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-form.tsx @@ -16,7 +16,7 @@ import { MailConfigurationTargets } from "./mail-configuration-targets"; import { MailFeedback } from "./mail-feedback"; export function MailConfigurationForm(props: MailConfigurationFormProps) { - const { company, csrfToken, initialProfileId = "" } = props; + const { company, initialProfileId = "" } = props; const { quick, setQuick, @@ -121,7 +121,6 @@ export function MailConfigurationForm(props: MailConfigurationFormProps) { {quick && ( saveMailboxUse(company.id, input, csrfToken, use?.id), + mutationFn: (input: MailboxUseInput) => saveMailboxUse(company.id, input, use?.id), onSuccess: async () => { await Promise.all([ client.invalidateQueries({ queryKey: mailboxKeys.company(company.id) }), diff --git a/modules/companies/src/web/mailbox-uses/shared/types/mailbox-use-form-props.ts b/modules/companies/src/web/mailbox-uses/shared/types/mailbox-use-form-props.ts index 360fad1..c9b209a 100644 --- a/modules/companies/src/web/mailbox-uses/shared/types/mailbox-use-form-props.ts +++ b/modules/companies/src/web/mailbox-uses/shared/types/mailbox-use-form-props.ts @@ -3,7 +3,6 @@ import type { MailboxUseDTO } from "../../../../common/mailboxes"; export type MailboxUseFormProps = { company: CompanyDTO; - csrfToken: string; use?: MailboxUseDTO | undefined; initialMailboxId?: string; initialPersonId?: string; diff --git a/modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx b/modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx index dacc5e7..4fa7e09 100644 --- a/modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx +++ b/modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx @@ -9,14 +9,12 @@ import { MailboxUseForm } from "./mailbox-use-form"; export function MailboxUseEditorDialog({ use, company, - csrfToken, mailboxId, onSaved, onOpenChange, }: { use: ComponentProps["use"]; company: CompanyDTO; - csrfToken: string; mailboxId: string; onSaved: () => void; onOpenChange: (open: boolean) => void; @@ -34,7 +32,6 @@ export function MailboxUseEditorDialog({ { if (!open) { setQuick(undefined); @@ -134,7 +132,6 @@ export function MailboxUseForm(props: MailboxUseFormProps) { {quick === "mailbox" && ( { if (!open) { setQuick(undefined); diff --git a/modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx b/modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx index f41d98e..f802f03 100644 --- a/modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx +++ b/modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx @@ -6,12 +6,10 @@ import { MailboxForm } from "../../shared/ui/mailbox-form"; export function MailboxCreateDialog({ company, - csrfToken, onSaved, onOpenChange, }: { company: CompanyDTO; - csrfToken: string; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; }) { @@ -25,12 +23,7 @@ export function MailboxCreateDialog({ open title={t("mailbox.new")} > - + ); } diff --git a/modules/companies/src/web/mailboxes/shared/api/mailbox-request.ts b/modules/companies/src/web/mailboxes/shared/api/mailbox-request.ts index f8ba873..4dc365c 100644 --- a/modules/companies/src/web/mailboxes/shared/api/mailbox-request.ts +++ b/modules/companies/src/web/mailboxes/shared/api/mailbox-request.ts @@ -1,21 +1,17 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { MailboxUseInput } from "../../../../common/mailboxes"; import { MailboxesResponseSchema } from "../../../../common/mailboxes"; import { MailApiError } from "../../../mail-configurations/shared/api/mail-api"; -export async function mailboxRequest( - companyId: string, - values?: MailboxUseInput, - token?: string, - id?: string -) { - const response = await fetch( +export async function mailboxRequest(companyId: string, values?: MailboxUseInput, id?: string) { + const response = await csrfFetch( `/api/mailboxes/${companyId}${values ? "/uses" : ""}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), } ); diff --git a/modules/companies/src/web/mailboxes/shared/types/mailbox-relations-props.ts b/modules/companies/src/web/mailboxes/shared/types/mailbox-relations-props.ts index 378404d..49b6a25 100644 --- a/modules/companies/src/web/mailboxes/shared/types/mailbox-relations-props.ts +++ b/modules/companies/src/web/mailboxes/shared/types/mailbox-relations-props.ts @@ -2,6 +2,5 @@ import type { CompanyDTO } from "../../../../common"; export type MailboxRelationsProps = { company: CompanyDTO; - csrfToken: string; mailboxId: string; }; diff --git a/modules/companies/src/web/mailboxes/shared/ui/mailbox-form.tsx b/modules/companies/src/web/mailboxes/shared/ui/mailbox-form.tsx index ed44428..16720d9 100644 --- a/modules/companies/src/web/mailboxes/shared/ui/mailbox-form.tsx +++ b/modules/companies/src/web/mailboxes/shared/ui/mailbox-form.tsx @@ -6,14 +6,12 @@ import type { MailboxDTO } from "../../../../common/mailboxes"; /** El alta rápida de correo utiliza la misma ficha y persistencia que Recursos. */ export function MailboxForm({ company, - csrfToken, mailbox, onSaved, allowQuickCreate = true, initialAddress = "", }: { company: CompanyDTO; - csrfToken: string; mailbox?: MailboxDTO | undefined; onSaved: (id: string) => void; allowQuickCreate?: boolean; @@ -23,7 +21,6 @@ export function MailboxForm({ { if (!open) { diff --git a/modules/companies/src/web/people/detail/ui/person-details.tsx b/modules/companies/src/web/people/detail/ui/person-details.tsx index 27f202d..7f5243b 100644 --- a/modules/companies/src/web/people/detail/ui/person-details.tsx +++ b/modules/companies/src/web/people/detail/ui/person-details.tsx @@ -24,13 +24,11 @@ import { deletePerson } from "../../shared/api"; export function PersonDetails({ company, person, - csrfToken, onBack, onEdit, }: { company: CompanyDTO; person: PersonDTO; - csrfToken: string; onBack: () => void; onEdit: () => void; }) { @@ -121,7 +119,7 @@ export function PersonDetails({ } setDeleting(true); try { - await deletePerson(company.id, person.id, csrfToken); + await deletePerson(company.id, person.id); onBack(); } catch { window.alert(t("deletePersonError")); @@ -214,7 +212,7 @@ export function PersonDetails({ } setDeletingAssignment(item.id); try { - await deleteAssignment(company.id, person.id, item.id, csrfToken); + await deleteAssignment(company.id, person.id, item.id); window.location.reload(); } catch { window.alert(t("deleteAssignmentError")); @@ -243,7 +241,6 @@ export function PersonDetails({ { if (!open) { diff --git a/modules/companies/src/web/people/list/ui/people-section.tsx b/modules/companies/src/web/people/list/ui/people-section.tsx index f89794b..c5908f7 100644 --- a/modules/companies/src/web/people/list/ui/people-section.tsx +++ b/modules/companies/src/web/people/list/ui/people-section.tsx @@ -19,11 +19,9 @@ import { PersonAvatar, PersonEditorDialog } from "../../shared"; export function PeopleSection({ company, - csrfToken, initialId, }: { company: CompanyDTO; - csrfToken: string; initialId?: string | undefined; }) { const { t, i18n } = useTranslation("companies"); @@ -41,7 +39,6 @@ export function PeopleSection({ const personEditor = ( { if (!open) { setSelection(selected ? { mode: "detail", id: selected.id } : undefined); @@ -65,7 +62,6 @@ export function PeopleSection({ {personEditor} { setSelection(undefined); diff --git a/modules/companies/src/web/people/shared/api/save-person.api.ts b/modules/companies/src/web/people/shared/api/save-person.api.ts index 3eb2daf..90e565f 100644 --- a/modules/companies/src/web/people/shared/api/save-person.api.ts +++ b/modules/companies/src/web/people/shared/api/save-person.api.ts @@ -1,47 +1,38 @@ +import { csrfFetch } from "@metis/core/client"; + import { CompanyResponseSchema } from "../../../../common"; import { companyRequest } from "../../../companies/shared/api/company-request"; -export const savePerson = ( - companyId: string, - id: string | undefined, - body: unknown, - token: string -) => - companyRequest( - `/${companyId}/people${id ? `/${id}/update` : ""}`, - CompanyResponseSchema, - body, - token - ); +export const savePerson = (companyId: string, id: string | undefined, body: unknown) => + companyRequest(`/${companyId}/people${id ? `/${id}/update` : ""}`, CompanyResponseSchema, body); -export const deletePerson = (companyId: string, id: string, token: string) => - companyRequest(`/${companyId}/people/${id}/delete`, CompanyResponseSchema, {}, token); +export const deletePerson = (companyId: string, id: string) => + companyRequest(`/${companyId}/people/${id}/delete`, CompanyResponseSchema, {}); -export async function savePersonPhoto( - companyId: string, - personId: string, - file: File, - token: string -) { - const response = await fetch(`/api/companies/${companyId}/people/${personId}/photo`, { +export async function savePersonPhoto(companyId: string, personId: string, file: File) { + const response = await csrfFetch(`/api/companies/${companyId}/people/${personId}/photo`, { method: "POST", - headers: { "Content-Type": "application/octet-stream", "X-CSRF-Token": token }, + headers: { "Content-Type": "application/octet-stream" }, body: file, }); - if (!response.ok) throw new Error("PHOTO_UPLOAD_FAILED"); + if (!response.ok) { + throw new Error("PHOTO_UPLOAD_FAILED"); + } return response.json() as Promise<{ photoVersion: string }>; } -export async function removePersonPhoto(companyId: string, personId: string, token: string) { - const response = await fetch(`/api/companies/${companyId}/people/${personId}/photo/remove`, { +export async function removePersonPhoto(companyId: string, personId: string) { + const response = await csrfFetch(`/api/companies/${companyId}/people/${personId}/photo/remove`, { method: "POST", - headers: { "Content-Type": "application/json", "X-CSRF-Token": token }, + headers: { "Content-Type": "application/json" }, body: "{}", }); - if (!response.ok) throw new Error("PHOTO_REMOVE_FAILED"); + if (!response.ok) { + throw new Error("PHOTO_REMOVE_FAILED"); + } return response.json() as Promise<{ photoVersion: null }>; } diff --git a/modules/companies/src/web/people/shared/controllers/use-person-form-controller.ts b/modules/companies/src/web/people/shared/controllers/use-person-form-controller.ts index faea73c..3ffd39d 100644 --- a/modules/companies/src/web/people/shared/controllers/use-person-form-controller.ts +++ b/modules/companies/src/web/people/shared/controllers/use-person-form-controller.ts @@ -1,7 +1,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useForm } from "react-hook-form"; import { useState } from "react"; +import { useForm } from "react-hook-form"; import { accessKeys } from "../../../access/shared/queries"; import { companyKeys } from "../../../companies/shared/queries"; @@ -12,12 +12,7 @@ import { savePerson, savePersonPhoto } from "../api"; import { PersonFormSchema } from "../schemas"; import type { PersonFormProps, PersonFormValues } from "../types"; -export function usePersonFormController({ - person, - companyId, - csrfToken, - onSaved, -}: PersonFormProps) { +export function usePersonFormController({ person, companyId, onSaved }: PersonFormProps) { const client = useQueryClient(); const [photoFile, setPhotoFile] = useState(null); const form = useForm({ @@ -33,7 +28,7 @@ export function usePersonFormController({ }); const mutation = useMutation({ mutationFn: (values: PersonFormValues) => - savePerson(companyId, person?.id, buildPersonRequest(values), csrfToken), + savePerson(companyId, person?.id, buildPersonRequest(values)), onSuccess: async (response) => { await Promise.all([ client.invalidateQueries({ queryKey: accessKeys.scope(companyId) }), @@ -47,7 +42,9 @@ export function usePersonFormController({ : response.company.people.at(-1); if (saved) { - if (photoFile) await savePersonPhoto(companyId, saved.id, photoFile, csrfToken); + if (photoFile) { + await savePersonPhoto(companyId, saved.id, photoFile); + } onSaved(saved.id); } }, diff --git a/modules/companies/src/web/people/shared/types/person-form-props.ts b/modules/companies/src/web/people/shared/types/person-form-props.ts index 2d48066..8f9b5d9 100644 --- a/modules/companies/src/web/people/shared/types/person-form-props.ts +++ b/modules/companies/src/web/people/shared/types/person-form-props.ts @@ -8,7 +8,6 @@ export type PersonFormValues = z.infer; export type PersonFormProps = { person?: PersonDTO | undefined; companyId: string; - csrfToken: string; onSaved: (id: string) => void; requireActive?: boolean; }; diff --git a/modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx b/modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx index 489bc2e..7e16046 100644 --- a/modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx +++ b/modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx @@ -6,12 +6,10 @@ import { WorkProfileForm } from "../../../work-profiles/shared"; export function PrinterProfileCreateDialog({ company, - csrfToken, onSaved, onOpenChange, }: { company: CompanyDTO; - csrfToken: string; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; }) { @@ -26,12 +24,7 @@ export function PrinterProfileCreateDialog({ title={t("profiles.new")} wide > - + ); } diff --git a/modules/companies/src/web/printer-configurations/list/types/printer-configurations-page-props.ts b/modules/companies/src/web/printer-configurations/list/types/printer-configurations-page-props.ts index 5bd2dcb..c3d0fdc 100644 --- a/modules/companies/src/web/printer-configurations/list/types/printer-configurations-page-props.ts +++ b/modules/companies/src/web/printer-configurations/list/types/printer-configurations-page-props.ts @@ -2,6 +2,5 @@ import type { CompanyDTO } from "../../../../common"; export type PrinterConfigurationsPageProps = { company: CompanyDTO; - csrfToken: string; initialProfileId?: string; }; diff --git a/modules/companies/src/web/printer-configurations/list/ui/printer-configurations-page.tsx b/modules/companies/src/web/printer-configurations/list/ui/printer-configurations-page.tsx index 4f3bff6..7742052 100644 --- a/modules/companies/src/web/printer-configurations/list/ui/printer-configurations-page.tsx +++ b/modules/companies/src/web/printer-configurations/list/ui/printer-configurations-page.tsx @@ -16,7 +16,7 @@ import { usePrinterConfigurationsPageController } from "../controllers"; import type { PrinterConfigurationsPageProps } from "../types"; export function PrinterConfigurationsPage(props: PrinterConfigurationsPageProps) { - const { company, csrfToken, initialProfileId } = props; + const { company, initialProfileId } = props; const { editor, setEditor, @@ -41,7 +41,6 @@ export function PrinterConfigurationsPage(props: PrinterConfigurationsPageProps)
setDeleteId(undefined)} path={deleteId ? `/api/printer-configurations/${company.id}/${deleteId}/delete` : undefined} queryKey={["printer-configurations", company.id]} @@ -162,7 +161,6 @@ export function PrinterConfigurationsPage(props: PrinterConfigurationsPageProps) setEditor(undefined)} /> diff --git a/modules/companies/src/web/printer-configurations/shared/api/printer-api.ts b/modules/companies/src/web/printer-configurations/shared/api/printer-api.ts index d544bd5..7267d3d 100644 --- a/modules/companies/src/web/printer-configurations/shared/api/printer-api.ts +++ b/modules/companies/src/web/printer-configurations/shared/api/printer-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { PrinterConfigurationInput } from "../../../../common/printer-configurations"; @@ -14,18 +15,13 @@ export class PrinterApiError extends Error { } } -async function request( - companyId: string, - values?: PrinterConfigurationInput, - token?: string, - id?: string -) { - const response = await fetch( +async function request(companyId: string, values?: PrinterConfigurationInput, id?: string) { + const response = await csrfFetch( `/api/printer-configurations/${companyId}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), } ); @@ -44,7 +40,7 @@ export const printerApi = { async get(companyId: string) { return PrinterConfigurationsResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: PrinterConfigurationInput, token: string, id?: string) { - return PrinterConfigurationResponseSchema.parse(await request(companyId, values, token, id)); + async save(companyId: string, values: PrinterConfigurationInput, id?: string) { + return PrinterConfigurationResponseSchema.parse(await request(companyId, values, id)); }, }; diff --git a/modules/companies/src/web/printer-configurations/shared/controllers/use-printer-configuration-form-controller.ts b/modules/companies/src/web/printer-configurations/shared/controllers/use-printer-configuration-form-controller.ts index 6c96ad7..1ecb7a3 100644 --- a/modules/companies/src/web/printer-configurations/shared/controllers/use-printer-configuration-form-controller.ts +++ b/modules/companies/src/web/printer-configurations/shared/controllers/use-printer-configuration-form-controller.ts @@ -27,7 +27,6 @@ function availablePrinterStates( export function usePrinterConfigurationFormController({ company, - csrfToken, configuration, initialProfileId = "", onSaved, @@ -62,7 +61,7 @@ export function usePrinterConfigurationFormController({ const ready = [profiles.data, inventory.data].every(Boolean); const mutation = useMutation({ mutationFn: (input: PrinterConfigurationInput) => - printerApi.save(company.id, input, csrfToken, configuration?.id), + printerApi.save(company.id, input, configuration?.id), onSuccess: async () => { await client.invalidateQueries({ queryKey: printerKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/printer-configurations/shared/printer.test.tsx b/modules/companies/src/web/printer-configurations/shared/printer.test.tsx index 720f292..0966428 100644 --- a/modules/companies/src/web/printer-configurations/shared/printer.test.tsx +++ b/modules/companies/src/web/printer-configurations/shared/printer.test.tsx @@ -60,10 +60,7 @@ test("impresoras: listado sin formularios, alta en cabecera y traducciones", asy ["es", "Impresoras del perfil", "Configurar impresora"], ["en", "Profile printers", "Configure printer"], ]) { - const html = await render( - language ?? "es", - - ); + const html = await render(language ?? "es", ); assert.ok(html.includes(title ?? "")); assert.ok(html.includes(action ?? "")); @@ -75,7 +72,7 @@ test("impresoras: selectores con alta rápida y ayuda inferior antes de guardar" for (const language of ["es", "en"]) { const html = await render( language, - undefined} /> + undefined} /> ); const text = language === "es" ? companiesSpanish : companiesEnglish; diff --git a/modules/companies/src/web/printer-configurations/shared/types/printer-configuration-form-props.ts b/modules/companies/src/web/printer-configurations/shared/types/printer-configuration-form-props.ts index aedc2bd..424768a 100644 --- a/modules/companies/src/web/printer-configurations/shared/types/printer-configuration-form-props.ts +++ b/modules/companies/src/web/printer-configurations/shared/types/printer-configuration-form-props.ts @@ -3,7 +3,6 @@ import type { PrinterConfigurationDTO } from "../../../../common/printer-configu export type PrinterConfigurationFormProps = { company: CompanyDTO; - csrfToken: string; configuration?: PrinterConfigurationDTO | undefined; initialProfileId?: string; onSaved: () => void; diff --git a/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx b/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx index 0694170..10dd588 100644 --- a/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx +++ b/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx @@ -9,13 +9,11 @@ import { PrinterConfigurationForm } from "./printer-configuration-form"; export function PrinterConfigurationEditorDialog({ configuration, company, - csrfToken, initialProfileId, onClose, }: { configuration: ComponentProps["configuration"]; company: CompanyDTO; - csrfToken: string; initialProfileId: ComponentProps["initialProfileId"]; onClose: () => void; }) { @@ -37,7 +35,6 @@ export function PrinterConfigurationEditorDialog({ onClose()} /> diff --git a/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-form.tsx b/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-form.tsx index 10ca759..5348db7 100644 --- a/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-form.tsx +++ b/modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-form.tsx @@ -25,7 +25,7 @@ function profileOptions(profiles: readonly WorkProfileDTO[], selected: string) { } export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) { - const { company, csrfToken, configuration } = props; + const { company, configuration } = props; const { quick, setQuick, @@ -173,7 +173,6 @@ export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) { {quick === "profile" && ( { if (!open) { setQuick(undefined); @@ -185,7 +184,6 @@ export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) { {quick === "printer" && ( setQuick(undefined)} diff --git a/modules/companies/src/web/process-templates/list/types/index.ts b/modules/companies/src/web/process-templates/list/types/index.ts deleted file mode 100644 index d48266b..0000000 --- a/modules/companies/src/web/process-templates/list/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { ProcessTemplatesPageProps } from "./process-templates-page-props"; diff --git a/modules/companies/src/web/process-templates/list/types/process-templates-page-props.ts b/modules/companies/src/web/process-templates/list/types/process-templates-page-props.ts deleted file mode 100644 index 6bce299..0000000 --- a/modules/companies/src/web/process-templates/list/types/process-templates-page-props.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type ProcessTemplatesPageProps = { - csrfToken: string; -}; diff --git a/modules/companies/src/web/process-templates/list/ui/process-templates-page.tsx b/modules/companies/src/web/process-templates/list/ui/process-templates-page.tsx index 9c0c593..70f4adf 100644 --- a/modules/companies/src/web/process-templates/list/ui/process-templates-page.tsx +++ b/modules/companies/src/web/process-templates/list/ui/process-templates-page.tsx @@ -11,10 +11,8 @@ import { useTranslation } from "react-i18next"; import { ProcessFeedback } from "../../../processes/shared"; import { ProcessTemplateEditorDialog } from "../../shared"; import { useProcessTemplatesPageController } from "../controllers"; -import type { ProcessTemplatesPageProps } from "../types"; -export function ProcessTemplatesPage(props: ProcessTemplatesPageProps) { - const { csrfToken } = props; +export function ProcessTemplatesPage() { const { query, editor, setEditor, copy, setCopy, templates, selected, copied, close } = useProcessTemplatesPageController(); const { t, i18n } = useTranslation("companies"); @@ -144,7 +142,6 @@ export function ProcessTemplatesPage(props: ProcessTemplatesPageProps) { { if (!open) { diff --git a/modules/companies/src/web/process-templates/shared/api/save-process-template.api.ts b/modules/companies/src/web/process-templates/shared/api/save-process-template.api.ts index 81fa916..4de418a 100644 --- a/modules/companies/src/web/process-templates/shared/api/save-process-template.api.ts +++ b/modules/companies/src/web/process-templates/shared/api/save-process-template.api.ts @@ -2,12 +2,8 @@ import type { ProcessTemplateInput } from "../../../../common/processes"; import { ProcessTemplateResponseSchema } from "../../../../common/processes"; import { processRequest } from "../../../processes/shared/api/process-request"; -export async function saveProcessTemplate( - values: ProcessTemplateInput, - token: string, - id?: string -) { +export async function saveProcessTemplate(values: ProcessTemplateInput, id?: string) { return ProcessTemplateResponseSchema.parse( - await processRequest(`templates${id ? `/${id}/update` : ""}`, { values }, token) + await processRequest(`templates${id ? `/${id}/update` : ""}`, { values }) ); } diff --git a/modules/companies/src/web/process-templates/shared/controllers/use-process-template-form-controller.ts b/modules/companies/src/web/process-templates/shared/controllers/use-process-template-form-controller.ts index 29b2e02..de56780 100644 --- a/modules/companies/src/web/process-templates/shared/controllers/use-process-template-form-controller.ts +++ b/modules/companies/src/web/process-templates/shared/controllers/use-process-template-form-controller.ts @@ -12,7 +12,6 @@ import type { ProcessTemplateFormProps, Values } from "../types"; export function useProcessTemplateFormController({ template, initialValues, - csrfToken, onSaved, }: ProcessTemplateFormProps) { const client = useQueryClient(); @@ -37,8 +36,7 @@ export function useProcessTemplateFormController({ }, }); const mutation = useMutation({ - mutationFn: (v: Values) => - saveProcessTemplate(buildProcessTemplateInput(v), csrfToken, template?.id), + mutationFn: (v: Values) => saveProcessTemplate(buildProcessTemplateInput(v), template?.id), onSuccess: async (response) => { await client.invalidateQueries({ queryKey: processKeys.catalog() }); if (mounted.current) { diff --git a/modules/companies/src/web/process-templates/shared/types/process-template-form-props.ts b/modules/companies/src/web/process-templates/shared/types/process-template-form-props.ts index d97f666..06a9220 100644 --- a/modules/companies/src/web/process-templates/shared/types/process-template-form-props.ts +++ b/modules/companies/src/web/process-templates/shared/types/process-template-form-props.ts @@ -8,6 +8,5 @@ export type Values = z.infer; export type ProcessTemplateFormProps = { template?: ProcessTemplateDTO | undefined; initialValues?: ProcessTemplateInput | undefined; - csrfToken: string; onSaved: (id: string) => void; }; diff --git a/modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx b/modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx index ce82c2e..b7a03a2 100644 --- a/modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx +++ b/modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx @@ -9,7 +9,6 @@ export function ProcessTemplateEditorDialog({ copy, selected, copied, - csrfToken, onSaved, onOpenChange, }: { @@ -17,7 +16,6 @@ export function ProcessTemplateEditorDialog({ copy: string | undefined; selected: ComponentProps["template"]; copied: ComponentProps["template"]; - csrfToken: string; onSaved: () => void; onOpenChange: (open: boolean) => void; }) { @@ -34,7 +32,6 @@ export function ProcessTemplateEditorDialog({ > {editor && ( void; onOpenChange: (open: boolean) => void; }) { @@ -29,9 +27,9 @@ export function ProcessQuickCreateDialog({ wide > {quick === "person" ? ( - + ) : ( - + )} ); diff --git a/modules/companies/src/web/processes/detail/controllers/use-process-detail-controller.ts b/modules/companies/src/web/processes/detail/controllers/use-process-detail-controller.ts index 06f5a5a..4eb5f8b 100644 --- a/modules/companies/src/web/processes/detail/controllers/use-process-detail-controller.ts +++ b/modules/companies/src/web/processes/detail/controllers/use-process-detail-controller.ts @@ -5,12 +5,12 @@ import { refreshProcess } from "../../refresh/api"; import { processKeys } from "../../shared/queries"; import type { ProcessDetailProps } from "../types"; -export function useProcessDetailController({ process, csrfToken }: ProcessDetailProps) { +export function useProcessDetailController({ process }: ProcessDetailProps) { const [edit, setEdit] = useState(false); const client = useQueryClient(); const refresh = useMutation({ mutationFn: () => - refreshProcess(process.companyId, process.id, process.history.at(-1)?.id ?? "", csrfToken), + refreshProcess(process.companyId, process.id, process.history.at(-1)?.id ?? ""), onSuccess: async () => { await client.invalidateQueries({ queryKey: processKeys.company(process.companyId) }); }, diff --git a/modules/companies/src/web/processes/detail/types/process-detail-props.ts b/modules/companies/src/web/processes/detail/types/process-detail-props.ts index ee26dab..d0df9c1 100644 --- a/modules/companies/src/web/processes/detail/types/process-detail-props.ts +++ b/modules/companies/src/web/processes/detail/types/process-detail-props.ts @@ -3,7 +3,6 @@ import type { PersonProcessDTO } from "../../../../common/processes"; export type ProcessDetailProps = { process: PersonProcessDTO; person: string; - csrfToken: string; onDelete?: (id: string) => void; onDeleted?: () => void; }; diff --git a/modules/companies/src/web/processes/detail/ui/process-detail.tsx b/modules/companies/src/web/processes/detail/ui/process-detail.tsx index e056983..edc3e24 100644 --- a/modules/companies/src/web/processes/detail/ui/process-detail.tsx +++ b/modules/companies/src/web/processes/detail/ui/process-detail.tsx @@ -1,6 +1,6 @@ import { Button } from "@repo/shadcn-ui/components/button"; -import { useState } from "react"; import { Card, CardContent } from "@repo/shadcn-ui/components/card"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { ProcessFeedback } from "../../shared"; @@ -10,7 +10,7 @@ import { useProcessDetailController } from "../controllers"; import type { ProcessDetailProps } from "../types"; export function ProcessDetail(props: ProcessDetailProps) { - const { process, person, csrfToken, onDelete, onDeleted } = props; + const { process, person, onDelete, onDeleted } = props; const { edit, setEdit, refresh, open, resolved } = useProcessDetailController(props); const { t, i18n } = useTranslation("companies"); const [deleting, setDeleting] = useState(false); @@ -44,14 +44,15 @@ export function ProcessDetail(props: ProcessDetailProps) { @@ -130,7 +131,6 @@ export function ProcessDetail(props: ProcessDetailProps) { setEdit(false)} open={edit} diff --git a/modules/companies/src/web/processes/list/api/get-processes.api.ts b/modules/companies/src/web/processes/list/api/get-processes.api.ts index e9e7a4a..0ca5b67 100644 --- a/modules/companies/src/web/processes/list/api/get-processes.api.ts +++ b/modules/companies/src/web/processes/list/api/get-processes.api.ts @@ -5,6 +5,6 @@ export async function getProcesses(company: string) { return ProcessesResponseSchema.parse(await processRequest(company)); } -export async function deleteProcess(company: string, id: string, token: string) { - await processRequest(`${company}/${id}/delete`, {}, token); +export async function deleteProcess(company: string, id: string) { + await processRequest(`${company}/${id}/delete`, {}); } diff --git a/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts b/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts index 48125ef..8601361 100644 --- a/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts +++ b/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts @@ -38,8 +38,7 @@ export function useProcessesPageController({ company }: ProcessesPageProps) { }); const remove = useMutation({ - mutationFn: ({ id, token }: { id: string; token: string }) => - deleteProcess(company.id, id, token), + mutationFn: (id: string) => deleteProcess(company.id, id), onSuccess: async () => { await client.invalidateQueries({ queryKey: processKeys.company(company.id) }); setSelected(undefined); diff --git a/modules/companies/src/web/processes/list/types/processes-page-props.ts b/modules/companies/src/web/processes/list/types/processes-page-props.ts index 1609a65..6991dc6 100644 --- a/modules/companies/src/web/processes/list/types/processes-page-props.ts +++ b/modules/companies/src/web/processes/list/types/processes-page-props.ts @@ -2,5 +2,4 @@ import type { CompanyDTO } from "../../../../common"; export type ProcessesPageProps = { company: CompanyDTO; - csrfToken: string; }; diff --git a/modules/companies/src/web/processes/list/ui/processes-page.tsx b/modules/companies/src/web/processes/list/ui/processes-page.tsx index 2b0721a..f0801a7 100644 --- a/modules/companies/src/web/processes/list/ui/processes-page.tsx +++ b/modules/companies/src/web/processes/list/ui/processes-page.tsx @@ -17,7 +17,7 @@ import { useProcessesPageController } from "../controllers"; import type { ProcessesPageProps } from "../types"; export function ProcessesPage(props: ProcessesPageProps) { - const { company, csrfToken } = props; + const { company } = props; const { create, setCreate, @@ -66,11 +66,10 @@ export function ProcessesPage(props: ProcessesPageProps) { )} {current && !query.isError ? ( remove.mutate(id)} + onDeleted={() => setSelected(undefined)} person={person(current.values.personId)} process={current} - onDeleted={() => setSelected(undefined)} - onDelete={(id) => remove.mutate({ id, token: csrfToken })} /> ) : ( !selected && ( @@ -163,7 +162,7 @@ export function ProcessesPage(props: ProcessesPageProps) { if ( window.confirm(t("processes.deleteConfirm", { name: p.values.name })) ) { - remove.mutate({ id: p.id, token: csrfToken }); + remove.mutate(p.id); } }} > @@ -200,7 +199,6 @@ export function ProcessesPage(props: ProcessesPageProps) { )} { setCreate(false); diff --git a/modules/companies/src/web/processes/refresh/api/refresh-process.api.ts b/modules/companies/src/web/processes/refresh/api/refresh-process.api.ts index a83a274..0eb130a 100644 --- a/modules/companies/src/web/processes/refresh/api/refresh-process.api.ts +++ b/modules/companies/src/web/processes/refresh/api/refresh-process.api.ts @@ -1,13 +1,8 @@ import { ProcessResponseSchema } from "../../../../common/processes"; import { processRequest } from "../../shared/api/process-request"; -export async function refreshProcess( - company: string, - id: string, - revisionId: string, - token: string -) { +export async function refreshProcess(company: string, id: string, revisionId: string) { return ProcessResponseSchema.parse( - await processRequest(`${company}/${id}/refresh`, { revisionId }, token) + await processRequest(`${company}/${id}/refresh`, { revisionId }) ); } diff --git a/modules/companies/src/web/processes/shared/api/process-request.ts b/modules/companies/src/web/processes/shared/api/process-request.ts index 2f1f5ce..810f41f 100644 --- a/modules/companies/src/web/processes/shared/api/process-request.ts +++ b/modules/companies/src/web/processes/shared/api/process-request.ts @@ -1,13 +1,13 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import { ProcessApiError } from "./process-api-error"; -export async function processRequest(path: string, body?: unknown, token?: string) { - const response = await fetch(`/api/processes/${path}`, { +export async function processRequest(path: string, body?: unknown) { + const response = await csrfFetch(`/api/processes/${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined ? {} : { "content-type": "application/json", "x-csrf-token": token ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const data: unknown = response.status === 204 ? undefined : await response.json(); diff --git a/modules/companies/src/web/processes/shared/processes.test.tsx b/modules/companies/src/web/processes/shared/processes.test.tsx index e62354d..e14fe2a 100644 --- a/modules/companies/src/web/processes/shared/processes.test.tsx +++ b/modules/companies/src/web/processes/shared/processes.test.tsx @@ -55,11 +55,11 @@ async function render(language: string, node: ReactNode) { test("procesos: listados es/en sin formularios junto a los datos", async () => { for (const language of ["es", "en"]) { const text = language === "es" ? companiesSpanish : companiesEnglish; - const html = await render(language, ); + const html = await render(language, ); assert.ok(html.includes(text.processes.new) && html.includes(text.processes.templates)); assert.ok(!html.includes("); + const templates = await render(language, ); assert.ok(templates.includes(text.processes.globalTemplates)); assert.ok(!templates.includes(" { const text = language === "es" ? companiesSpanish : companiesEnglish; const html = await render( language, - undefined} /> + undefined} /> ); assert.ok(html.includes(text.newPerson) && html.includes(text.processes.newTemplate)); @@ -81,10 +81,7 @@ test("procesos: alta rápida y ayuda inferior antes del pie", async () => { test("plantillas: instrucciones, desactivación y ayuda global es/en sin datos de compañía", async () => { for (const language of ["es", "en"]) { const text = language === "es" ? companiesSpanish : companiesEnglish; - const html = await render( - language, - undefined} /> - ); + const html = await render(language, undefined} />); assert.ok(html.includes(text.processes.instructionsHelp)); assert.ok(html.includes(text.processes.templateHelp.scope)); @@ -135,10 +132,7 @@ test("seguimiento: origen personal, revisión actual y edición sin afirmar reti for (const language of ["es", "en"]) { const text = language === "es" ? companiesSpanish : companiesEnglish; - const html = await render( - language, - - ); + const html = await render(language, ); assert.ok(html.includes(text.remote.personal_mobile)); assert.ok(html.includes(text.processes.stillRecorded)); @@ -148,7 +142,7 @@ test("seguimiento: origen personal, revisión actual y edición sin afirmar reti assert.ok(!html.includes(" undefined} process={process} /> + undefined} process={process} /> ); assert.ok(editor.includes(text.processes.taskNotes)); @@ -157,7 +151,6 @@ test("seguimiento: origen personal, revisión actual y edición sin afirmar reti const closed = await render( language, diff --git a/modules/companies/src/web/processes/start/api/start-process.api.ts b/modules/companies/src/web/processes/start/api/start-process.api.ts index f362567..d6de362 100644 --- a/modules/companies/src/web/processes/start/api/start-process.api.ts +++ b/modules/companies/src/web/processes/start/api/start-process.api.ts @@ -2,6 +2,6 @@ import type { StartProcessInput } from "../../../../common/processes"; import { ProcessResponseSchema } from "../../../../common/processes"; import { processRequest } from "../../shared/api/process-request"; -export async function startProcess(company: string, values: StartProcessInput, token: string) { - return ProcessResponseSchema.parse(await processRequest(company, { values }, token)); +export async function startProcess(company: string, values: StartProcessInput) { + return ProcessResponseSchema.parse(await processRequest(company, { values })); } diff --git a/modules/companies/src/web/processes/start/controllers/use-process-start-form-controller.ts b/modules/companies/src/web/processes/start/controllers/use-process-start-form-controller.ts index 9d7b8df..de8fa3f 100644 --- a/modules/companies/src/web/processes/start/controllers/use-process-start-form-controller.ts +++ b/modules/companies/src/web/processes/start/controllers/use-process-start-form-controller.ts @@ -10,11 +10,7 @@ import { processKeys } from "../../shared/queries"; import { getProcessReview, startProcess } from "../api"; import type { ProcessStartFormProps } from "../types"; -export function useProcessStartFormController({ - company, - csrfToken, - onSaved, -}: ProcessStartFormProps) { +export function useProcessStartFormController({ company, onSaved }: ProcessStartFormProps) { const client = useQueryClient(); const [quick, setQuick] = useState<"person" | "template">(); const mounted = useRef(true); @@ -39,7 +35,7 @@ export function useProcessStartFormController({ enabled: Boolean(values.personId), }); const mutation = useMutation({ - mutationFn: (input: StartProcessInput) => startProcess(company.id, input, csrfToken), + mutationFn: (input: StartProcessInput) => startProcess(company.id, input), onSuccess: async (response) => { await client.invalidateQueries({ queryKey: processKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/processes/start/types/process-start-form-props.ts b/modules/companies/src/web/processes/start/types/process-start-form-props.ts index 27ea6d7..abb6fb5 100644 --- a/modules/companies/src/web/processes/start/types/process-start-form-props.ts +++ b/modules/companies/src/web/processes/start/types/process-start-form-props.ts @@ -2,6 +2,5 @@ import type { CompanyDTO } from "../../../../common"; export type ProcessStartFormProps = { company: CompanyDTO; - csrfToken: string; onSaved: (id: string) => void; }; diff --git a/modules/companies/src/web/processes/start/ui/process-start-dialog.tsx b/modules/companies/src/web/processes/start/ui/process-start-dialog.tsx index a4a6d65..6e3aa07 100644 --- a/modules/companies/src/web/processes/start/ui/process-start-dialog.tsx +++ b/modules/companies/src/web/processes/start/ui/process-start-dialog.tsx @@ -9,13 +9,11 @@ import { ProcessStartForm } from "./process-start-form"; export function ProcessStartDialog({ open, company, - csrfToken, onSaved, onOpenChange, }: { open: boolean; company: CompanyDTO; - csrfToken: string; onSaved: ComponentProps["onSaved"]; onOpenChange: (open: boolean) => void; }) { @@ -30,7 +28,7 @@ export function ProcessStartDialog({ title={t("processes.new")} wide > - {open && } + {open && } ); } diff --git a/modules/companies/src/web/processes/start/ui/process-start-form.tsx b/modules/companies/src/web/processes/start/ui/process-start-form.tsx index 1d1ea0c..ab07186 100644 --- a/modules/companies/src/web/processes/start/ui/process-start-form.tsx +++ b/modules/companies/src/web/processes/start/ui/process-start-form.tsx @@ -13,7 +13,7 @@ import { useProcessStartFormController } from "../controllers"; import type { ProcessStartFormProps } from "../types"; export function ProcessStartForm(props: ProcessStartFormProps) { - const { company, csrfToken } = props; + const { company } = props; const { quick, setQuick, query, form, values, template, review, mutation, onCreated, submit } = useProcessStartFormController(props); const { t } = useTranslation("companies"); @@ -108,7 +108,6 @@ export function ProcessStartForm(props: ProcessStartFormProps) { {quick && ( { if (!open) { diff --git a/modules/companies/src/web/processes/update/api/update-process.api.ts b/modules/companies/src/web/processes/update/api/update-process.api.ts index 8b6ccc6..b3a82f4 100644 --- a/modules/companies/src/web/processes/update/api/update-process.api.ts +++ b/modules/companies/src/web/processes/update/api/update-process.api.ts @@ -2,13 +2,6 @@ import type { UpdateProcessInput } from "../../../../common/processes"; import { ProcessResponseSchema } from "../../../../common/processes"; import { processRequest } from "../../shared/api/process-request"; -export async function updateProcess( - company: string, - id: string, - values: UpdateProcessInput, - token: string -) { - return ProcessResponseSchema.parse( - await processRequest(`${company}/${id}/update`, { values }, token) - ); +export async function updateProcess(company: string, id: string, values: UpdateProcessInput) { + return ProcessResponseSchema.parse(await processRequest(`${company}/${id}/update`, { values })); } diff --git a/modules/companies/src/web/processes/update/controllers/use-process-edit-form-controller.ts b/modules/companies/src/web/processes/update/controllers/use-process-edit-form-controller.ts index 6e92dd8..407f3e6 100644 --- a/modules/companies/src/web/processes/update/controllers/use-process-edit-form-controller.ts +++ b/modules/companies/src/web/processes/update/controllers/use-process-edit-form-controller.ts @@ -9,11 +9,7 @@ import { processKeys } from "../../shared/queries"; import { updateProcess } from "../api"; import type { ProcessEditFormProps } from "../types"; -export function useProcessEditFormController({ - process, - csrfToken, - onSaved, -}: ProcessEditFormProps) { +export function useProcessEditFormController({ process, onSaved }: ProcessEditFormProps) { const client = useQueryClient(); const mounted = useRef(true); @@ -39,8 +35,7 @@ export function useProcessEditFormController({ }, }); const mutation = useMutation({ - mutationFn: (input: UpdateProcessInput) => - updateProcess(process.companyId, process.id, input, csrfToken), + mutationFn: (input: UpdateProcessInput) => updateProcess(process.companyId, process.id, input), onSuccess: async () => { await client.invalidateQueries({ queryKey: processKeys.company(process.companyId) }); if (mounted.current) { diff --git a/modules/companies/src/web/processes/update/types/process-edit-form-props.ts b/modules/companies/src/web/processes/update/types/process-edit-form-props.ts index aba5844..f303f36 100644 --- a/modules/companies/src/web/processes/update/types/process-edit-form-props.ts +++ b/modules/companies/src/web/processes/update/types/process-edit-form-props.ts @@ -2,6 +2,5 @@ import type { PersonProcessDTO } from "../../../../common/processes"; export type ProcessEditFormProps = { process: PersonProcessDTO; - csrfToken: string; onSaved: () => void; }; diff --git a/modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx b/modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx index d345bd0..2449111 100644 --- a/modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx +++ b/modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx @@ -7,13 +7,11 @@ import { ProcessEditForm } from "./process-edit-form"; export function ProcessEditDialog({ open, process, - csrfToken, onSaved, onOpenChange, }: { open: boolean; process: ComponentProps["process"]; - csrfToken: string; onSaved: () => void; onOpenChange: (open: boolean) => void; }) { @@ -28,7 +26,7 @@ export function ProcessEditDialog({ title={t("processes.update")} wide > - {open && } + {open && } ); } diff --git a/modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx b/modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx index 2db42bb..8b11337 100644 --- a/modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx +++ b/modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx @@ -11,14 +11,12 @@ export type RemoteQuickKind = "person" | "source" | "target" | "resource" | "sit export function RemoteQuickEditor({ kind, company, - csrfToken, personId, onSaved, onClose, }: { kind: RemoteQuickKind; company: CompanyDTO; - csrfToken: string; personId: string; onSaved: (id: string) => void; onClose: () => void; @@ -29,7 +27,6 @@ export function RemoteQuickEditor({ return ( {kind === "site" ? ( - + ) : ( - + )} ); diff --git a/modules/companies/src/web/remote-connections/list/types/remote-connections-page-props.ts b/modules/companies/src/web/remote-connections/list/types/remote-connections-page-props.ts index c6b87ae..58311ab 100644 --- a/modules/companies/src/web/remote-connections/list/types/remote-connections-page-props.ts +++ b/modules/companies/src/web/remote-connections/list/types/remote-connections-page-props.ts @@ -2,6 +2,5 @@ import type { CompanyDTO } from "../../../../common"; export type RemoteConnectionsPageProps = { company: CompanyDTO; - csrfToken: string; initialPersonId?: string | undefined; }; diff --git a/modules/companies/src/web/remote-connections/list/ui/remote-connections-page.tsx b/modules/companies/src/web/remote-connections/list/ui/remote-connections-page.tsx index 5dbccda..e2e75f4 100644 --- a/modules/companies/src/web/remote-connections/list/ui/remote-connections-page.tsx +++ b/modules/companies/src/web/remote-connections/list/ui/remote-connections-page.tsx @@ -15,7 +15,7 @@ import { useRemoteConnectionsPageController } from "../controllers"; import type { RemoteConnectionsPageProps } from "../types"; export function RemoteConnectionsPage(props: RemoteConnectionsPageProps) { - const { company, csrfToken, initialPersonId } = props; + const { company, initialPersonId } = props; const { editor, setEditor, @@ -160,7 +160,6 @@ export function RemoteConnectionsPage(props: RemoteConnectionsPageProps) { setEditor(undefined)} /> diff --git a/modules/companies/src/web/remote-connections/shared/api/remote-api.ts b/modules/companies/src/web/remote-connections/shared/api/remote-api.ts index abf3ba6..239b1d2 100644 --- a/modules/companies/src/web/remote-connections/shared/api/remote-api.ts +++ b/modules/companies/src/web/remote-connections/shared/api/remote-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { RemoteConnectionInput } from "../../../../common/remote-connections"; @@ -14,18 +15,16 @@ export class RemoteApiError extends Error { } } -async function request( - companyId: string, - values?: RemoteConnectionInput, - token?: string, - id?: string -) { - const response = await fetch(`/api/remote-connections/${companyId}${id ? `/${id}/update` : ""}`, { - method: values ? "POST" : "GET", - credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, - ...(values ? { body: JSON.stringify({ values }) } : {}), - }); +async function request(companyId: string, values?: RemoteConnectionInput, id?: string) { + const response = await csrfFetch( + `/api/remote-connections/${companyId}${id ? `/${id}/update` : ""}`, + { + method: values ? "POST" : "GET", + credentials: "same-origin", + headers: values ? { "content-type": "application/json" } : {}, + ...(values ? { body: JSON.stringify({ values }) } : {}), + } + ); const body: unknown = await response.json(); if (!response.ok) { @@ -41,7 +40,7 @@ export const remoteApi = { async get(companyId: string) { return RemoteConnectionsResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: RemoteConnectionInput, token: string, id?: string) { - return RemoteConnectionResponseSchema.parse(await request(companyId, values, token, id)); + async save(companyId: string, values: RemoteConnectionInput, id?: string) { + return RemoteConnectionResponseSchema.parse(await request(companyId, values, id)); }, }; diff --git a/modules/companies/src/web/remote-connections/shared/controllers/use-remote-connection-form-controller.ts b/modules/companies/src/web/remote-connections/shared/controllers/use-remote-connection-form-controller.ts index d79be8b..4c4fc63 100644 --- a/modules/companies/src/web/remote-connections/shared/controllers/use-remote-connection-form-controller.ts +++ b/modules/companies/src/web/remote-connections/shared/controllers/use-remote-connection-form-controller.ts @@ -27,7 +27,6 @@ const QUICK_FIELDS = { export function useRemoteConnectionFormController({ company, - csrfToken, connection, initialPersonId, onSaved, @@ -75,7 +74,7 @@ export function useRemoteConnectionFormController({ : ["pending", "configured"]; const mutation = useMutation({ mutationFn: (values: RemoteConnectionInput) => - remoteApi.save(company.id, values, csrfToken, connection?.id), + remoteApi.save(company.id, values, connection?.id), onSuccess: async () => { await client.invalidateQueries({ queryKey: remoteKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/remote-connections/shared/remote.test.tsx b/modules/companies/src/web/remote-connections/shared/remote.test.tsx index 1e15583..00abbc0 100644 --- a/modules/companies/src/web/remote-connections/shared/remote.test.tsx +++ b/modules/companies/src/web/remote-connections/shared/remote.test.tsx @@ -59,10 +59,7 @@ async function render(language: string, node: ReactNode) { test("remotos: consulta sin formularios y traducciones", async () => { for (const language of ["es", "en"]) { - const html = await render( - language, - - ); + const html = await render(language, ); const text = language === "es" ? companiesSpanish : companiesEnglish; assert.ok(html.includes(text.remote.title)); @@ -74,7 +71,7 @@ test("remotos: formulario, altas rápidas y ayuda antes del pie", async () => { for (const language of ["es", "en"]) { const html = await render( language, - undefined} /> + undefined} /> ); const text = language === "es" ? companiesSpanish : companiesEnglish; diff --git a/modules/companies/src/web/remote-connections/shared/types/remote-connection-form-props.ts b/modules/companies/src/web/remote-connections/shared/types/remote-connection-form-props.ts index 69366d9..c260538 100644 --- a/modules/companies/src/web/remote-connections/shared/types/remote-connection-form-props.ts +++ b/modules/companies/src/web/remote-connections/shared/types/remote-connection-form-props.ts @@ -3,7 +3,6 @@ import type { RemoteConnectionDTO } from "../../../../common/remote-connections" export type RemoteConnectionFormProps = { company: CompanyDTO; - csrfToken: string; connection?: RemoteConnectionDTO | undefined; initialPersonId?: string | undefined; onSaved: () => void; diff --git a/modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx b/modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx index 621e926..a845b4a 100644 --- a/modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx +++ b/modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx @@ -9,13 +9,11 @@ import { RemoteConnectionForm } from "./remote-connection-form"; export function RemoteConnectionEditorDialog({ connection, company, - csrfToken, initialPersonId, onClose, }: { connection: ComponentProps["connection"]; company: CompanyDTO; - csrfToken: string; initialPersonId: ComponentProps["initialPersonId"]; onClose: () => void; }) { @@ -37,7 +35,6 @@ export function RemoteConnectionEditorDialog({ onClose()} /> diff --git a/modules/companies/src/web/remote-connections/shared/ui/remote-connection-form.tsx b/modules/companies/src/web/remote-connections/shared/ui/remote-connection-form.tsx index 088a8cc..ba5073d 100644 --- a/modules/companies/src/web/remote-connections/shared/ui/remote-connection-form.tsx +++ b/modules/companies/src/web/remote-connections/shared/ui/remote-connection-form.tsx @@ -18,7 +18,7 @@ import { RemoteEndpointFields } from "./remote-endpoint-fields"; import { RemoteFeedback } from "./remote-feedback"; export function RemoteConnectionForm(props: RemoteConnectionFormProps) { - const { company, csrfToken } = props; + const { company } = props; const { quick, setQuick, @@ -148,7 +148,6 @@ export function RemoteConnectionForm(props: RemoteConnectionFormProps) { {quick && ( setQuick(undefined)} onSaved={quickCreated} diff --git a/modules/companies/src/web/shared/api/delete-record.api.ts b/modules/companies/src/web/shared/api/delete-record.api.ts index fa88214..5be1e42 100644 --- a/modules/companies/src/web/shared/api/delete-record.api.ts +++ b/modules/companies/src/web/shared/api/delete-record.api.ts @@ -1,10 +1,11 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; -export async function deleteRecord(path: string, token: string) { - const response = await fetch(path, { +export async function deleteRecord(path: string) { + const response = await csrfFetch(path, { method: "POST", credentials: "same-origin", - headers: { "content-type": "application/json", "x-csrf-token": token }, + headers: { "content-type": "application/json" }, body: "{}", }); diff --git a/modules/companies/src/web/shared/client-contracts.test.ts b/modules/companies/src/web/shared/client-contracts.test.ts index 2f9dcf9..0a0943d 100644 --- a/modules/companies/src/web/shared/client-contracts.test.ts +++ b/modules/companies/src/web/shared/client-contracts.test.ts @@ -1,6 +1,8 @@ import assert from "node:assert/strict"; import { test } from "node:test"; +import { configureCsrfTokenReader } from "@metis/core/client"; + import { COMPANY_LOGO_LIMITS, COMPANY_LOGO_TYPES } from "../../common"; import { getAccess } from "../access/list/api"; import { saveAccess } from "../access/shared/api"; @@ -16,6 +18,7 @@ import { buildSiteRequest } from "../sites/shared/adapters"; const COMPANY_ID = "e0420eb9-2aab-4368-96a0-2ce83dd23eb4"; test("las consultas de compañías conservan sus rutas y rechazan respuestas inválidas", async (context) => { + context.after(configureCsrfTokenReader(() => "a".repeat(64))); context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => { assert.equal(options.method, "GET"); if (url === "/api/companies") { @@ -30,6 +33,7 @@ test("las consultas de compañías conservan sus rutas y rechazan respuestas inv }); test("lectura y guardado de accesos están separados sin cambiar el contrato HTTP", async (context) => { + context.after(configureCsrfTokenReader(() => "a".repeat(64))); const calls: { url: unknown; options: RequestInit }[] = []; context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => { @@ -40,7 +44,7 @@ test("lectura y guardado de accesos están separados sin cambiar el contrato HTT await getAccess("global"); const values = { kind: "system", name: "Directory", active: true } as const; - await saveAccess("global", values, COMPANY_ID, "csrf"); + await saveAccess("global", values, COMPANY_ID); assert.deepEqual( calls.map((call) => call.url), ["/api/access/global", `/api/access/global/${COMPANY_ID}/update`] @@ -51,10 +55,11 @@ test("lectura y guardado de accesos están separados sin cambiar el contrato HTT assert.ok(save); assert.equal(save.method, "POST"); assert.equal(save.body, JSON.stringify({ values })); - assert.equal(new Headers(save.headers).get("x-csrf-token"), "csrf"); + assert.equal(new Headers(save.headers).get("x-csrf-token"), "a".repeat(64)); }); test("el guardado de usos de buzón mantiene su endpoint y no se confunde con la consulta", async (context) => { + context.after(configureCsrfTokenReader(() => "a".repeat(64))); const calls: { url: unknown; options: RequestInit }[] = []; context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => { @@ -70,7 +75,7 @@ test("el guardado de usos de buzón mantiene su endpoint y no se confunde con la endDate: null, }; - await saveMailboxUse(COMPANY_ID, values, "csrf", COMPANY_ID); + await saveMailboxUse(COMPANY_ID, values, COMPANY_ID); assert.deepEqual( calls.map((call) => call.url), [`/api/mailboxes/${COMPANY_ID}`, `/api/mailboxes/${COMPANY_ID}/uses/${COMPANY_ID}/update`] diff --git a/modules/companies/src/web/shared/types/configuration-quick-editor-props.ts b/modules/companies/src/web/shared/types/configuration-quick-editor-props.ts index 89d0c5f..abccafa 100644 --- a/modules/companies/src/web/shared/types/configuration-quick-editor-props.ts +++ b/modules/companies/src/web/shared/types/configuration-quick-editor-props.ts @@ -6,7 +6,6 @@ export type ConfigurationQuickEditorProps = { kind: ConfigurationQuickKind; title?: string; company: CompanyDTO; - csrfToken: string; values: Readonly<{ personId: string; accountId: string; diff --git a/modules/companies/src/web/shared/ui/configuration-quick-editor.tsx b/modules/companies/src/web/shared/ui/configuration-quick-editor.tsx index 8feed05..f306072 100644 --- a/modules/companies/src/web/shared/ui/configuration-quick-editor.tsx +++ b/modules/companies/src/web/shared/ui/configuration-quick-editor.tsx @@ -16,7 +16,7 @@ const titles = { } as const; export function ConfigurationQuickEditor(props: ConfigurationQuickEditorProps) { - const { kind, title, company, csrfToken, values, data } = props; + const { kind, title, company, values, data } = props; const { created, close, catalog } = useConfigurationQuickEditorController(props); const { t } = useTranslation("companies"); @@ -36,7 +36,6 @@ export function ConfigurationQuickEditor(props: ConfigurationQuickEditorProps) { {kind === "person" && ( created("personId", id)} requireActive /> @@ -44,7 +43,6 @@ export function ConfigurationQuickEditor(props: ConfigurationQuickEditorProps) { {(kind === "account" || kind === "authorization") && data && ( void; companyId: string; @@ -30,7 +28,7 @@ export function DeleteRecordDialog({ if (!path) { return; } - await deleteRecord(path, csrfToken); + await deleteRecord(path); await Promise.all([ client.invalidateQueries({ queryKey }), client.invalidateQueries({ queryKey: ["person-processes", companyId] }), diff --git a/modules/companies/src/web/sites/list/ui/site-section.tsx b/modules/companies/src/web/sites/list/ui/site-section.tsx index 8a55b8d..4f73592 100644 --- a/modules/companies/src/web/sites/list/ui/site-section.tsx +++ b/modules/companies/src/web/sites/list/ui/site-section.tsx @@ -11,7 +11,7 @@ import { useTranslation } from "react-i18next"; import type { CompanyDTO } from "../../../../common"; import { SiteEditorDialog } from "../../shared"; -export function SiteSection({ company, csrfToken }: { company: CompanyDTO; csrfToken: string }) { +export function SiteSection({ company }: { company: CompanyDTO }) { const { t, i18n } = useTranslation("companies"); const [createOpen, setCreateOpen] = useState(false); const [selectedId, setSelectedId] = useState(); @@ -59,14 +59,12 @@ export function SiteSection({ company, csrfToken }: { company: CompanyDTO; csrfT /> setCreateOpen(false)} open={createOpen} /> { if (!open) { setSelectedId(undefined); diff --git a/modules/companies/src/web/sites/shared/api/save-site.api.ts b/modules/companies/src/web/sites/shared/api/save-site.api.ts index edd881d..f2ee594 100644 --- a/modules/companies/src/web/sites/shared/api/save-site.api.ts +++ b/modules/companies/src/web/sites/shared/api/save-site.api.ts @@ -1,10 +1,5 @@ import { CompanyResponseSchema } from "../../../../common"; import { companyRequest } from "../../../companies/shared/api/company-request"; -export const saveSite = (companyId: string, id: string | undefined, body: unknown, token: string) => - companyRequest( - `/${companyId}/sites${id ? `/${id}/update` : ""}`, - CompanyResponseSchema, - body, - token - ); +export const saveSite = (companyId: string, id: string | undefined, body: unknown) => + companyRequest(`/${companyId}/sites${id ? `/${id}/update` : ""}`, CompanyResponseSchema, body); diff --git a/modules/companies/src/web/sites/shared/controllers/use-site-form-controller.ts b/modules/companies/src/web/sites/shared/controllers/use-site-form-controller.ts index d98dfdb..909514d 100644 --- a/modules/companies/src/web/sites/shared/controllers/use-site-form-controller.ts +++ b/modules/companies/src/web/sites/shared/controllers/use-site-form-controller.ts @@ -9,15 +9,14 @@ import { buildSiteRequest } from "../adapters"; import { saveSite } from "../api"; import type { SiteFormProps } from "../types"; -export function useSiteFormController({ site, companyId, csrfToken, onSaved }: SiteFormProps) { +export function useSiteFormController({ site, companyId, onSaved }: SiteFormProps) { const client = useQueryClient(); const form = useForm({ resolver: zodResolver(SiteRequestSchema), defaultValues: { name: site?.name ?? "", address: site?.address ?? "" }, }); const mutation = useMutation({ - mutationFn: (values: SiteRequestDTO) => - saveSite(companyId, site?.id, buildSiteRequest(values), csrfToken), + mutationFn: (values: SiteRequestDTO) => saveSite(companyId, site?.id, buildSiteRequest(values)), onSuccess: (response) => { client.setQueryData(companyKeys.detail(companyId), response); if (!site) { diff --git a/modules/companies/src/web/sites/shared/types/site-form-props.ts b/modules/companies/src/web/sites/shared/types/site-form-props.ts index 9721aa0..5671b0c 100644 --- a/modules/companies/src/web/sites/shared/types/site-form-props.ts +++ b/modules/companies/src/web/sites/shared/types/site-form-props.ts @@ -3,7 +3,6 @@ import type { SiteDTO } from "../../../../common"; export type SiteFormProps = { site?: SiteDTO; companyId: string; - csrfToken: string; onSaved?: (id: string) => void; showTitle?: boolean; }; diff --git a/modules/companies/src/web/telework-authorizations/list/types/telework-authorizations-page-props.ts b/modules/companies/src/web/telework-authorizations/list/types/telework-authorizations-page-props.ts index 20644cd..ffb97c2 100644 --- a/modules/companies/src/web/telework-authorizations/list/types/telework-authorizations-page-props.ts +++ b/modules/companies/src/web/telework-authorizations/list/types/telework-authorizations-page-props.ts @@ -2,6 +2,5 @@ import type { CompanyDTO } from "../../../../common"; export type TeleworkAuthorizationsPageProps = { company: CompanyDTO; - csrfToken: string; initialPersonId?: string | undefined; }; diff --git a/modules/companies/src/web/telework-authorizations/list/ui/telework-authorizations-page.tsx b/modules/companies/src/web/telework-authorizations/list/ui/telework-authorizations-page.tsx index 5cf743c..e53ee0e 100644 --- a/modules/companies/src/web/telework-authorizations/list/ui/telework-authorizations-page.tsx +++ b/modules/companies/src/web/telework-authorizations/list/ui/telework-authorizations-page.tsx @@ -15,7 +15,7 @@ import { useTeleworkAuthorizationsPageController } from "../controllers"; import type { TeleworkAuthorizationsPageProps } from "../types"; export function TeleworkAuthorizationsPage(props: TeleworkAuthorizationsPageProps) { - const { company, csrfToken, initialPersonId } = props; + const { company, initialPersonId } = props; const { editor, setEditor, @@ -143,7 +143,6 @@ export function TeleworkAuthorizationsPage(props: TeleworkAuthorizationsPageProp authorization={edited} authorizations={authorizations} company={company} - csrfToken={csrfToken} initialPersonId={initialPersonId} onClose={() => setEditor(undefined)} /> diff --git a/modules/companies/src/web/telework-authorizations/shared/api/telework-api.ts b/modules/companies/src/web/telework-authorizations/shared/api/telework-api.ts index ee1bc96..cc5ffec 100644 --- a/modules/companies/src/web/telework-authorizations/shared/api/telework-api.ts +++ b/modules/companies/src/web/telework-authorizations/shared/api/telework-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { TeleworkAuthorizationInput } from "../../../../common/telework-authorizations"; @@ -14,18 +15,13 @@ export class TeleworkApiError extends Error { } } -async function request( - companyId: string, - values?: TeleworkAuthorizationInput, - token?: string, - id?: string -) { - const response = await fetch( +async function request(companyId: string, values?: TeleworkAuthorizationInput, id?: string) { + const response = await csrfFetch( `/api/telework-authorizations/${companyId}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), } ); @@ -44,7 +40,7 @@ export const teleworkApi = { async get(companyId: string) { return TeleworkAuthorizationsResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: TeleworkAuthorizationInput, token: string, id?: string) { - return TeleworkAuthorizationResponseSchema.parse(await request(companyId, values, token, id)); + async save(companyId: string, values: TeleworkAuthorizationInput, id?: string) { + return TeleworkAuthorizationResponseSchema.parse(await request(companyId, values, id)); }, }; diff --git a/modules/companies/src/web/telework-authorizations/shared/controllers/use-telework-authorization-form-controller.ts b/modules/companies/src/web/telework-authorizations/shared/controllers/use-telework-authorization-form-controller.ts index d8c804d..33cf52f 100644 --- a/modules/companies/src/web/telework-authorizations/shared/controllers/use-telework-authorization-form-controller.ts +++ b/modules/companies/src/web/telework-authorizations/shared/controllers/use-telework-authorization-form-controller.ts @@ -11,7 +11,6 @@ import type { TeleworkAuthorizationFormProps } from "../types"; export function useTeleworkAuthorizationFormController({ company, - csrfToken, authorization, authorizations, initialPersonId, @@ -42,7 +41,7 @@ export function useTeleworkAuthorizationFormController({ const fixed = Boolean(authorization || initialPersonId); const mutation = useMutation({ mutationFn: (input: TeleworkAuthorizationInput) => - teleworkApi.save(company.id, input, csrfToken, authorization?.id), + teleworkApi.save(company.id, input, authorization?.id), onSuccess: async () => { await Promise.all([ client.invalidateQueries({ queryKey: teleworkKeys.company(company.id) }), diff --git a/modules/companies/src/web/telework-authorizations/shared/telework.test.tsx b/modules/companies/src/web/telework-authorizations/shared/telework.test.tsx index 6cdb2d9..7935181 100644 --- a/modules/companies/src/web/telework-authorizations/shared/telework.test.tsx +++ b/modules/companies/src/web/telework-authorizations/shared/telework.test.tsx @@ -51,10 +51,7 @@ async function render(language: string, node: ReactNode) { test("teletrabajo: consulta sin formularios y traducciones", async () => { for (const language of ["es", "en"]) { - const html = await render( - language, - - ); + const html = await render(language, ); const text = language === "es" ? companiesSpanish : companiesEnglish; assert.ok(html.includes(text.telework.title)); @@ -67,12 +64,7 @@ test("teletrabajo: formulario con alta rápida de persona, fechas opcionales y a for (const language of ["es", "en"]) { const html = await render( language, - undefined} - /> + undefined} /> ); const text = language === "es" ? companiesSpanish : companiesEnglish; @@ -123,7 +115,6 @@ test("teletrabajo: alta desde un perfil fija la persona y no abre otra alta ráp undefined} /> diff --git a/modules/companies/src/web/telework-authorizations/shared/types/telework-authorization-form-props.ts b/modules/companies/src/web/telework-authorizations/shared/types/telework-authorization-form-props.ts index 2b530ce..31d127b 100644 --- a/modules/companies/src/web/telework-authorizations/shared/types/telework-authorization-form-props.ts +++ b/modules/companies/src/web/telework-authorizations/shared/types/telework-authorization-form-props.ts @@ -3,7 +3,6 @@ import type { TeleworkAuthorizationDTO } from "../../../../common/telework-autho export type TeleworkAuthorizationFormProps = { company: CompanyDTO; - csrfToken: string; authorization?: TeleworkAuthorizationDTO | undefined; authorizations: readonly TeleworkAuthorizationDTO[]; initialPersonId?: string | undefined; diff --git a/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx b/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx index 28c10c1..e209e9d 100644 --- a/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx +++ b/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx @@ -9,14 +9,12 @@ import { TeleworkAuthorizationForm } from "./telework-authorization-form"; export function TeleworkAuthorizationEditorDialog({ authorization, company, - csrfToken, authorizations, initialPersonId, onClose, }: { authorization: ComponentProps["authorization"]; company: CompanyDTO; - csrfToken: string; authorizations: ComponentProps["authorizations"]; initialPersonId: ComponentProps["initialPersonId"]; onClose: () => void; @@ -39,7 +37,6 @@ export function TeleworkAuthorizationEditorDialog({ authorization={authorization} authorizations={authorizations} company={company} - csrfToken={csrfToken} initialPersonId={initialPersonId} onSaved={() => onClose()} /> diff --git a/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-form.tsx b/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-form.tsx index 329cb5f..e1af063 100644 --- a/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-form.tsx +++ b/modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-form.tsx @@ -16,7 +16,7 @@ import type { TeleworkAuthorizationFormProps } from "../types"; import { TeleworkFeedback } from "./telework-feedback"; export function TeleworkAuthorizationForm(props: TeleworkAuthorizationFormProps) { - const { company, csrfToken, authorization } = props; + const { company, authorization } = props; const { quick, setQuick, form, values, fixed, mutation, reserved, submit, personCreated } = useTeleworkAuthorizationFormController(props); const { t } = useTranslation("companies"); @@ -110,7 +110,6 @@ export function TeleworkAuthorizationForm(props: TeleworkAuthorizationFormProps) {quick && ( { if (!open) { setQuick(false); diff --git a/modules/companies/src/web/tickets/create/types/ticket-quick-editor-props.ts b/modules/companies/src/web/tickets/create/types/ticket-quick-editor-props.ts index 83fd285..035c07a 100644 --- a/modules/companies/src/web/tickets/create/types/ticket-quick-editor-props.ts +++ b/modules/companies/src/web/tickets/create/types/ticket-quick-editor-props.ts @@ -3,6 +3,5 @@ import type { TicketTargetInput } from "../../../../common/tickets"; export type TicketQuickEditorProps = { companyId: string; kind: TicketTargetInput["kind"]; - csrfToken: string; onSaved: (id: string) => void; }; diff --git a/modules/companies/src/web/tickets/create/ui/intervention-ticket-create-dialog.tsx b/modules/companies/src/web/tickets/create/ui/intervention-ticket-create-dialog.tsx index d942f55..cbb3acf 100644 --- a/modules/companies/src/web/tickets/create/ui/intervention-ticket-create-dialog.tsx +++ b/modules/companies/src/web/tickets/create/ui/intervention-ticket-create-dialog.tsx @@ -7,7 +7,6 @@ import { TicketForm } from "../../shared"; export function InterventionTicketCreateDialog({ open, companyId, - csrfToken, tickets, companies, technicians, @@ -16,7 +15,6 @@ export function InterventionTicketCreateDialog({ }: { open: boolean; companyId: string; - csrfToken: string; tickets: ComponentProps["data"]["tickets"]; companies: ComponentProps["data"]["companies"]; technicians: ComponentProps["data"]["technicians"]; @@ -36,7 +34,6 @@ export function InterventionTicketCreateDialog({ > {open && ( - {kind === "person" && ( - - )} - {kind === "resource" && ( - - )} + {kind === "person" && } + {kind === "resource" && } {kind === "asset" && catalog.data && ( )} {kind === "workstation" && ( - + )} { nested.onSaved(id); setNested(undefined); @@ -76,7 +65,6 @@ export function TicketQuickEditor(props: TicketQuickEditorProps) { {nested?.kind === "site" && ( { nested.onSaved(id); setNested(undefined); diff --git a/modules/companies/src/web/tickets/create/ui/ticket-quick-forms.tsx b/modules/companies/src/web/tickets/create/ui/ticket-quick-forms.tsx index ad705b0..8f77655 100644 --- a/modules/companies/src/web/tickets/create/ui/ticket-quick-forms.tsx +++ b/modules/companies/src/web/tickets/create/ui/ticket-quick-forms.tsx @@ -7,14 +7,12 @@ import { PersonForm } from "../../../people"; export function TicketQuickForms({ kind, companyId, - csrfToken, onClose, onCompany, onPerson, }: { kind: "company" | "person" | undefined; companyId: string; - csrfToken: string; onClose: () => void; onCompany: (id: string) => void; onPerson: (id: string) => void; @@ -33,12 +31,8 @@ export function TicketQuickForms({ open={Boolean(kind)} title={t(kind === "company" ? "newCompany" : "newPerson")} > - {kind === "company" && ( - - )} - {kind === "person" && ( - - )} + {kind === "company" && } + {kind === "person" && } ); } diff --git a/modules/companies/src/web/tickets/create/ui/ticket-target-create-dialog.tsx b/modules/companies/src/web/tickets/create/ui/ticket-target-create-dialog.tsx index 83dcada..a2ea106 100644 --- a/modules/companies/src/web/tickets/create/ui/ticket-target-create-dialog.tsx +++ b/modules/companies/src/web/tickets/create/ui/ticket-target-create-dialog.tsx @@ -7,13 +7,11 @@ import { TicketQuickEditor } from "./ticket-quick-editor"; export function TicketTargetCreateDialog({ quick, companyId, - csrfToken, onSaved, onOpenChange, }: { quick: ComponentProps["kind"] | undefined; companyId: string; - csrfToken: string; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; }) { @@ -28,14 +26,7 @@ export function TicketTargetCreateDialog({ title={t("tickets.newTarget")} wide > - {quick && ( - - )} + {quick && } ); } diff --git a/modules/companies/src/web/tickets/list/types/tickets-page-props.ts b/modules/companies/src/web/tickets/list/types/tickets-page-props.ts index 3e0dc8e..cf885d6 100644 --- a/modules/companies/src/web/tickets/list/types/tickets-page-props.ts +++ b/modules/companies/src/web/tickets/list/types/tickets-page-props.ts @@ -8,7 +8,6 @@ export type TicketFilters = { export type TicketsPageProps = { companyId?: string | undefined; - csrfToken: string; initialId?: string | undefined; initialCreate?: boolean | undefined; initialPendingOnly?: boolean | undefined; diff --git a/modules/companies/src/web/tickets/list/ui/tickets-page.tsx b/modules/companies/src/web/tickets/list/ui/tickets-page.tsx index 79afc01..97fe489 100644 --- a/modules/companies/src/web/tickets/list/ui/tickets-page.tsx +++ b/modules/companies/src/web/tickets/list/ui/tickets-page.tsx @@ -19,7 +19,7 @@ import { useTicketsPageController } from "../controllers"; import type { TicketsPageProps } from "../types"; export function TicketsPage(props: TicketsPageProps) { - const { companyId, csrfToken } = props; + const { companyId } = props; const { query, filters, @@ -256,14 +256,12 @@ export function TicketsPage(props: TicketsPageProps) { /> setInterventionTicketId(undefined)} /> diff --git a/modules/companies/src/web/tickets/shared/api/ticket-api.ts b/modules/companies/src/web/tickets/shared/api/ticket-api.ts index 3b48d1e..20f9cdf 100644 --- a/modules/companies/src/web/tickets/shared/api/ticket-api.ts +++ b/modules/companies/src/web/tickets/shared/api/ticket-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { SaveTicketInput } from "../../../../common/tickets"; @@ -18,11 +19,11 @@ export class TicketApiError extends Error { } } -async function request(path: string, input?: SaveTicketInput, token?: string) { - const response = await fetch(`/api/tickets${path}`, { +async function request(path: string, input?: SaveTicketInput) { + const response = await csrfFetch(`/api/tickets${path}`, { method: input ? "POST" : "GET", credentials: "same-origin", - headers: input ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: input ? { "content-type": "application/json" } : {}, ...(input ? { body: JSON.stringify(input) } : {}), }); const data: unknown = await response.json(); @@ -43,9 +44,9 @@ export const ticketApi = { async lookups(companyId: string) { return TicketLookupsSchema.parse(await request(`/${companyId}/lookups`)); }, - async save(companyId: string, input: SaveTicketInput, token: string, id?: string) { + async save(companyId: string, input: SaveTicketInput, id?: string) { return TicketResponseSchema.parse( - await request(`/${companyId}${id ? `/${id}/update` : ""}`, input, token) + await request(`/${companyId}${id ? `/${id}/update` : ""}`, input) ); }, }; diff --git a/modules/companies/src/web/tickets/shared/controllers/use-ticket-form-controller.ts b/modules/companies/src/web/tickets/shared/controllers/use-ticket-form-controller.ts index 4ee3673..eedc9ad 100644 --- a/modules/companies/src/web/tickets/shared/controllers/use-ticket-form-controller.ts +++ b/modules/companies/src/web/tickets/shared/controllers/use-ticket-form-controller.ts @@ -9,7 +9,7 @@ import { TicketInputSchema } from "../../../../common/tickets"; import { ticketApi, ticketKeys } from "../api"; import type { TicketFormProps } from "../types"; -export function useTicketFormController({ scope, ticket, csrfToken, onSaved }: TicketFormProps) { +export function useTicketFormController({ scope, ticket, onSaved }: TicketFormProps) { const client = useQueryClient(); const [companyId, setCompanyId] = useState(ticket?.companyId ?? scope ?? ""); const [requestKey] = useState(() => UniqueID.generateNewID().toPrimitive()); @@ -38,7 +38,6 @@ export function useTicketFormController({ scope, ticket, csrfToken, onSaved }: T ticketApi.save( companyId, { values: input, requestKey, expectedRevisionId: ticket?.history.at(-1)?.id ?? null }, - csrfToken, ticket?.id ), onSuccess: async (response) => { diff --git a/modules/companies/src/web/tickets/shared/tickets.test.tsx b/modules/companies/src/web/tickets/shared/tickets.test.tsx index d1259d4..d68c248 100644 --- a/modules/companies/src/web/tickets/shared/tickets.test.tsx +++ b/modules/companies/src/web/tickets/shared/tickets.test.tsx @@ -99,9 +99,7 @@ test("tickets: filtros globales por compañía, estado, prioridad, responsable y assert.equal(filterTickets([item], { ...empty, assignee: "unassigned" }).length, 1); }); test("tickets: alta modal, compañía fija y ayuda inferior", async () => { - const html = await render( - undefined} scope={companyId} /> - ); + const html = await render( undefined} scope={companyId} />); assert.ok(html.includes("Compañía test")); assert.ok(html.includes("Nueva persona")); @@ -115,12 +113,12 @@ test("tickets: vistas global y por compañía sin formularios junto al listado, ["es", "Todos los tickets"], ["en", "All tickets"], ]) { - const html = await render(, language); + const html = await render(, language); assert.ok(html.includes(expected ?? "")); assert.equal(html.includes("); + const scoped = await render(); assert.equal(scoped.includes("tickets-company-filter"), false); }); diff --git a/modules/companies/src/web/tickets/shared/types/ticket-form-props.ts b/modules/companies/src/web/tickets/shared/types/ticket-form-props.ts index 510af26..0d6a17e 100644 --- a/modules/companies/src/web/tickets/shared/types/ticket-form-props.ts +++ b/modules/companies/src/web/tickets/shared/types/ticket-form-props.ts @@ -4,6 +4,5 @@ export type TicketFormProps = { scope?: string; ticket?: TicketDTO; data: TicketListDTO; - csrfToken: string; onSaved: (id: string) => void; }; diff --git a/modules/companies/src/web/tickets/shared/ui/ticket-editor-dialog.tsx b/modules/companies/src/web/tickets/shared/ui/ticket-editor-dialog.tsx index c0b703f..1ada42c 100644 --- a/modules/companies/src/web/tickets/shared/ui/ticket-editor-dialog.tsx +++ b/modules/companies/src/web/tickets/shared/ui/ticket-editor-dialog.tsx @@ -8,14 +8,12 @@ export function TicketEditorDialog({ open, edit, companyId, - csrfToken, data, onOpenChange, }: { open: boolean; edit: ComponentProps["ticket"]; companyId: string | undefined; - csrfToken: string; data: ComponentProps["data"] | undefined; onOpenChange: (open: boolean) => void; }) { @@ -35,7 +33,6 @@ export function TicketEditorDialog({ key={edit?.id ?? "new"} {...(companyId ? { scope: companyId } : {})} {...(edit ? { ticket: edit } : {})} - csrfToken={csrfToken} data={data} onSaved={() => onOpenChange(false)} /> diff --git a/modules/companies/src/web/tickets/shared/ui/ticket-form.tsx b/modules/companies/src/web/tickets/shared/ui/ticket-form.tsx index ba48697..ff95239 100644 --- a/modules/companies/src/web/tickets/shared/ui/ticket-form.tsx +++ b/modules/companies/src/web/tickets/shared/ui/ticket-form.tsx @@ -19,7 +19,7 @@ import { TicketHelp } from "./ticket-help"; import { TicketTargets } from "./ticket-targets"; export function TicketForm(props: TicketFormProps) { - const { scope, ticket, data, csrfToken } = props; + const { scope, ticket, data } = props; const { client, companyId, @@ -130,7 +130,6 @@ export function TicketForm(props: TicketFormProps) { {lookups.data && ( setQuick(undefined)} onCompany={(id) => { diff --git a/modules/companies/src/web/tickets/shared/ui/ticket-targets.tsx b/modules/companies/src/web/tickets/shared/ui/ticket-targets.tsx index a0dedf4..c4891b1 100644 --- a/modules/companies/src/web/tickets/shared/ui/ticket-targets.tsx +++ b/modules/companies/src/web/tickets/shared/ui/ticket-targets.tsx @@ -12,14 +12,12 @@ import { ticketTargetOptions } from "../helpers"; export function TicketTargets({ companyId, - csrfToken, values, lookups, onChange, lookupKey, }: { companyId: string; - csrfToken: string; values: TicketTargetInput[]; lookups: TicketLookupsDTO; onChange: (values: TicketTargetInput[]) => void; @@ -116,7 +114,6 @@ export function TicketTargets({ { if (!open) { setQuick(undefined); diff --git a/modules/companies/src/web/work-environments/detail/ui/environment-content.tsx b/modules/companies/src/web/work-environments/detail/ui/environment-content.tsx index 7793ccf..094b794 100644 --- a/modules/companies/src/web/work-environments/detail/ui/environment-content.tsx +++ b/modules/companies/src/web/work-environments/detail/ui/environment-content.tsx @@ -23,7 +23,6 @@ export function EnvironmentContent({ profile, profiles, sources, - csrfToken, onManage, onProfile, }: { @@ -33,7 +32,6 @@ export function EnvironmentContent({ authorizations: readonly TeleworkAuthorizationDTO[]; profiles: readonly WorkProfileDTO[]; sources: EnvironmentSources; - csrfToken: string; onManage: (kind: ProfileConfigurationKind | "telework" | "remote") => void; onProfile: (id: string) => void; }) { @@ -190,7 +188,6 @@ export function EnvironmentContent({
diff --git a/modules/companies/src/web/work-environments/detail/ui/profile-configurations.tsx b/modules/companies/src/web/work-environments/detail/ui/profile-configurations.tsx index c308fee..bb35f27 100644 --- a/modules/companies/src/web/work-environments/detail/ui/profile-configurations.tsx +++ b/modules/companies/src/web/work-environments/detail/ui/profile-configurations.tsx @@ -16,20 +16,18 @@ import { WorkEnvironmentPage } from "./work-environment-page"; export function ProfileConfigurations({ personId, company, - csrfToken, selection, onBack, onSelection, }: { company: CompanyDTO; - csrfToken: string; personId: string | undefined; selection: ProfileSelection; onSelection: (selection: ProfileSelection) => void; onBack: () => void; }) { const { t } = useTranslation("companies"); - const props = { company, csrfToken, initialProfileId: selection.profileId }; + const props = { company, initialProfileId: selection.profileId }; return (
@@ -44,7 +42,6 @@ export function ProfileConfigurations({ {selection.kind === "environment" && ( onSelection({ kind, profileId: selection.profileId, fromEnvironment: true }) } @@ -53,14 +50,10 @@ export function ProfileConfigurations({ /> )} {selection.kind === "remote" && ( - + )} {selection.kind === "telework" && ( - + )} {selection.kind === "folders" && } {selection.kind === "mail" && } diff --git a/modules/companies/src/web/work-environments/detail/ui/work-environment-page.tsx b/modules/companies/src/web/work-environments/detail/ui/work-environment-page.tsx index 685576f..b6cb84e 100644 --- a/modules/companies/src/web/work-environments/detail/ui/work-environment-page.tsx +++ b/modules/companies/src/web/work-environments/detail/ui/work-environment-page.tsx @@ -10,13 +10,11 @@ import { EnvironmentContent } from "./environment-content"; export function WorkEnvironmentPage({ company, profileId, - csrfToken, onManage, onProfile, }: { company: CompanyDTO; profileId: string; - csrfToken: string; onManage: (kind: ProfileConfigurationKind | "telework" | "remote") => void; onProfile: (id: string) => void; }) { @@ -54,7 +52,6 @@ export function WorkEnvironmentPage({ authorizations={query.data.authorizations} company={company} connections={query.data.connections} - csrfToken={csrfToken} key={profile.id} onManage={onManage} onProfile={onProfile} diff --git a/modules/companies/src/web/work-environments/shared/work-environment.test.tsx b/modules/companies/src/web/work-environments/shared/work-environment.test.tsx index ef4543c..16c9528 100644 --- a/modules/companies/src/web/work-environments/shared/work-environment.test.tsx +++ b/modules/companies/src/web/work-environments/shared/work-environment.test.tsx @@ -401,7 +401,6 @@ async function render( undefined} onProfile={() => undefined} profileId={profile.id} diff --git a/modules/companies/src/web/work-profiles/list/types/work-profiles-page-props.ts b/modules/companies/src/web/work-profiles/list/types/work-profiles-page-props.ts index f8a632a..65da7cc 100644 --- a/modules/companies/src/web/work-profiles/list/types/work-profiles-page-props.ts +++ b/modules/companies/src/web/work-profiles/list/types/work-profiles-page-props.ts @@ -2,7 +2,6 @@ import type { CompanyDTO } from "../../../../common"; export type WorkProfilesPageProps = { company: CompanyDTO; - csrfToken: string; initialId?: string | undefined; initialConfiguration?: | "folders" diff --git a/modules/companies/src/web/work-profiles/list/ui/work-profiles-page.tsx b/modules/companies/src/web/work-profiles/list/ui/work-profiles-page.tsx index 9232460..b5a9299 100644 --- a/modules/companies/src/web/work-profiles/list/ui/work-profiles-page.tsx +++ b/modules/companies/src/web/work-profiles/list/ui/work-profiles-page.tsx @@ -16,7 +16,7 @@ import { useWorkProfilesPageController } from "../controllers"; import type { WorkProfilesPageProps } from "../types"; export function WorkProfilesPage(props: WorkProfilesPageProps) { - const { company, csrfToken } = props; + const { company } = props; const { configuration, setConfiguration, @@ -50,7 +50,6 @@ export function WorkProfilesPage(props: WorkProfilesPageProps) { return ( item.id === configuration.profileId)?.values.personId} @@ -213,7 +212,6 @@ export function WorkProfilesPage(props: WorkProfilesPageProps) { {editor && ( setEditor(undefined)} profile={edited} /> diff --git a/modules/companies/src/web/work-profiles/shared/api/work-profile-api.ts b/modules/companies/src/web/work-profiles/shared/api/work-profile-api.ts index bd844ad..8f14da2 100644 --- a/modules/companies/src/web/work-profiles/shared/api/work-profile-api.ts +++ b/modules/companies/src/web/work-profiles/shared/api/work-profile-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { WorkProfileInput } from "../../../../common/work-profiles"; @@ -14,11 +15,11 @@ export class WorkProfileApiError extends Error { } } -async function request(companyId: string, values?: WorkProfileInput, token?: string, id?: string) { - const response = await fetch(`/api/work-profiles/${companyId}${id ? `/${id}/update` : ""}`, { +async function request(companyId: string, values?: WorkProfileInput, id?: string) { + const response = await csrfFetch(`/api/work-profiles/${companyId}${id ? `/${id}/update` : ""}`, { method: values ? "POST" : "GET", credentials: "same-origin", - headers: values ? { "content-type": "application/json", "x-csrf-token": token ?? "" } : {}, + headers: values ? { "content-type": "application/json" } : {}, ...(values ? { body: JSON.stringify({ values }) } : {}), }); const json: unknown = await response.json(); @@ -36,7 +37,7 @@ export const profileApi = { async get(companyId: string) { return WorkProfilesResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: WorkProfileInput, token: string, id?: string) { - return WorkProfileResponseSchema.parse(await request(companyId, values, token, id)); + async save(companyId: string, values: WorkProfileInput, id?: string) { + return WorkProfileResponseSchema.parse(await request(companyId, values, id)); }, }; diff --git a/modules/companies/src/web/work-profiles/shared/controllers/use-work-profile-form-controller.ts b/modules/companies/src/web/work-profiles/shared/controllers/use-work-profile-form-controller.ts index ccfa72a..2da3f3d 100644 --- a/modules/companies/src/web/work-profiles/shared/controllers/use-work-profile-form-controller.ts +++ b/modules/companies/src/web/work-profiles/shared/controllers/use-work-profile-form-controller.ts @@ -13,12 +13,7 @@ import type { ConfigurationQuickKind } from "../../../shared/types"; import { profileApi, profileKeys } from "../api"; import type { WorkProfileFormProps } from "../types"; -export function useWorkProfileFormController({ - company, - csrfToken, - profile, - onSaved, -}: WorkProfileFormProps) { +export function useWorkProfileFormController({ company, profile, onSaved }: WorkProfileFormProps) { const client = useQueryClient(); const mounted = useRef(true); @@ -67,8 +62,7 @@ export function useWorkProfileFormController({ ); }); const mutation = useMutation({ - mutationFn: (input: WorkProfileInput) => - profileApi.save(company.id, input, csrfToken, profile?.id), + mutationFn: (input: WorkProfileInput) => profileApi.save(company.id, input, profile?.id), onSuccess: async (result) => { await client.invalidateQueries({ queryKey: profileKeys.company(company.id) }); if (mounted.current) { diff --git a/modules/companies/src/web/work-profiles/shared/profile.test.tsx b/modules/companies/src/web/work-profiles/shared/profile.test.tsx index 2c54efd..eba6935 100644 --- a/modules/companies/src/web/work-profiles/shared/profile.test.tsx +++ b/modules/companies/src/web/work-profiles/shared/profile.test.tsx @@ -57,7 +57,7 @@ async function render(language: string, node: ReactNode) { test("perfiles: listado es/en, alta en cabecera y ausencia de formulario junto al listado", async () => { for (const language of ["es", "en"]) { - const html = await render(language, ); + const html = await render(language, ); assert.equal(html.includes(" { - const html = await render( - "es", - undefined} /> - ); + const html = await render("es", undefined} />); for (const label of ["Nueva persona", "Nueva cuenta", "Nuevo dispositivo"]) { assert.ok(html.includes(`aria-label="${label}"`)); diff --git a/modules/companies/src/web/work-profiles/shared/types/work-profile-form-props.ts b/modules/companies/src/web/work-profiles/shared/types/work-profile-form-props.ts index e301b01..02e5bf6 100644 --- a/modules/companies/src/web/work-profiles/shared/types/work-profile-form-props.ts +++ b/modules/companies/src/web/work-profiles/shared/types/work-profile-form-props.ts @@ -3,7 +3,6 @@ import type { WorkProfileDTO } from "../../../../common/work-profiles"; export type WorkProfileFormProps = { company: CompanyDTO; - csrfToken: string; profile?: WorkProfileDTO | undefined; onSaved: (id: string) => void; allowQuickCreate?: boolean; diff --git a/modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx b/modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx index 7f6602f..a45bffd 100644 --- a/modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx +++ b/modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx @@ -9,12 +9,10 @@ import { WorkProfileForm } from "./work-profile-form"; export function WorkProfileEditorDialog({ profile, company, - csrfToken, onClose, }: { profile: ComponentProps["profile"]; company: CompanyDTO; - csrfToken: string; onClose: () => void; }) { const { t } = useTranslation("companies"); @@ -32,12 +30,7 @@ export function WorkProfileEditorDialog({ title={t(profile ? "profiles.edit" : "profiles.new")} wide > - onClose()} - profile={profile} - /> + onClose()} profile={profile} /> ); } diff --git a/modules/companies/src/web/work-profiles/shared/ui/work-profile-form.tsx b/modules/companies/src/web/work-profiles/shared/ui/work-profile-form.tsx index dbd47f1..3ba8349 100644 --- a/modules/companies/src/web/work-profiles/shared/ui/work-profile-form.tsx +++ b/modules/companies/src/web/work-profiles/shared/ui/work-profile-form.tsx @@ -16,7 +16,7 @@ import type { WorkProfileFormProps } from "../types"; import { ProfileFeedback } from "./profile-feedback"; export function WorkProfileForm(props: WorkProfileFormProps) { - const { company, csrfToken, profile, allowQuickCreate = true } = props; + const { company, profile, allowQuickCreate = true } = props; const { quick, setQuick, @@ -132,7 +132,6 @@ export function WorkProfileForm(props: WorkProfileFormProps) { {quick && ( setQuick(undefined)} diff --git a/modules/companies/src/web/workspace/catalogs/ui/global-catalog-content.tsx b/modules/companies/src/web/workspace/catalogs/ui/global-catalog-content.tsx index 18751a1..4d13c47 100644 --- a/modules/companies/src/web/workspace/catalogs/ui/global-catalog-content.tsx +++ b/modules/companies/src/web/workspace/catalogs/ui/global-catalog-content.tsx @@ -6,25 +6,19 @@ import { InterventionTemplatesPage } from "../../../intervention-templates"; import { ProcessTemplatesPage } from "../../../process-templates"; import type { GlobalCatalog } from "../../shared/types"; -export function GlobalCatalogContent({ - catalog, - csrfToken, -}: { - catalog: GlobalCatalog; - csrfToken: string; -}) { +export function GlobalCatalogContent({ catalog }: { catalog: GlobalCatalog }) { switch (catalog) { case "process-templates": - return ; + return ; case "intervention-templates": - return ; + return ; case "access": - return ; + return ; case "assets": - return ; + return ; case "brands": - return ; + return ; case "resources": - return ; + return ; } } diff --git a/modules/companies/src/web/workspace/view/types/companies-workspace-props.ts b/modules/companies/src/web/workspace/view/types/companies-workspace-props.ts index 976176f..232614a 100644 --- a/modules/companies/src/web/workspace/view/types/companies-workspace-props.ts +++ b/modules/companies/src/web/workspace/view/types/companies-workspace-props.ts @@ -1,7 +1,6 @@ import type { ReactNode } from "react"; export type CompaniesWorkspaceProps = { - csrfToken: string; accountNavigation: ReactNode; userName: string; languageSwitcher: ReactNode; diff --git a/modules/companies/src/web/workspace/view/ui/companies-workspace.tsx b/modules/companies/src/web/workspace/view/ui/companies-workspace.tsx index 8837656..eee0b8b 100644 --- a/modules/companies/src/web/workspace/view/ui/companies-workspace.tsx +++ b/modules/companies/src/web/workspace/view/ui/companies-workspace.tsx @@ -11,7 +11,7 @@ import { WorkspaceHeader } from "./workspace-header"; import { WorkspaceSidebar } from "./workspace-sidebar"; export function CompaniesWorkspace(props: CompaniesWorkspaceProps) { - const { csrfToken, accountNavigation, userName, languageSwitcher } = props; + const { accountNavigation, userName, languageSwitcher } = props; const { query, setLocation, @@ -71,7 +71,6 @@ export function CompaniesWorkspace(props: CompaniesWorkspaceProps) { )} {current.area === "company" ? ( void; onSelectCompany: (id: string) => void; }) { @@ -32,7 +30,6 @@ export function WorkspaceContent({ case "interventions": return ( ); case "catalogs": - return catalog ? ( - - ) : ( - - ); + return catalog ? : ; case "company": return null; case "home": - return ; + return ; case "not-found": case "invalid-route": return ; diff --git a/modules/core/package.json b/modules/core/package.json index 4df1b66..5985d71 100644 --- a/modules/core/package.json +++ b/modules/core/package.json @@ -10,7 +10,7 @@ }, "scripts": { "typecheck": "tsc -p tsconfig.json", - "test": "node --import tsx --test src/common/dto/response/get-health.response.dto.test.ts src/web/navigation/shared/browser-navigation.test.ts" + "test": "node --import tsx --test src/common/dto/response/get-health.response.dto.test.ts src/web/navigation/shared/browser-navigation.test.ts src/web/http/shared/api/csrf-fetch.test.ts" }, "dependencies": { "zod": "4.3.6", diff --git a/modules/core/src/web/http/shared/api/csrf-fetch.test.ts b/modules/core/src/web/http/shared/api/csrf-fetch.test.ts new file mode 100644 index 0000000..6c7fbf3 --- /dev/null +++ b/modules/core/src/web/http/shared/api/csrf-fetch.test.ts @@ -0,0 +1,109 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { QueryClient } from "@tanstack/react-query"; + +import { configureCsrfTokenReader, csrfFetch } from "./csrf-fetch"; + +const FIRST_TOKEN = "a".repeat(64); +const NEXT_TOKEN = "b".repeat(64); + +test("las escrituras leen la sesión actual de Query y siguen su rotación y eliminación", async (context) => { + const client = new QueryClient(); + const key = ["identity", "session"]; + const tokens: (string | null)[] = []; + + context.after( + configureCsrfTokenReader(() => client.getQueryData<{ csrfToken: string }>(key)?.csrfToken) + ); + context.mock.method(globalThis, "fetch", async (_path: string, options: RequestInit) => { + tokens.push(new Headers(options.headers).get("x-csrf-token")); + + return new Response(null, { status: 204 }); + }); + client.setQueryData(key, { csrfToken: FIRST_TOKEN }); + await csrfFetch("/api/users", { method: "POST" }); + client.setQueryData(key, { csrfToken: NEXT_TOKEN }); + await csrfFetch("/api/users", { method: "DELETE" }); + client.removeQueries({ queryKey: key }); + assert.throws(() => csrfFetch("/api/users", { method: "POST" }), /CSRF_SESSION_UNAVAILABLE/); + assert.deepEqual(tokens, [FIRST_TOKEN, NEXT_TOKEN]); + client.clear(); +}); + +test("GET, HEAD y OPTIONS funcionan sin sesión y no envían tokens suministrados por el llamador", async (context) => { + context.after(configureCsrfTokenReader(() => undefined)); + context.mock.method(globalThis, "fetch", async (path: string, options: RequestInit) => { + assert.equal(path, "/api/auth/session"); + assert.equal(options.credentials, "same-origin"); + assert.equal(new Headers(options.headers).has("x-csrf-token"), false); + + return new Response(null, { status: 204 }); + }); + await csrfFetch("/api/auth/session"); + for (const method of ["GET", "HEAD", "OPTIONS"]) { + await csrfFetch("/api/auth/session", { method, headers: { "x-csrf-token": "obsolete" } }); + } +}); + +test("mantiene cuerpos JSON, binarios, headers, cancelación y respuestas de error sin reintentar", async (context) => { + context.after(configureCsrfTokenReader(() => FIRST_TOKEN)); + const bodies = [ + JSON.stringify({ name: "Ana" }), + new File(["photo"], "photo.webp", { type: "image/webp" }), + ]; + const controller = new AbortController(); + const response = Response.json({ code: "FORBIDDEN" }, { status: 403 }); + const headers = new Headers({ + "content-type": "application/octet-stream", + "x-csrf-token": "obsolete", + }); + let calls = 0; + + context.mock.method(globalThis, "fetch", async (_path: string, options: RequestInit) => { + assert.equal(options.body, bodies[calls]); + assert.equal(options.signal, controller.signal); + assert.equal(options.credentials, "same-origin"); + assert.equal(new Headers(options.headers).get("x-csrf-token"), FIRST_TOKEN); + assert.equal(new Headers(options.headers).get("content-type"), "application/octet-stream"); + calls += 1; + + return response; + }); + for (const body of bodies) { + assert.equal( + await csrfFetch("/api/companies/photo", { + method: "POST", + body, + headers, + signal: controller.signal, + }), + response + ); + } + assert.equal(calls, 2); + assert.equal(headers.get("x-csrf-token"), "obsolete"); +}); + +test("rechaza tokens ausentes o inválidos y destinos externos antes de enviar una petición", (context) => { + const mock = context.mock.method(globalThis, "fetch", async () => new Response()); + + for (const token of [undefined, "short", "g".repeat(64)]) { + const reset = configureCsrfTokenReader(() => token); + + try { + assert.throws(() => csrfFetch("/api/users", { method: "POST" }), /CSRF_SESSION_UNAVAILABLE/); + } finally { + reset(); + } + } + context.after(configureCsrfTokenReader(() => FIRST_TOKEN)); + for (const path of [ + "https://outside.test/api/users", + "//outside.test/api/users", + "/api\\outside.test", + ]) { + assert.throws(() => csrfFetch(path, { method: "POST" }), /INVALID_API_PATH/); + } + assert.equal(mock.mock.callCount(), 0); +}); diff --git a/modules/core/src/web/http/shared/api/csrf-fetch.ts b/modules/core/src/web/http/shared/api/csrf-fetch.ts new file mode 100644 index 0000000..ce1d17f --- /dev/null +++ b/modules/core/src/web/http/shared/api/csrf-fetch.ts @@ -0,0 +1,35 @@ +type CsrfTokenReader = () => string | undefined; + +let readCsrfToken: CsrfTokenReader = () => undefined; + +/** La composición web aporta la sesión actual; no se duplica su token ni se usa storage. */ +export function configureCsrfTokenReader(reader: CsrfTokenReader) { + const previous = readCsrfToken; + + readCsrfToken = reader; + + return () => { + readCsrfToken = previous; + }; +} + +/** Añade CSRF únicamente a escrituras contra la API local, manteniendo body y errores. */ +export function csrfFetch(path: string, options: RequestInit = {}): Promise { + if (!path.startsWith("/api/") || path.includes("\\")) { + throw new Error("INVALID_API_PATH"); + } + const headers = new Headers(options.headers); + const method = (options.method ?? "GET").toUpperCase(); + + headers.delete("x-csrf-token"); + if (!["GET", "HEAD", "OPTIONS"].includes(method)) { + const token = readCsrfToken(); + + if (!(token && /^[a-f0-9]{64}$/.test(token))) { + throw new Error("CSRF_SESSION_UNAVAILABLE"); + } + headers.set("x-csrf-token", token); + } + + return fetch(path, { ...options, credentials: "same-origin", headers }); +} diff --git a/modules/core/src/web/index.ts b/modules/core/src/web/index.ts index 8a01fce..e83e42b 100644 --- a/modules/core/src/web/index.ts +++ b/modules/core/src/web/index.ts @@ -1,7 +1,7 @@ export { default as coreEnglish } from "../common/locales/en.json"; - export { default as coreSpanish } from "../common/locales/es.json"; +export { configureCsrfTokenReader, csrfFetch } from "./http/shared/api/csrf-fetch"; export { BrowserLink, EntityNotFound, @@ -16,5 +16,4 @@ export { useCreateNavigation, useEntityNavigation, } from "./navigation"; - export { HomePage } from "./workspace"; diff --git a/modules/identity/src/web/passwords/change/api/change-password.api.ts b/modules/identity/src/web/passwords/change/api/change-password.api.ts index a2b2de0..e1a638d 100644 --- a/modules/identity/src/web/passwords/change/api/change-password.api.ts +++ b/modules/identity/src/web/passwords/change/api/change-password.api.ts @@ -1,5 +1,5 @@ import { UserResponseSchema } from "../../../../common"; import { identityRequest } from "../../../shared/api/identity-request"; -export const changePassword = (body: unknown, token: string) => - identityRequest("/auth/change-password", UserResponseSchema, body, token); +export const changePassword = (body: unknown) => + identityRequest("/auth/change-password", UserResponseSchema, body); diff --git a/modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx b/modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx index 741ef49..207603b 100644 --- a/modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx +++ b/modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx @@ -3,15 +3,7 @@ import { useTranslation } from "react-i18next"; import { PasswordForm } from "../../shared"; -export function AccountPasswordDialog({ - open, - csrfToken, - onClose, -}: { - open: boolean; - csrfToken: string; - onClose: () => void; -}) { +export function AccountPasswordDialog({ open, onClose }: { open: boolean; onClose: () => void }) { const { t } = useTranslation("identity"); return ( @@ -26,7 +18,7 @@ export function AccountPasswordDialog({ open={open} title={t("changePassword")} > - + ); } diff --git a/modules/identity/src/web/passwords/reset/api/reset-user-password.api.ts b/modules/identity/src/web/passwords/reset/api/reset-user-password.api.ts index d7cdbdf..142d6d9 100644 --- a/modules/identity/src/web/passwords/reset/api/reset-user-password.api.ts +++ b/modules/identity/src/web/passwords/reset/api/reset-user-password.api.ts @@ -1,5 +1,5 @@ import { UserResponseSchema } from "../../../../common"; import { identityRequest } from "../../../shared/api/identity-request"; -export const resetUserPassword = (id: string, body: unknown, token: string) => - identityRequest(`/users/${id}/reset-password`, UserResponseSchema, body, token); +export const resetUserPassword = (id: string, body: unknown) => + identityRequest(`/users/${id}/reset-password`, UserResponseSchema, body); diff --git a/modules/identity/src/web/passwords/shared/controllers/use-password-form-controller.ts b/modules/identity/src/web/passwords/shared/controllers/use-password-form-controller.ts index fad865c..f703f9f 100644 --- a/modules/identity/src/web/passwords/shared/controllers/use-password-form-controller.ts +++ b/modules/identity/src/web/passwords/shared/controllers/use-password-form-controller.ts @@ -10,7 +10,7 @@ import { changePassword } from "../../change/api"; import { resetUserPassword } from "../../reset/api"; import type { PasswordFormProps } from "../types"; -export function usePasswordFormController({ target, csrfToken, onSaved }: PasswordFormProps) { +export function usePasswordFormController({ target, onSaved }: PasswordFormProps) { const client = useQueryClient(); const form = useForm({ resolver: zodResolver( @@ -25,10 +25,9 @@ export function usePasswordFormController({ target, csrfToken, onSaved }: Passwo target ? resetUserPassword( target.id, - ResetPasswordRequestSchema.parse({ password: values.password }), - csrfToken + ResetPasswordRequestSchema.parse({ password: values.password }) ) - : changePassword(values, csrfToken), + : changePassword(values), onSuccess: async () => { form.reset(); await client.invalidateQueries({ queryKey: identityKeys.session }); diff --git a/modules/identity/src/web/passwords/shared/types/password-form-props.ts b/modules/identity/src/web/passwords/shared/types/password-form-props.ts index 94f81b3..139f478 100644 --- a/modules/identity/src/web/passwords/shared/types/password-form-props.ts +++ b/modules/identity/src/web/passwords/shared/types/password-form-props.ts @@ -2,7 +2,6 @@ import type { UserDTO } from "../../../../common"; export type PasswordFormProps = { target?: UserDTO; - csrfToken: string; onSaved?: () => void; showTitle?: boolean; }; diff --git a/modules/identity/src/web/sessions/shared/api/authenticate.api.ts b/modules/identity/src/web/sessions/shared/api/authenticate.api.ts index 5022aa2..6a7e14b 100644 --- a/modules/identity/src/web/sessions/shared/api/authenticate.api.ts +++ b/modules/identity/src/web/sessions/shared/api/authenticate.api.ts @@ -1,5 +1,5 @@ import { UserResponseSchema } from "../../../../common"; import { identityRequest } from "../../../shared/api/identity-request"; -export const authenticate = (setup: boolean, body: unknown, token: string) => - identityRequest(setup ? "/auth/bootstrap" : "/auth/login", UserResponseSchema, body, token); +export const authenticate = (setup: boolean, body: unknown) => + identityRequest(setup ? "/auth/bootstrap" : "/auth/login", UserResponseSchema, body); diff --git a/modules/identity/src/web/sessions/shared/api/logout.api.ts b/modules/identity/src/web/sessions/shared/api/logout.api.ts index 873a0c3..83762d5 100644 --- a/modules/identity/src/web/sessions/shared/api/logout.api.ts +++ b/modules/identity/src/web/sessions/shared/api/logout.api.ts @@ -1,5 +1,4 @@ import { ActionResponseSchema } from "../../../../common"; import { identityRequest } from "../../../shared/api/identity-request"; -export const logout = (token: string) => - identityRequest("/auth/logout", ActionResponseSchema, {}, token); +export const logout = () => identityRequest("/auth/logout", ActionResponseSchema, {}); diff --git a/modules/identity/src/web/sessions/shared/controllers/use-credential-form-controller.ts b/modules/identity/src/web/sessions/shared/controllers/use-credential-form-controller.ts index da2f510..e0fc628 100644 --- a/modules/identity/src/web/sessions/shared/controllers/use-credential-form-controller.ts +++ b/modules/identity/src/web/sessions/shared/controllers/use-credential-form-controller.ts @@ -8,7 +8,7 @@ import { identityKeys } from "../../../shared/queries"; import { authenticate } from "../api"; import type { CredentialFormProps } from "../types"; -export function useCredentialFormController({ setup, csrfToken }: CredentialFormProps) { +export function useCredentialFormController({ setup }: CredentialFormProps) { const client = useQueryClient(); const form = useForm({ resolver: zodResolver( @@ -25,8 +25,7 @@ export function useCredentialFormController({ setup, csrfToken }: CredentialForm setup, setup ? BootstrapAdminRequestSchema.parse(values) - : LoginRequestSchema.parse({ email: values.email, password: values.password }), - csrfToken + : LoginRequestSchema.parse({ email: values.email, password: values.password }) ), onSuccess: async () => { client.removeQueries({ diff --git a/modules/identity/src/web/sessions/shared/types/credential-form-props.ts b/modules/identity/src/web/sessions/shared/types/credential-form-props.ts index 9803f28..ab7413b 100644 --- a/modules/identity/src/web/sessions/shared/types/credential-form-props.ts +++ b/modules/identity/src/web/sessions/shared/types/credential-form-props.ts @@ -1,4 +1,3 @@ export type CredentialFormProps = { setup: boolean; - csrfToken: string; }; diff --git a/modules/identity/src/web/shared/api/identity-api.test.ts b/modules/identity/src/web/shared/api/identity-api.test.ts index 8cec768..85ce029 100644 --- a/modules/identity/src/web/shared/api/identity-api.test.ts +++ b/modules/identity/src/web/shared/api/identity-api.test.ts @@ -1,13 +1,15 @@ import assert from "node:assert/strict"; import { test } from "node:test"; +import { configureCsrfTokenReader } from "@metis/core/client"; + import { getSession, logout } from "../../sessions/shared/api"; import { identityApi } from "./identity-api"; import { IdentityApiError } from "./identity-api-error"; const USER_ID = "e0420eb9-2aab-4368-96a0-2ce83dd23eb4"; -const CSRF_TOKEN = "a".repeat(32); +const CSRF_TOKEN = "a".repeat(64); const USER = { id: USER_ID, name: "Ana", @@ -17,6 +19,7 @@ const USER = { }; test("la sesión valida la respuesta y conserva el contrato público utilizado por la aplicación", async (context) => { + context.after(configureCsrfTokenReader(() => CSRF_TOKEN)); context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => { assert.equal(url, "/api/auth/session"); assert.equal(options.method, "GET"); @@ -34,6 +37,7 @@ test("la sesión valida la respuesta y conserva el contrato público utilizado p }); test("login, configuración inicial, usuarios y contraseñas conservan rutas, body y CSRF", async (context) => { + context.after(configureCsrfTokenReader(() => CSRF_TOKEN)); const calls: { url: unknown; options: RequestInit }[] = []; context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => { @@ -43,12 +47,12 @@ test("login, configuración inicial, usuarios y contraseñas conservan rutas, bo }); const body = { password: "example-password" }; - await identityApi.authenticate(false, body, CSRF_TOKEN); - await identityApi.authenticate(true, body, CSRF_TOKEN); - await identityApi.create(body, CSRF_TOKEN); - await identityApi.update(USER_ID, body, CSRF_TOKEN); - await identityApi.resetPassword(USER_ID, body, CSRF_TOKEN); - await identityApi.changePassword(body, CSRF_TOKEN); + await identityApi.authenticate(false, body); + await identityApi.authenticate(true, body); + await identityApi.create(body); + await identityApi.update(USER_ID, body); + await identityApi.resetPassword(USER_ID, body); + await identityApi.changePassword(body); assert.deepEqual( calls.map((call) => call.url), [ @@ -68,6 +72,7 @@ test("login, configuración inicial, usuarios y contraseñas conservan rutas, bo }); test("cerrar sesión espera la respuesta de la operación sin perder el token", async (context) => { + context.after(configureCsrfTokenReader(() => CSRF_TOKEN)); context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => { assert.equal(url, "/api/auth/logout"); assert.equal(options.body, "{}"); @@ -75,10 +80,11 @@ test("cerrar sesión espera la respuesta de la operación sin perder el token", return Response.json({ success: true }); }); - assert.deepEqual(await logout(CSRF_TOKEN), { success: true }); + assert.deepEqual(await logout(), { success: true }); }); test("los errores HTTP no filtran detalles internos y los datos de sesión inválidos se rechazan", async (context) => { + context.after(configureCsrfTokenReader(() => CSRF_TOKEN)); const responses = [ Response.json({ code: "FORBIDDEN", requestId: USER_ID }, { status: 403 }), Response.json({ sql: "private" }, { status: 500 }), diff --git a/modules/identity/src/web/shared/api/identity-request.ts b/modules/identity/src/web/shared/api/identity-request.ts index 6384d5d..712d3ee 100644 --- a/modules/identity/src/web/shared/api/identity-request.ts +++ b/modules/identity/src/web/shared/api/identity-request.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import type { z } from "zod/v4"; import { IdentityErrorResponseSchema } from "../../../common"; @@ -7,16 +8,12 @@ import { IdentityApiError } from "./identity-api-error"; export async function identityRequest( path: string, schema: z.ZodType, - body?: unknown, - csrfToken?: string + body?: unknown ): Promise { - const response = await fetch(`/api${path}`, { + const response = await csrfFetch(`/api${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": csrfToken ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const json: unknown = await response.json(); diff --git a/modules/identity/src/web/users/create/api/create-user.api.ts b/modules/identity/src/web/users/create/api/create-user.api.ts index a99c803..a70e6ba 100644 --- a/modules/identity/src/web/users/create/api/create-user.api.ts +++ b/modules/identity/src/web/users/create/api/create-user.api.ts @@ -1,5 +1,4 @@ import { UserResponseSchema } from "../../../../common"; import { identityRequest } from "../../../shared/api/identity-request"; -export const createUser = (body: unknown, token: string) => - identityRequest("/users", UserResponseSchema, body, token); +export const createUser = (body: unknown) => identityRequest("/users", UserResponseSchema, body); diff --git a/modules/identity/src/web/users/list/types/index.ts b/modules/identity/src/web/users/list/types/index.ts deleted file mode 100644 index 880d3f0..0000000 --- a/modules/identity/src/web/users/list/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { UserManagementProps } from "./user-management-props"; diff --git a/modules/identity/src/web/users/list/types/user-management-props.ts b/modules/identity/src/web/users/list/types/user-management-props.ts deleted file mode 100644 index 113dd75..0000000 --- a/modules/identity/src/web/users/list/types/user-management-props.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type UserManagementProps = { - csrfToken: string; -}; diff --git a/modules/identity/src/web/users/list/ui/user-management-dialog.tsx b/modules/identity/src/web/users/list/ui/user-management-dialog.tsx index 992631c..898b5a7 100644 --- a/modules/identity/src/web/users/list/ui/user-management-dialog.tsx +++ b/modules/identity/src/web/users/list/ui/user-management-dialog.tsx @@ -5,11 +5,9 @@ import { UserManagement } from "./user-management"; export function UserManagementDialog({ open, - csrfToken, onOpenChange, }: { open: boolean; - csrfToken: string; onOpenChange: (open: boolean) => void; }) { const { t } = useTranslation("identity"); @@ -22,7 +20,7 @@ export function UserManagementDialog({ title={t("users")} wide > - + ); } diff --git a/modules/identity/src/web/users/list/ui/user-management.tsx b/modules/identity/src/web/users/list/ui/user-management.tsx index 9827101..01cb1a7 100644 --- a/modules/identity/src/web/users/list/ui/user-management.tsx +++ b/modules/identity/src/web/users/list/ui/user-management.tsx @@ -11,10 +11,8 @@ import { useTranslation } from "react-i18next"; import { FormFeedback } from "../../../shared"; import { UserEditorDialog } from "../../shared"; import { useUserManagementController } from "../controllers"; -import type { UserManagementProps } from "../types"; -export function UserManagement(props: UserManagementProps) { - const { csrfToken } = props; +export function UserManagement() { const { users, createOpen, setCreateOpen, selection, setSelection, selected } = useUserManagementController(); const { t, i18n } = useTranslation("identity"); @@ -105,15 +103,9 @@ export function UserManagement(props: UserManagementProps) { title={t("users")} /> )} - setCreateOpen(false)} - open={createOpen} - /> + setCreateOpen(false)} open={createOpen} /> {selected && selection && ( setSelection(undefined)} open diff --git a/modules/identity/src/web/users/shared/controllers/use-user-form-controller.ts b/modules/identity/src/web/users/shared/controllers/use-user-form-controller.ts index f46697c..3ca0d77 100644 --- a/modules/identity/src/web/users/shared/controllers/use-user-form-controller.ts +++ b/modules/identity/src/web/users/shared/controllers/use-user-form-controller.ts @@ -10,7 +10,7 @@ import { updateUser } from "../../update/api"; import { UserFormSchema } from "../schemas"; import type { UserFormProps, UserFormValues } from "../types"; -export function useUserFormController({ user, csrfToken, onSaved }: UserFormProps) { +export function useUserFormController({ user, onSaved }: UserFormProps) { const client = useQueryClient(); const form = useForm({ resolver: zodResolver(user ? UserFormSchema.extend({ password: z.string() }) : UserFormSchema), @@ -30,16 +30,14 @@ export function useUserFormController({ user, csrfToken, onSaved }: UserFormProp name: values.name, email: values.email, active: values.active, - }), - csrfToken + }) ) : createUser( CreateUserRequestSchema.parse({ name: values.name, email: values.email, password: values.password, - }), - csrfToken + }) ), onSuccess: async () => { if (!user) { diff --git a/modules/identity/src/web/users/shared/types/user-form-props.ts b/modules/identity/src/web/users/shared/types/user-form-props.ts index f55f417..1f16c21 100644 --- a/modules/identity/src/web/users/shared/types/user-form-props.ts +++ b/modules/identity/src/web/users/shared/types/user-form-props.ts @@ -7,7 +7,6 @@ export type UserFormValues = z.infer; export type UserFormProps = { user?: UserDTO; - csrfToken: string; onSaved?: () => void; showTitle?: boolean; }; diff --git a/modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx b/modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx index 4e76267..1d00336 100644 --- a/modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx +++ b/modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx @@ -9,7 +9,6 @@ import { UserForm } from "./user-form"; export function UserEditorDialog( props: { open: boolean; - csrfToken: string; onClose: () => void; } & ({ mode: "create" } | { mode: "edit" | "password"; user: UserDTO }) ) { @@ -36,7 +35,6 @@ export function UserEditorDialog( > {props.mode === "password" ? ( ) : ( - identityRequest(`/users/${id}/update`, UserResponseSchema, body, token); +export const updateUser = (id: string, body: unknown) => + identityRequest(`/users/${id}/update`, UserResponseSchema, body); diff --git a/modules/identity/src/web/workspace/view/controllers/use-authenticated-workspace-controller.ts b/modules/identity/src/web/workspace/view/controllers/use-authenticated-workspace-controller.ts index ec96f13..8a39de2 100644 --- a/modules/identity/src/web/workspace/view/controllers/use-authenticated-workspace-controller.ts +++ b/modules/identity/src/web/workspace/view/controllers/use-authenticated-workspace-controller.ts @@ -3,9 +3,8 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { logout as logoutSession } from "../../../sessions/shared/api"; import { identityKeys } from "../../../shared/queries"; -import type { AuthenticatedWorkspaceProps } from "../types"; -export function useAuthenticatedWorkspaceController({ snapshot }: AuthenticatedWorkspaceProps) { +export function useAuthenticatedWorkspaceController() { const client = useQueryClient(); const location = useBrowserLocation(); const pathname = location.split("?")[0]; @@ -25,7 +24,7 @@ export function useAuthenticatedWorkspaceController({ snapshot }: AuthenticatedW }; const logout = useMutation({ - mutationFn: () => logoutSession(snapshot.csrfToken), + mutationFn: () => logoutSession(), onSuccess: async () => { client.removeQueries({ predicate: (query) => query.queryKey[0] !== "identity" || query.queryKey[1] !== "session", diff --git a/modules/identity/src/web/workspace/view/ui/authenticated-workspace.tsx b/modules/identity/src/web/workspace/view/ui/authenticated-workspace.tsx index 6ce2140..4ac4a2e 100644 --- a/modules/identity/src/web/workspace/view/ui/authenticated-workspace.tsx +++ b/modules/identity/src/web/workspace/view/ui/authenticated-workspace.tsx @@ -16,7 +16,7 @@ function isHomePage(page: string, isInitialAdmin: boolean) { export function AuthenticatedWorkspace(props: AuthenticatedWorkspaceProps) { const { snapshot, children, pages, renderWorkspace } = props; - const { page, setPage, logout } = useAuthenticatedWorkspaceController(props); + const { page, setPage, logout } = useAuthenticatedWorkspaceController(); const { t } = useTranslation("identity"); if (!snapshot.user) { @@ -82,11 +82,11 @@ export function AuthenticatedWorkspace(props: AuthenticatedWorkspaceProps) { ) : (
{page === "users" ? ( - + ) : page === "password" ? ( - + ) : ( diff --git a/modules/identity/src/web/workspace/view/ui/custom-workspace.tsx b/modules/identity/src/web/workspace/view/ui/custom-workspace.tsx index 90d9877..f8fec41 100644 --- a/modules/identity/src/web/workspace/view/ui/custom-workspace.tsx +++ b/modules/identity/src/web/workspace/view/ui/custom-workspace.tsx @@ -72,7 +72,6 @@ export function CustomWorkspace({
)} { if (!open) { setPage("home"); @@ -80,11 +79,7 @@ export function CustomWorkspace({ }} open={page === "users" && snapshot.user.isInitialAdmin} /> - setPage("home")} - open={page === "password"} - /> + setPage("home")} open={page === "password"} /> ); } diff --git a/modules/identity/src/web/workspace/view/ui/identity-workspace.tsx b/modules/identity/src/web/workspace/view/ui/identity-workspace.tsx index bfaf1b3..ad95c19 100644 --- a/modules/identity/src/web/workspace/view/ui/identity-workspace.tsx +++ b/modules/identity/src/web/workspace/view/ui/identity-workspace.tsx @@ -60,7 +60,6 @@ export function IdentityWorkspace(props: IdentityWorkspaceProps) { diff --git a/modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx b/modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx index 68f6a09..d9c1662 100644 --- a/modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx +++ b/modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx @@ -5,12 +5,10 @@ import { CatalogFieldForm } from "../../shared"; export function CatalogFieldCreateDialog({ scope, - csrfToken, onSaved, onOpenChange, }: { scope: string; - csrfToken: string; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; }) { @@ -25,7 +23,7 @@ export function CatalogFieldCreateDialog({ title={t("newField")} >

{t("quickNotice")}

- + ); } diff --git a/modules/resources/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts b/modules/resources/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts index f832fc9..2fa51de 100644 --- a/modules/resources/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts +++ b/modules/resources/src/web/catalog-fields/shared/controllers/use-catalog-form-controller.ts @@ -5,12 +5,7 @@ import { useCatalogSave } from "../../../catalog/shared/controllers"; import { FieldFormSchema } from "../schemas"; import type { CatalogFieldFormProps, FieldFormValues } from "../types"; -export function useCatalogFieldFormController({ - scope, - csrfToken, - item, - onSaved, -}: CatalogFieldFormProps) { +export function useCatalogFieldFormController({ scope, item, onSaved }: CatalogFieldFormProps) { const current = item?.values.kind === "field" ? item.values : undefined; const form = useForm({ resolver: zodResolver(FieldFormSchema), @@ -23,7 +18,7 @@ export function useCatalogFieldFormController({ choicesText: current?.choices.join("\n") ?? "", }, }); - const mutation = useCatalogSave(scope, csrfToken, onSaved, item?.id); + const mutation = useCatalogSave(scope, onSaved, item?.id); const type = form.watch("type"); const submit = form.handleSubmit(({ choicesText, ...values }) => mutation.mutate({ diff --git a/modules/resources/src/web/catalog-fields/shared/types/catalog-form-props.ts b/modules/resources/src/web/catalog-fields/shared/types/catalog-form-props.ts index 840378b..06149b8 100644 --- a/modules/resources/src/web/catalog-fields/shared/types/catalog-form-props.ts +++ b/modules/resources/src/web/catalog-fields/shared/types/catalog-form-props.ts @@ -8,7 +8,6 @@ export type FieldFormValues = z.infer; export type CatalogFieldFormProps = { scope: string; - csrfToken: string; item?: CatalogItemDTO | undefined; onSaved: CatalogSaved; }; diff --git a/modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx b/modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx index 6098616..be827d0 100644 --- a/modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx +++ b/modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx @@ -6,13 +6,11 @@ import { CatalogTemplateForm } from "../../shared"; export function CatalogTemplateCreateDialog({ scope, - csrfToken, items, onSaved, onOpenChange, }: { scope: string; - csrfToken: string; items: ComponentProps["items"]; onSaved: (id: string) => void; onOpenChange: (open: boolean) => void; @@ -29,13 +27,7 @@ export function CatalogTemplateCreateDialog({ wide >

{t("relatedNotice")}

- + ); } diff --git a/modules/resources/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts b/modules/resources/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts index d75ef17..d82da53 100644 --- a/modules/resources/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts +++ b/modules/resources/src/web/catalog-templates/shared/controllers/use-catalog-template-form-controller.ts @@ -12,7 +12,6 @@ import type { CatalogTemplateFormProps, TemplateFormValues } from "../types"; export function useCatalogTemplateFormController({ scope, - csrfToken, item, copy, items, @@ -38,7 +37,7 @@ export function useCatalogTemplateFormController({ sections: layout.sections, }, }); - const mutation = useCatalogSave(scope, csrfToken, onSaved, item?.id); + const mutation = useCatalogSave(scope, onSaved, item?.id); const selected = form.watch("fields"); const fields = items.filter((entry) => entry.values.kind === "field"); diff --git a/modules/resources/src/web/catalog-templates/shared/types/catalog-template-form-props.ts b/modules/resources/src/web/catalog-templates/shared/types/catalog-template-form-props.ts index f009f9e..afd7e40 100644 --- a/modules/resources/src/web/catalog-templates/shared/types/catalog-template-form-props.ts +++ b/modules/resources/src/web/catalog-templates/shared/types/catalog-template-form-props.ts @@ -8,7 +8,6 @@ export type TemplateFormValues = z.infer; export type CatalogTemplateFormProps = { scope: string; - csrfToken: string; item?: CatalogItemDTO | undefined; copy?: CatalogItemDTO | undefined; items: readonly CatalogItemDTO[]; diff --git a/modules/resources/src/web/catalog-templates/shared/ui/catalog-template-form.tsx b/modules/resources/src/web/catalog-templates/shared/ui/catalog-template-form.tsx index 25e39e2..7703f8a 100644 --- a/modules/resources/src/web/catalog-templates/shared/ui/catalog-template-form.tsx +++ b/modules/resources/src/web/catalog-templates/shared/ui/catalog-template-form.tsx @@ -17,7 +17,7 @@ import { useCatalogTemplateFormController } from "../controllers"; import type { CatalogTemplateFormProps } from "../types"; export function CatalogTemplateForm(props: CatalogTemplateFormProps) { - const { scope, csrfToken, item, allowQuickCreate = true } = props; + const { scope, item, allowQuickCreate = true } = props; const { creatingField, setCreatingField, form, mutation, selected, fields, add, submit } = useCatalogTemplateFormController(props); const { t } = useTranslation("resources"); @@ -139,7 +139,6 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) { {creatingField && ( { if (!open) { setCreatingField(false); diff --git a/modules/resources/src/web/catalog/list/controllers/use-catalog-page-controller.ts b/modules/resources/src/web/catalog/list/controllers/use-catalog-page-controller.ts index f2da745..c820b0c 100644 --- a/modules/resources/src/web/catalog/list/controllers/use-catalog-page-controller.ts +++ b/modules/resources/src/web/catalog/list/controllers/use-catalog-page-controller.ts @@ -7,7 +7,7 @@ import type { CatalogEditor } from "../../shared"; import { catalogApi, catalogKeys } from "../../shared/api"; import type { ResourceCatalogPageProps } from "../types"; -export function useResourceCatalogPageController({ scope, csrfToken }: ResourceCatalogPageProps) { +export function useResourceCatalogPageController({ scope }: ResourceCatalogPageProps) { const client = useQueryClient(); const { searchParams, setSearchParams } = useBrowserSearchParams(); const requestedTab = searchParams.get("tab"); @@ -41,7 +41,7 @@ export function useResourceCatalogPageController({ scope, csrfToken }: ResourceC }); const mutation = useMutation({ mutationFn: ({ item, active }: { item: CatalogItemDTO; active: boolean | null }) => - catalogApi.status(scope, item.id, active, csrfToken), + catalogApi.status(scope, item.id, active), onSuccess: async (response) => { await client.invalidateQueries({ queryKey: ["resource-catalog"] }); client.setQueryData(catalogKeys.scope(scope), response); diff --git a/modules/resources/src/web/catalog/list/types/catalog-page-props.ts b/modules/resources/src/web/catalog/list/types/catalog-page-props.ts index 64cffd2..307d7df 100644 --- a/modules/resources/src/web/catalog/list/types/catalog-page-props.ts +++ b/modules/resources/src/web/catalog/list/types/catalog-page-props.ts @@ -1,4 +1,3 @@ export type ResourceCatalogPageProps = { scope: string; - csrfToken: string; }; diff --git a/modules/resources/src/web/catalog/list/ui/catalog-page.tsx b/modules/resources/src/web/catalog/list/ui/catalog-page.tsx index ec37132..5fa19de 100644 --- a/modules/resources/src/web/catalog/list/ui/catalog-page.tsx +++ b/modules/resources/src/web/catalog/list/ui/catalog-page.tsx @@ -10,7 +10,7 @@ import type { ResourceCatalogPageProps } from "../types"; import { CatalogCell } from "./catalog-cell"; export function ResourceCatalogPage(props: ResourceCatalogPageProps) { - const { scope, csrfToken } = props; + const { scope } = props; const { tab, setTab, @@ -177,7 +177,6 @@ export function ResourceCatalogPage(props: ResourceCatalogPageProps) { setViewId(undefined)} /> {editor && ( setEditor(undefined)} diff --git a/modules/resources/src/web/catalog/shared/api/catalog-api.ts b/modules/resources/src/web/catalog/shared/api/catalog-api.ts index 2186fb4..aa90885 100644 --- a/modules/resources/src/web/catalog/shared/api/catalog-api.ts +++ b/modules/resources/src/web/catalog/shared/api/catalog-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { ResourceDefinitionSpec } from "../../../../common"; @@ -11,14 +12,11 @@ export class CatalogApiError extends Error { } } -async function request(path: string, body?: unknown, csrfToken?: string) { - const response = await fetch(`/api/resource-catalog/${path}`, { +async function request(path: string, body?: unknown) { + const response = await csrfFetch(`/api/resource-catalog/${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": csrfToken ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const json: unknown = await response.json(); @@ -36,14 +34,12 @@ export const catalogApi = { async get(scope: string) { return CatalogResponseSchema.parse(await request(scope)); }, - async save(scope: string, values: ResourceDefinitionSpec, csrfToken: string, id?: string) { + async save(scope: string, values: ResourceDefinitionSpec, id?: string) { return SaveDefinitionResponseSchema.parse( - await request(`${scope}${id ? `/${id}/update` : ""}`, { values }, csrfToken) + await request(`${scope}${id ? `/${id}/update` : ""}`, { values }) ); }, - async status(scope: string, id: string, active: boolean | null, csrfToken: string) { - return CatalogResponseSchema.parse( - await request(`${scope}/${id}/status`, { active }, csrfToken) - ); + async status(scope: string, id: string, active: boolean | null) { + return CatalogResponseSchema.parse(await request(`${scope}/${id}/status`, { active })); }, }; diff --git a/modules/resources/src/web/catalog/shared/catalog.test.tsx b/modules/resources/src/web/catalog/shared/catalog.test.tsx index d008a9c..6a2b27c 100644 --- a/modules/resources/src/web/catalog/shared/catalog.test.tsx +++ b/modules/resources/src/web/catalog/shared/catalog.test.tsx @@ -48,7 +48,7 @@ test("el catálogo muestra plantillas de serie y acción de alta sin formularios ["en", "New template", "Actions"], ]) { assert.ok(language && create && actions); - const html = await render(language, ); + const html = await render(language, ); assert.ok(html.includes("Buzón de correo")); assert.ok(html.includes(create)); @@ -58,17 +58,14 @@ test("el catálogo muestra plantillas de serie y acción de alta sin formularios } }); test("los formularios tienen ayuda inferior y selector de campo con alta integrada", async () => { - const field = await render( - "es", - undefined} scope="global" /> - ); + const field = await render("es", undefined} scope="global" />); assert.ok(field.indexOf('name="description"') < field.indexOf(" undefined} scope="global" /> + undefined} scope="global" /> ); assert.ok(template.includes('aria-label="Nuevo campo"')); diff --git a/modules/resources/src/web/catalog/shared/controllers/use-catalog-save.ts b/modules/resources/src/web/catalog/shared/controllers/use-catalog-save.ts index 9c6a669..1c12029 100644 --- a/modules/resources/src/web/catalog/shared/controllers/use-catalog-save.ts +++ b/modules/resources/src/web/catalog/shared/controllers/use-catalog-save.ts @@ -6,12 +6,7 @@ import { catalogApi, catalogKeys } from "../api"; export type CatalogSaved = (id: string, data: SaveDefinitionResponseDTO) => void; -export function useCatalogSave( - scope: string, - csrfToken: string, - onSaved: CatalogSaved, - id?: string -) { +export function useCatalogSave(scope: string, onSaved: CatalogSaved, id?: string) { const client = useQueryClient(); const mounted = useRef(true); @@ -24,7 +19,7 @@ export function useCatalogSave( }, []); return useMutation({ - mutationFn: (values: ResourceDefinitionSpec) => catalogApi.save(scope, values, csrfToken, id), + mutationFn: (values: ResourceDefinitionSpec) => catalogApi.save(scope, values, id), onSuccess: async (response) => { await client.invalidateQueries({ queryKey: ["resource-catalog"] }); client.setQueryData(catalogKeys.scope(scope), { items: response.items }); diff --git a/modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx b/modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx index 3541569..97f386a 100644 --- a/modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx +++ b/modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx @@ -23,13 +23,11 @@ export function CatalogEditorDialog({ editor, items, scope, - csrfToken, onClose, }: { editor: CatalogEditor; items: readonly CatalogItemDTO[]; scope: string; - csrfToken: string; onClose: () => void; }) { const { t } = useTranslation("resources"); @@ -48,16 +46,10 @@ export function CatalogEditorDialog({ wide={editor.kind === "template"} > {editor.kind === "field" ? ( - onClose()} - scope={scope} - /> + onClose()} scope={scope} /> ) : ( onClose()} diff --git a/modules/resources/src/web/resources/list/types/resources-page-props.ts b/modules/resources/src/web/resources/list/types/resources-page-props.ts index 4f648f5..f94f775 100644 --- a/modules/resources/src/web/resources/list/types/resources-page-props.ts +++ b/modules/resources/src/web/resources/list/types/resources-page-props.ts @@ -4,7 +4,6 @@ import type { ResourceDTO } from "../../../../common"; export type ResourcesPageProps = { companyId: string; - csrfToken: string; relatedContent?: (resource: ResourceDTO) => ReactNode; initialId?: string | undefined; initialCreate?: boolean | undefined; diff --git a/modules/resources/src/web/resources/list/ui/resources-page.tsx b/modules/resources/src/web/resources/list/ui/resources-page.tsx index 19299ce..5cf8a1c 100644 --- a/modules/resources/src/web/resources/list/ui/resources-page.tsx +++ b/modules/resources/src/web/resources/list/ui/resources-page.tsx @@ -16,7 +16,7 @@ import { useResourcesPageController } from "../controllers"; import type { ResourcesPageProps } from "../types"; export function ResourcesPage(props: ResourcesPageProps) { - const { companyId, csrfToken, relatedContent } = props; + const { companyId, relatedContent } = props; const { search, setSearch, @@ -129,7 +129,6 @@ export function ResourcesPage(props: ResourcesPageProps) { {editor && catalog.data && ( setEditor(undefined)} onSaved={(id) => { diff --git a/modules/resources/src/web/resources/shared/api/inventory-api.ts b/modules/resources/src/web/resources/shared/api/inventory-api.ts index e63d5f1..8b5509e 100644 --- a/modules/resources/src/web/resources/shared/api/inventory-api.ts +++ b/modules/resources/src/web/resources/shared/api/inventory-api.ts @@ -1,3 +1,4 @@ +import { csrfFetch } from "@metis/core/client"; import { z } from "zod/v4"; import type { ResourceInput } from "../../../../common/inventory"; @@ -11,14 +12,11 @@ export class InventoryApiError extends Error { } } -async function request(path: string, body?: unknown, csrfToken?: string) { - const response = await fetch(`/api/resources/${path}`, { +async function request(path: string, body?: unknown) { + const response = await csrfFetch(`/api/resources/${path}`, { method: body === undefined ? "GET" : "POST", credentials: "same-origin", - headers: - body === undefined - ? {} - : { "content-type": "application/json", "x-csrf-token": csrfToken ?? "" }, + headers: body === undefined ? {} : { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const data: unknown = await response.json(); @@ -36,9 +34,9 @@ export const inventoryApi = { async get(companyId: string) { return InventoryResponseSchema.parse(await request(companyId)); }, - async save(companyId: string, values: ResourceInput, token: string, id?: string) { + async save(companyId: string, values: ResourceInput, id?: string) { return ResourceResponseSchema.parse( - await request(`${companyId}${id ? `/${id}/update` : ""}`, { values }, token) + await request(`${companyId}${id ? `/${id}/update` : ""}`, { values }) ); }, }; diff --git a/modules/resources/src/web/resources/shared/controllers/use-resource-form-controller.ts b/modules/resources/src/web/resources/shared/controllers/use-resource-form-controller.ts index 4b5e140..3115321 100644 --- a/modules/resources/src/web/resources/shared/controllers/use-resource-form-controller.ts +++ b/modules/resources/src/web/resources/shared/controllers/use-resource-form-controller.ts @@ -12,7 +12,6 @@ import type { ResourceFormProps } from "../types"; export function useResourceFormController({ companyId, - csrfToken, resource, items, onSaved, @@ -66,8 +65,7 @@ export function useResourceFormController({ ); const helpPrefix = isSoftwareLicense ? "license" : "resource"; const mutation = useMutation({ - mutationFn: (input: ResourceInput) => - inventoryApi.save(companyId, input, csrfToken, resource?.id), + mutationFn: (input: ResourceInput) => inventoryApi.save(companyId, input, resource?.id), onSuccess: async (response) => { await Promise.all( [ diff --git a/modules/resources/src/web/resources/shared/resources.test.tsx b/modules/resources/src/web/resources/shared/resources.test.tsx index 7bf1f0e..c10b7c5 100644 --- a/modules/resources/src/web/resources/shared/resources.test.tsx +++ b/modules/resources/src/web/resources/shared/resources.test.tsx @@ -52,7 +52,7 @@ test("recursos: listado con alta modal y sin formulario visible, en español e i ["en", "New resource"], ]) { assert.ok(language && label); - const html = await render(language, ); + const html = await render(language, ); assert.ok(html.includes(label)); assert.equal(html.includes(" { const html = await render( "es", - undefined} /> + undefined} /> ); assert.ok(html.includes('aria-label="Nueva plantilla"')); @@ -74,7 +74,6 @@ test("recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de undefined} /> @@ -125,7 +124,6 @@ test("licencia: formulario reutilizable con campos opcionales y ayuda antes de g language, undefined} resource={resource} diff --git a/modules/resources/src/web/resources/shared/types/resource-editor-props.ts b/modules/resources/src/web/resources/shared/types/resource-editor-props.ts index 046b421..d0ebb54 100644 --- a/modules/resources/src/web/resources/shared/types/resource-editor-props.ts +++ b/modules/resources/src/web/resources/shared/types/resource-editor-props.ts @@ -1,6 +1,5 @@ export type ResourceEditorProps = { companyId: string; - csrfToken: string; resourceId?: string | undefined; initialKind?: "mailbox" | "access" | "application" | "certificate" | undefined; initialAddress?: string; diff --git a/modules/resources/src/web/resources/shared/types/resource-form-props.ts b/modules/resources/src/web/resources/shared/types/resource-form-props.ts index 78c8bf9..f064cea 100644 --- a/modules/resources/src/web/resources/shared/types/resource-form-props.ts +++ b/modules/resources/src/web/resources/shared/types/resource-form-props.ts @@ -3,7 +3,6 @@ import type { ResourceDTO, ResourceInput } from "../../../../common/inventory"; export type ResourceFormProps = { companyId: string; - csrfToken: string; resource?: ResourceDTO | undefined; items: readonly CatalogItemDTO[]; onSaved: (id: string) => void; diff --git a/modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx b/modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx index 4378fee..1c670e1 100644 --- a/modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx +++ b/modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx @@ -7,14 +7,12 @@ import { ResourceForm } from "./resource-form"; export function ResourceEditorDialog({ resource, companyId, - csrfToken, items, onClose, onSaved, }: { resource: ComponentProps["resource"]; companyId: string; - csrfToken: string; items: ComponentProps["items"]; onClose: () => void; onSaved: (id: string) => void; @@ -34,13 +32,7 @@ export function ResourceEditorDialog({ title={t(resource ? "editResource" : "newResource")} wide > - + ); } diff --git a/modules/resources/src/web/resources/shared/ui/resource-editor.tsx b/modules/resources/src/web/resources/shared/ui/resource-editor.tsx index ebc2266..d977032 100644 --- a/modules/resources/src/web/resources/shared/ui/resource-editor.tsx +++ b/modules/resources/src/web/resources/shared/ui/resource-editor.tsx @@ -9,7 +9,6 @@ import { ResourceForm } from "./resource-form"; export function ResourceEditor(props: ResourceEditorProps) { const { companyId, - csrfToken, resourceId, initialKind, initialAddress = "", @@ -46,7 +45,6 @@ export function ResourceEditor(props: ResourceEditorProps) { {creatingTemplate && ( { if (!open) {