From 49ec5edffbc708321bec39fba50b1fa723457564 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 5 Jan 2026 22:37:43 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20resolve=20desktop=20uploa?= =?UTF-8?q?d=20CORS=20issue=20(#11255)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 fix: resolve desktop upload CORS issue Expand CORS bypass to handle all HTTP/HTTPS requests in desktop app. Previously, CORS bypass only applied to local file server (127.0.0.1), which caused upload failures when the renderer uses app:// protocol. Changes: - Remove Origin header from all requests to prevent CORS preflight - Add permissive CORS headers to all responses - Update comments to reflect the new behavior Resolves LOBE-2581 * 🐛 fix: enhance CORS handling in desktop app Refine CORS bypass implementation to store and utilize the original Origin header for responses. This change ensures proper CORS headers are added based on the request's origin, improving compatibility with credentialed requests and OPTIONS preflight handling. Changes: - Store Origin header for each request and remove it to prevent CORS preflight. - Add CORS headers to responses using the stored origin. - Implement caching for OPTIONS requests with a max age. Resolves LOBE-2581 Signed-off-by: Innei * 🐛 fix: add onBeforeSendHeaders mock to Browser tests Enhance the Browser test suite by adding a mock for the onBeforeSendHeaders function in the session's webRequest object. This addition improves the test coverage for CORS handling scenarios. Signed-off-by: Innei --------- Signed-off-by: Innei --- apps/desktop/src/main/core/browser/Browser.ts | 68 +++++++++++++------ .../core/browser/__tests__/Browser.test.ts | 1 + 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/apps/desktop/src/main/core/browser/Browser.ts b/apps/desktop/src/main/core/browser/Browser.ts index 78fae6b1f3..bce6c60383 100644 --- a/apps/desktop/src/main/core/browser/Browser.ts +++ b/apps/desktop/src/main/core/browser/Browser.ts @@ -374,14 +374,10 @@ export default class Browser { | undefined; logger.info(`Creating new BrowserWindow instance: ${this.identifier}`); logger.debug(`[${this.identifier}] Options for new window: ${JSON.stringify(this.options)}`); - logger.debug( - `[${this.identifier}] Saved window state: ${JSON.stringify(savedState)}`, - ); + logger.debug(`[${this.identifier}] Saved window state: ${JSON.stringify(savedState)}`); const resolvedState = this.resolveWindowState(savedState, { height, width }); - logger.debug( - `[${this.identifier}] Resolved window state: ${JSON.stringify(resolvedState)}`, - ); + logger.debug(`[${this.identifier}] Resolved window state: ${JSON.stringify(resolvedState)}`); const browserWindow = new BrowserWindow({ ...res, @@ -569,33 +565,65 @@ export default class Browser { } /** - * Setup CORS bypass for local file server (127.0.0.1:*) - * This is needed for Electron to access files from the local static file server + * Setup CORS bypass for ALL requests + * In production, the renderer uses app://next protocol which triggers CORS for all external requests + * This completely bypasses CORS by: + * 1. Removing Origin header from requests (prevents OPTIONS preflight) + * 2. Adding proper CORS response headers using the stored origin value */ private setupCORSBypass(browserWindow: BrowserWindow): void { - logger.debug(`[${this.identifier}] Setting up CORS bypass for local file server`); + logger.debug(`[${this.identifier}] Setting up CORS bypass for all requests`); const session = browserWindow.webContents.session; - // Intercept response headers to add CORS headers + // Store origin values for each request ID + const originMap = new Map(); + + // Remove Origin header and store it for later use + session.webRequest.onBeforeSendHeaders((details, callback) => { + const requestHeaders = { ...details.requestHeaders }; + + // Store and remove Origin header to prevent CORS preflight + if (requestHeaders['Origin']) { + originMap.set(details.id, requestHeaders['Origin']); + delete requestHeaders['Origin']; + logger.debug( + `[${this.identifier}] Removed Origin header for: ${details.url} (stored: ${requestHeaders['Origin']})`, + ); + } + + callback({ requestHeaders }); + }); + + // Add CORS headers to ALL responses using stored origin session.webRequest.onHeadersReceived((details, callback) => { - const url = details.url; + const responseHeaders = details.responseHeaders || {}; - // Only modify headers for local file server requests (127.0.0.1) - if (url.includes('127.0.0.1') || url.includes('lobe-desktop-file')) { - const responseHeaders = details.responseHeaders || {}; + // Get the original origin from our map, fallback to default + const origin = originMap.get(details.id) || '*'; - // Add CORS headers - responseHeaders['Access-Control-Allow-Origin'] = ['*']; - responseHeaders['Access-Control-Allow-Methods'] = ['GET, POST, PUT, DELETE, OPTIONS']; - responseHeaders['Access-Control-Allow-Headers'] = ['*']; + // Cannot use '*' when Access-Control-Allow-Credentials is true + responseHeaders['Access-Control-Allow-Origin'] = [origin]; + responseHeaders['Access-Control-Allow-Methods'] = ['GET, POST, PUT, DELETE, OPTIONS, PATCH']; + responseHeaders['Access-Control-Allow-Headers'] = ['*']; + responseHeaders['Access-Control-Allow-Credentials'] = ['true']; + + // Clean up the stored origin after response + originMap.delete(details.id); + + // For OPTIONS requests, add preflight cache and override status + if (details.method === 'OPTIONS') { + responseHeaders['Access-Control-Max-Age'] = ['86400']; // 24 hours + logger.debug(`[${this.identifier}] Adding CORS headers to OPTIONS response`); callback({ responseHeaders, + statusLine: 'HTTP/1.1 200 OK', }); - } else { - callback({ responseHeaders: details.responseHeaders }); + return; } + + callback({ responseHeaders }); }); logger.debug(`[${this.identifier}] CORS bypass setup completed`); diff --git a/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts b/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts index 891bdf7fe7..a1778d5d97 100644 --- a/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +++ b/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts @@ -36,6 +36,7 @@ const { mockBrowserWindow, mockNativeTheme, mockIpcMain, mockScreen, MockBrowser send: vi.fn(), session: { webRequest: { + onBeforeSendHeaders: vi.fn(), onHeadersReceived: vi.fn(), }, },