diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 1bc481aad7..34fceaa918 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -f6bc5647cc41ce348111f781b4a0db2153a28f8a +b7157d55fb9f8d8f7aeb7b1fb69bc935af895a2f diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 5212392dd4..f1318e8569 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -5966,6 +5966,9 @@ paths: - type saml: type: string + auto_login: + type: string + description: provider type to auto-redirect to on login (oauth key or "saml") required: - oauth diff --git a/backend/windmill-api/src/oauth2_oss.rs b/backend/windmill-api/src/oauth2_oss.rs index 3eaf179634..abdcc42548 100644 --- a/backend/windmill-api/src/oauth2_oss.rs +++ b/backend/windmill-api/src/oauth2_oss.rs @@ -92,11 +92,12 @@ pub struct TokenResponse { struct Logins { oauth: Vec, saml: Option, + auto_login: Option, } #[cfg(not(feature = "private"))] async fn list_logins() -> error::JsonResult { // Implementation is not open source - return Ok(Json(Logins { oauth: vec![], saml: None })); + return Ok(Json(Logins { oauth: vec![], saml: None, auto_login: None })); } #[allow(unused)] diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 836d37971e..6e6486666c 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -50,6 +50,7 @@ pub const HUB_API_SECRET_SETTING: &str = "hub_api_secret"; pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation"; pub const DISABLE_PASSWORD_LOGIN_SETTING: &str = "disable_password_login"; +pub const AUTO_LOGIN_PROVIDER_SETTING: &str = "auto_login_provider"; pub const HUB_BASE_URL_SETTING: &str = "hub_base_url"; pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url"; pub const DISABLE_HUB_SETTING: &str = "disable_hub"; diff --git a/frontend/src/lib/components/Login.svelte b/frontend/src/lib/components/Login.svelte index 091676bde7..b737ef8513 100644 --- a/frontend/src/lib/components/Login.svelte +++ b/frontend/src/lib/components/Login.svelte @@ -30,6 +30,7 @@ error?: string | undefined popup?: boolean firstTime?: boolean + autoRedirect?: boolean onLoginSuccess?: () => void } @@ -40,6 +41,7 @@ error = undefined, popup = false, firstTime = false, + autoRedirect = true, onLoginSuccess = undefined }: Props = $props() @@ -93,6 +95,7 @@ let saml: string | undefined = $state(undefined) let smtpConfigured: boolean | undefined = $state(undefined) let disablePasswordLogin = $state(false) + let autoRedirecting = $state(false) type OAuthLogin = { type: string @@ -201,12 +204,14 @@ console.error('Could not load password login setting', disabledResult.reason) } + let autoLogin: string | undefined = undefined if (loginsResult.status === 'fulfilled') { logins = loginsResult.value.oauth.map((login) => ({ type: login.type, displayName: login.display_name || login.type })) saml = loginsResult.value.saml + autoLogin = loginsResult.value.auto_login } else { logins = [] saml = undefined @@ -216,6 +221,28 @@ showPassword = !disablePasswordLogin && ((logins?.length === 0 && !saml) || (email != undefined && email.length > 0)) + + if (autoRedirect && autoLogin && !error && !shouldSkipAutoRedirect()) { + if (autoLogin === 'saml' && saml) { + autoRedirecting = true + if (!redirectSaml()) autoRedirecting = false + } else if (logins?.some((l) => l.type === autoLogin)) { + autoRedirecting = true + if (!storeRedirect(autoLogin)) { + autoRedirecting = false + sendUserToast('Popup blocked — please click the sign-in button to continue.', true) + } + } + } + } + + function shouldSkipAutoRedirect(): boolean { + try { + const params = new URLSearchParams(window.location.search) + return params.get('no_sso') === '1' + } catch { + return false + } } loadLogins() @@ -299,7 +326,7 @@ window.removeEventListener('storage', handleStorageEvent) }) - function storeRedirect(provider: string) { + function persistRd() { if (rd) { try { localStorage.setItem('rd', rd) @@ -307,6 +334,10 @@ console.error('Could not persist redirection to local storage', e) } } + } + + function storeRedirect(provider: string): boolean { + persistRd() let url = base + '/api/oauth/login/' + provider + (popup ? '?close=true' : '') console.log('storeRedirect', popup, url) @@ -314,20 +345,44 @@ localStorage.setItem('closeUponLogin', 'true') window.addEventListener('message', popupListener) window.addEventListener('storage', handleStorageEvent) - window.open(url, '_blank', 'popup') + const win = window.open(url, '_blank', 'popup') + if (!win) { + window.removeEventListener('message', popupListener) + window.removeEventListener('storage', handleStorageEvent) + return false + } + return true } else { localStorage.setItem('closeUponLogin', 'false') window.location.href = url + return true } } + function redirectSaml(): boolean { + if (!saml) { + sendUserToast('No SAML login available', true) + return false + } + persistRd() + window.location.href = saml + return true + } + $effect(() => { error && sendUserToast(escapeHtml(error), true) })
-
+ {#if autoRedirecting} +

Signing you in…

+ {/if} +
{#if !logins} {#each Array(4) as _} @@ -355,29 +410,10 @@ {/each} {/if} {#if saml} - + {/if}
- {#if !disablePasswordLogin && (saml || (logins && logins.length > 0))} + {#if !autoRedirecting && !disablePasswordLogin && (saml || (logins && logins.length > 0))}
0 ? 'mt-6' : '')}>