diff --git a/apps/pwa/src/api/client.ts b/apps/pwa/src/api/client.ts index 7067342..743acd9 100644 --- a/apps/pwa/src/api/client.ts +++ b/apps/pwa/src/api/client.ts @@ -219,11 +219,15 @@ export async function fetchAdminResetPassword( /** * POST /api/me/link-oidc — initiate the OIDC-link flow for the current local user (Surface 13). * - * The server returns a redirect URL to begin the OIDC authorization-code flow with a - * state parameter encoding the linkUserId claim. The caller should follow the redirect - * via top-level navigation (window.location.href = result.redirectUrl). + * The server returns the OIDC authorization endpoint URL (with a signed `state` parameter + * encoding the linkUserId claim) to begin the authorization-code flow. The caller should + * follow it via top-level navigation (window.location.href = authorizationUrl) when present. + * + * authorizationUrl is null when OIDC is not configured in env (the server cannot build the + * URL); callers MUST handle that case and surface an error instead of navigating to null. + * The server contract is { signedState, authorizationUrl } (see apps/api/src/routes/me.ts). */ -export async function fetchLinkOidc(): Promise<{ redirectUrl: string }> { +export async function fetchLinkOidc(): Promise<{ authorizationUrl: string | null }> { const res = await fetch('/api/me/link-oidc', { method: 'POST', credentials: 'include', @@ -234,7 +238,7 @@ export async function fetchLinkOidc(): Promise<{ redirectUrl: string }> { if (!res.ok) { throw new Error(`fetchLinkOidc failed: ${res.status}`); } - return res.json() as Promise<{ redirectUrl: string }>; + return res.json() as Promise<{ signedState: string; authorizationUrl: string | null }>; } // ── /api/me ──────────────────────────────────────────────────────────────── diff --git a/apps/pwa/src/components/SettingsSheet.tsx b/apps/pwa/src/components/SettingsSheet.tsx index 287af72..59f5b1a 100644 --- a/apps/pwa/src/components/SettingsSheet.tsx +++ b/apps/pwa/src/components/SettingsSheet.tsx @@ -859,9 +859,15 @@ function LinkOidcSheet({ isOpen, onClose }: LinkOidcSheetProps) { const linkMutation = useMutation({ mutationFn: fetchLinkOidc, onSuccess: (data) => { - // Close the sheet and initiate OIDC link flow + // authorizationUrl is null when OIDC is not configured in env (server could not + // build the URL). Do NOT navigate to null — surface an error and keep the sheet open. + if (!data.authorizationUrl) { + setError('Something went wrong. Please try again.'); + return; + } + // Close the sheet and initiate the OIDC link flow via top-level navigation. onClose(); - window.location.href = data.redirectUrl; + window.location.href = data.authorizationUrl; }, onError: () => { setError('Something went wrong. Please try again.');