mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🐛 fix: resolve desktop upload CORS issue (#11255)
* 🐛 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 <tukon479@gmail.com> * 🐛 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 <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -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<number, string>();
|
||||
|
||||
// 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`);
|
||||
|
||||
@@ -36,6 +36,7 @@ const { mockBrowserWindow, mockNativeTheme, mockIpcMain, mockScreen, MockBrowser
|
||||
send: vi.fn(),
|
||||
session: {
|
||||
webRequest: {
|
||||
onBeforeSendHeaders: vi.fn(),
|
||||
onHeadersReceived: vi.fn(),
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user