import { Box, Button, Icon, Icons, Spinner, Text, color, config } from 'folds'; import React, { useCallback, useEffect } from 'react'; import { createClient } from 'matrix-js-sdk'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { setFallbackSession } from '../../../state/sessions'; import { completeAuthorizationCodeGrant } from './oidcLoginUtil'; import { getOidcCallbackUrl } from './oidcConfig'; import { parseOidcCallbackParams } from './oidcState'; /** * Exchange the authorization code for a Matrix session and persist it. The SDK * (oidc-client-ts) reads the transient PKCE/state it stored in sessionStorage * before the redirect and validates `state` (CSRF). `redirectUri` is the * deterministic callback URL, recomputed here. */ const completeOidcLogin = async (code: string, state: string): Promise => { const { tokenResponse, homeserverUrl, oidcClientSettings, idTokenClaims } = await completeAuthorizationCodeGrant(code, state); // Derive Matrix user_id + device_id from the freshly-issued access token (the // device is owned by the authentication service under MSC3861). const tmp = createClient({ baseUrl: homeserverUrl, accessToken: tokenResponse.access_token }); const { user_id: userId, device_id: deviceId } = await tmp.whoami(); if (!userId || !deviceId) { throw new Error('Homeserver did not return a user/device for the OIDC session.'); } setFallbackSession(tokenResponse.access_token, deviceId, userId, homeserverUrl, { refreshToken: tokenResponse.refresh_token, expiresInMs: tokenResponse.expires_in ? tokenResponse.expires_in * 1000 : undefined, oidc: { issuer: oidcClientSettings.issuer, clientId: oidcClientSettings.clientId, redirectUri: getOidcCallbackUrl(), idTokenClaims: idTokenClaims as unknown as Record, }, }); }; function CallbackError({ message }: { message: string }) { return ( Sign-in failed {message} {/* Rendered outside the router, so use a plain navigation to the app root. */} ); } /** * Standalone page mounted by App.tsx for the OIDC redirect path (before the * router, since the redirect_uri must be a real non-hash path). */ export function OidcCallback() { const params = parseOidcCallbackParams(window.location.search); const [state, complete] = useAsyncCallback( useCallback(completeOidcLogin, []), ); useEffect(() => { if (params.kind === 'success') complete(params.code, params.state); }, [params, complete]); useEffect(() => { // Session persisted — full-page reload at the app root so it boots the // authenticated client (works for both hash and browser router configs). if (state.status === AsyncStatus.Success) { window.location.replace(import.meta.env.BASE_URL); } }, [state.status]); if (params.kind === 'error') { return ; } if (params.kind === 'invalid') { return ( ); } if (state.status === AsyncStatus.Error) { return ; } return ( Signing you in… ); }