From 1b6c238c682bf3629906a688372b504b40fd5163 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 18:39:47 +0200 Subject: [PATCH] refactor(desktop): harden OAuth redirect rewrite for host variants and self-hosters The interceptor previously matched a strict `${HOSTED_FRONTEND_URL}/*` prefix and did a naive String.replace, which broke whenever the backend NEXT_FRONTEND_URL differed at all (apex vs www, http vs https, or a self-hosted domain). Now: - Match by host: apex + www. sibling, both http and https. - Rewrite via URL parsing so only protocol/host change; query strings containing the host as a value are left intact. - Read HOSTED_FRONTEND_URL through getHostedFrontendUrl() which honors a SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE env var, letting self-hosters point their builds at their own frontend without rebuilding. Default behavior is identical when override is unset and backend host matches the baked-in value. --- surfsense_desktop/.env.example | 5 ++++ surfsense_desktop/src/modules/window.ts | 40 +++++++++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/surfsense_desktop/.env.example b/surfsense_desktop/.env.example index e127b99e0..2d9de7561 100644 --- a/surfsense_desktop/.env.example +++ b/surfsense_desktop/.env.example @@ -5,6 +5,11 @@ # inside the desktop app. Set to your production frontend domain. HOSTED_FRONTEND_URL=https://surfsense.net +# Runtime override for the above (read at app start, no rebuild required). +# Useful for self-hosters whose backend NEXT_FRONTEND_URL differs from the +# value baked into the official desktop builds. Leave empty to use HOSTED_FRONTEND_URL. +# SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE= + # PostHog analytics (leave empty to disable) POSTHOG_KEY= POSTHOG_HOST=https://assets.surfsense.com diff --git a/surfsense_desktop/src/modules/window.ts b/surfsense_desktop/src/modules/window.ts index 5317005d5..8d60e05f5 100644 --- a/surfsense_desktop/src/modules/window.ts +++ b/surfsense_desktop/src/modules/window.ts @@ -6,9 +6,26 @@ import { getServerPort } from './server'; import { setActiveSearchSpaceId } from './active-search-space'; const isDev = !app.isPackaged; -const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string; const isMac = process.platform === 'darwin'; +function getHostedFrontendUrl(): string { + return ( + process.env.SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE || + process.env.HOSTED_FRONTEND_URL || + 'https://surfsense.net' + ); +} + +function getHostedFrontendHosts(): string[] { + try { + const host = new URL(getHostedFrontendUrl()).host; + const sibling = host.startsWith('www.') ? host.slice(4) : `www.${host}`; + return Array.from(new Set([host, sibling])); + } catch { + return []; + } +} + let mainWindow: BrowserWindow | null = null; let isQuitting = false; @@ -58,11 +75,22 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { return { action: 'deny' }; }); - const filter = { urls: [`${HOSTED_FRONTEND_URL}/*`] }; - session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => { - const rewritten = details.url.replace(HOSTED_FRONTEND_URL, `http://localhost:${getServerPort()}`); - callback({ redirectURL: rewritten }); - }); + const hostedHosts = getHostedFrontendHosts(); + const rewriteFilter = { + urls: hostedHosts.flatMap((h) => [`http://${h}/*`, `https://${h}/*`]), + }; + if (rewriteFilter.urls.length > 0) { + session.defaultSession.webRequest.onBeforeRequest(rewriteFilter, (details, callback) => { + try { + const u = new URL(details.url); + u.protocol = 'http:'; + u.host = `localhost:${getServerPort()}`; + callback({ redirectURL: u.toString() }); + } catch { + callback({}); + } + }); + } mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => { console.error(`Failed to load ${validatedURL}: ${errorDescription} (${errorCode})`);