diff --git a/ui/src/auth/OidcCallback.tsx b/ui/src/auth/OidcCallback.tsx index c1158d07..2def73f9 100644 --- a/ui/src/auth/OidcCallback.tsx +++ b/ui/src/auth/OidcCallback.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef } from 'react'; import { Navigate, useNavigate } from 'react-router'; import { useAuthStore } from './auth-store'; +import { api } from '../api/client'; import { Card, Spinner, Alert, Button } from '@cameleer/design-system'; import { config } from '../config'; @@ -18,11 +19,31 @@ export function OidcCallback() { const errorParam = params.get('error'); if (errorParam) { - // prompt=none SSO attempt failed (no active session) — fall back to login form + // prompt=none failed — no session, fall back to login form if (errorParam === 'login_required' || errorParam === 'interaction_required') { window.location.replace(`${config.basePath}login?local`); return; } + // consent_required — retry without prompt=none so user can grant scopes + if (errorParam === 'consent_required' && !sessionStorage.getItem('oidc-consent-retry')) { + sessionStorage.setItem('oidc-consent-retry', '1'); + api.GET('/auth/oidc/config').then(({ data }) => { + if (data?.authorizationEndpoint && data?.clientId) { + const redirectUri = `${window.location.origin}${config.basePath}oidc/callback`; + const p = new URLSearchParams({ + response_type: 'code', + client_id: data.clientId, + redirect_uri: redirectUri, + scope: 'openid email profile', + }); + window.location.href = `${data.authorizationEndpoint}?${p}`; + } + }).catch(() => { + window.location.replace(`${config.basePath}login?local`); + }); + return; + } + sessionStorage.removeItem('oidc-consent-retry'); useAuthStore.setState({ error: params.get('error_description') || errorParam, loading: false, @@ -30,6 +51,8 @@ export function OidcCallback() { return; } + sessionStorage.removeItem('oidc-consent-retry'); + if (!code) { useAuthStore.setState({ error: 'No authorization code received', loading: false }); return;