diff --git a/ui/src/auth/LoginPage.tsx b/ui/src/auth/LoginPage.tsx index 17df6779..63609aed 100644 --- a/ui/src/auth/LoginPage.tsx +++ b/ui/src/auth/LoginPage.tsx @@ -47,11 +47,43 @@ export function LoginPage() { const [password, setPassword] = useState(''); const [oidcLoading, setOidcLoading] = useState(false); + // Mirrors cameleer-saas: when logout sets this flag, render a "Signed out" + // confirmation instead of the regular form. The flag is one-shot — read + + // cleared on mount. + const [signedOut] = useState(() => { + const flag = sessionStorage.getItem('cameleer:signed_out'); + if (flag) sessionStorage.removeItem('cameleer:signed_out'); + return !!flag; + }); + const { data: caps, isError: capsFailed, isLoading: capsLoading } = useAuthCapabilities(); if (isAuthenticated) return ; if (capsLoading) return null; + if (signedOut) { + return ( +
+ +
+
+ + cameleer +
+

You have been signed out successfully.

+ +
+
+
+ ); + } + const oidcPrimary = caps?.oidc?.primary === true; const adminRecoveryOnly = caps?.localAccounts?.adminRecoveryOnly === true; const providerName = caps?.oidc?.providerName || 'Single Sign-On'; @@ -87,10 +119,13 @@ export function LoginPage() { client_id: data.clientId, redirect_uri: redirectUri, scope: scopes.join(' '), + // Defence-in-depth: even if RP-Initiated Logout did not fully clear + // the IdP session (proxy/cookie edge cases), prompt=login forces the + // IdP to re-prompt for credentials instead of silent re-auth. + // OIDC Core 1.0 §3.1.2.1. + prompt: 'login', }); if (data.resource) params.set('resource', data.resource); - // Note: NO prompt=none. Per RFC 9700 §4.4, that's silent re-auth only; - // for first-time login it returns login_required and traps users on a local form. window.location.href = `${data.authorizationEndpoint}?${params}`; } catch { useAuthStore.setState({ error: 'OIDC configuration unavailable. Try the local form via /login?local.' });