From 75955cbf1e9447244720d1d3af01d8a39c7565b7 Mon Sep 17 00:00:00 2001 From: James Brunton Date: Thu, 18 Dec 2025 11:04:32 +0000 Subject: [PATCH] revert me --- .../src/main/resources/application.properties | 3 ++ frontend/src/desktop/services/authService.ts | 46 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/app/core/src/main/resources/application.properties b/app/core/src/main/resources/application.properties index 60c9baee6..2ecb4e7ee 100644 --- a/app/core/src/main/resources/application.properties +++ b/app/core/src/main/resources/application.properties @@ -63,5 +63,8 @@ spring.main.allow-bean-definition-overriding=true # Set up a consistent temporary directory location java.io.tmpdir=${stirling.tempfiles.directory:${java.io.tmpdir}/stirling-pdf} +# Force Google OAuth to always show account chooser (self-hosted SSO desktop parity with SaaS) +spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth?prompt=select_account + # V2 features v2=true diff --git a/frontend/src/desktop/services/authService.ts b/frontend/src/desktop/services/authService.ts index e11e8967a..a4f90a596 100644 --- a/frontend/src/desktop/services/authService.ts +++ b/frontend/src/desktop/services/authService.ts @@ -516,10 +516,33 @@ export class AuthService { */ async loginWithSelfHostedOAuth(providerPath: string, serverUrl: string): Promise { const trimmedServer = serverUrl.replace(/\/+$/, ''); - const fullUrl = providerPath.startsWith('http') + const baseUrl = providerPath.startsWith('http') ? providerPath : `${trimmedServer}${providerPath.startsWith('/') ? providerPath : `/${providerPath}`}`; + // Force account chooser like SaaS: add prompt=select_account (and max_age=0 to force re-auth) + const urlObj = new URL(baseUrl, trimmedServer); + urlObj.searchParams.set('prompt', 'select_account'); + urlObj.searchParams.set('max_age', '0'); + + // Ensure any provider-specific "login_hint" or session cookies are ignored by requiring explicit approval + urlObj.searchParams.set('approval_prompt', 'force'); // for older OAuth providers + urlObj.searchParams.set('access_type', 'offline'); // common param, sometimes influences prompt behavior + + // Break Keycloak/Okta/Google session by adding a random state to defeat cached redirect + urlObj.searchParams.set('state', `${crypto.randomUUID?.() ?? Date.now().toString(36)}-desktop`); + + // For Google specifically, be extra aggressive about forcing the chooser/consent + const isGoogle = providerPath.toLowerCase().includes('google'); + if (isGoogle) { + urlObj.searchParams.set('prompt', 'select_account consent'); + urlObj.searchParams.set('include_granted_scopes', 'false'); + urlObj.searchParams.delete('login_hint'); + urlObj.searchParams.set('authuser', '-1'); // tell Google to show account selector + } + + const fullUrl = urlObj.toString(); + // Ensure backend redirects back to /auth/callback try { document.cookie = `stirling_redirect_path=${encodeURIComponent('/auth/callback')}; path=/; max-age=300; SameSite=Lax`; @@ -528,7 +551,26 @@ export class AuthService { } // Force a real popup so the main webview stays on the app - const authWindow = window.open(fullUrl, 'stirling-desktop-sso', 'width=900,height=900'); + let authWindow: Window | null = null; + const popupName = `stirling-desktop-sso-${Date.now()}`; + + if (isGoogle) { + // Hit the Google AccountChooser directly with prompt=select_account and continue to our OAuth URL + const googleAccountChooser = `https://accounts.google.com/AccountChooser?prompt=select_account&continue=${encodeURIComponent(fullUrl)}`; + authWindow = window.open(googleAccountChooser, popupName, 'width=900,height=900'); + // Fallback: if navigation fails, push to OAuth URL shortly after + setTimeout(() => { + try { + if (authWindow && !authWindow.closed) { + authWindow.location.assign(fullUrl); + } + } catch (err) { + console.warn('Failed to navigate Google popup to OAuth URL', err); + } + }, 1500); + } else { + authWindow = window.open(fullUrl, popupName, 'width=900,height=900'); + } // Fallback: use Tauri shell.open and wait for deep link back if (!authWindow) {