From 1a7a4ba58fc5f62637af179be00cab906d3c179a Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Fri, 18 Sep 2026 16:35:01 -0300 Subject: [PATCH] fix(work-orders): open extra documents without credentialed CORS (SH-382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Viewing a work-order extra document called the authorized content endpoint with `credentials: "include"`. The API replies `Access-Control-Allow-Origin: *`, and in credentialed mode the browser rejects a wildcard origin outright, so the fetch threw, `openExtraDocContent` fell into its catch, closed the tab and toasted "Unable to open this document." — QA CT-03. The endpoint authenticates with the bearer token the ky beforeRequest hook attaches, not cookies, so credentials mode was dead weight. List, upload and delete never set it, which is why only viewing failed. Drop the option and guard the request shape in a test (JSDOM cannot enforce CORS). --- .../work-orders/api/work-order-board-documents-api.ts | 6 +++++- src/test/domain/work-orders/api/work-orders-api.test.ts | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/domain/work-orders/api/work-order-board-documents-api.ts b/src/domain/work-orders/api/work-order-board-documents-api.ts index 99c060fd..fbd7bf77 100644 --- a/src/domain/work-orders/api/work-order-board-documents-api.ts +++ b/src/domain/work-orders/api/work-order-board-documents-api.ts @@ -216,10 +216,14 @@ export const workOrderBoardDocumentsApi = { workOrderId: string | number, mediaId: string | number, ): Promise => { + // No `credentials: "include"`: this endpoint authenticates with the bearer token the ky + // beforeRequest hook attaches, not cookies. In credentialed mode the browser rejects the + // API's `Access-Control-Allow-Origin: *` outright, so the fetch would throw and the document + // would never open. const response = await apiRequestRaw( "get", API_PATHS.workOrder.mediaContent(workOrderId, mediaId), - { credentials: "include", throwHttpErrors: false }, + { throwHttpErrors: false }, "workOrderBoardDocumentsApi.getMediaContent", ); diff --git a/src/test/domain/work-orders/api/work-orders-api.test.ts b/src/test/domain/work-orders/api/work-orders-api.test.ts index e295ed9d..cbfabfa9 100644 --- a/src/test/domain/work-orders/api/work-orders-api.test.ts +++ b/src/test/domain/work-orders/api/work-orders-api.test.ts @@ -1022,8 +1022,14 @@ describe("workOrdersApi.getMediaContent", () => { expect(result).toBe(blob); expect(apiGetFn).toHaveBeenCalledWith( API_PATHS.workOrder.mediaContent(10, 12), - expect.objectContaining({ credentials: "include", throwHttpErrors: false }), + expect.objectContaining({ throwHttpErrors: false }), ); + // Regression guard (SH-382): a credentialed cross-origin fetch is rejected by the browser + // against the API's `Access-Control-Allow-Origin: *`, so the content request must not opt + // into credentials mode — otherwise the document never opens. JSDOM cannot enforce CORS, so + // this asserts the request shape rather than reproducing the block. + const [, options] = apiGetFn.mock.calls[0] as [string, Record]; + expect(options).not.toHaveProperty("credentials"); }); it("throws ApiError on 404 without inventing a filename", async () => {