92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
import { useEffect, useState, type ReactElement } from 'react';
|
|
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
|
import { api } from '@/lib/api';
|
|
import { resolveAuthCallbackURL } from '@/lib/auth-redirect';
|
|
import { signIn } from '@/lib/auth-client';
|
|
import type { SsoProviderDiscovery } from '@/lib/sso';
|
|
|
|
export function SsoCallbackPage(): ReactElement {
|
|
const { provider: providerId = '' } = useParams<'provider'>();
|
|
const [searchParams] = useSearchParams();
|
|
const requestedCallbackURL = searchParams.get('callbackURL');
|
|
const [providerName, setProviderName] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
async function redirectToProvider(): Promise<void> {
|
|
try {
|
|
const callbackURL = resolveAuthCallbackURL(requestedCallbackURL, window.location.origin);
|
|
const providers = await api<SsoProviderDiscovery[]>('/api/sso/providers');
|
|
if (cancelled) return;
|
|
|
|
const provider = providers.find((candidate) => candidate.id === providerId);
|
|
if (!provider) {
|
|
setError('Unknown SSO provider.');
|
|
return;
|
|
}
|
|
|
|
setProviderName(provider.name);
|
|
if (!provider.configured) {
|
|
setError(`${provider.name} is not enabled in this deployment.`);
|
|
return;
|
|
}
|
|
if (provider.loginMode !== 'oidc') {
|
|
setError(`${provider.name} is not available for OIDC sign in.`);
|
|
return;
|
|
}
|
|
|
|
const result = await signIn.oauth2({
|
|
providerId: provider.id,
|
|
callbackURL,
|
|
});
|
|
|
|
if (!cancelled && result?.error) {
|
|
setError(result.error.message ?? `${provider.name} sign in failed.`);
|
|
}
|
|
} catch (caught: unknown) {
|
|
if (!cancelled) {
|
|
setError(caught instanceof Error ? caught.message : 'Unable to start single sign-on.');
|
|
}
|
|
}
|
|
}
|
|
|
|
void redirectToProvider();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [providerId, requestedCallbackURL]);
|
|
|
|
return (
|
|
<div className="mx-auto flex min-h-[50vh] max-w-md flex-col justify-center">
|
|
<h1 className="text-2xl font-semibold text-text-primary">Single sign-on</h1>
|
|
<p className="mt-2 text-sm text-text-secondary">
|
|
{providerName
|
|
? `Redirecting you to ${providerName}...`
|
|
: 'Preparing your sign-in request...'}
|
|
</p>
|
|
|
|
{error ? (
|
|
<div
|
|
role="alert"
|
|
className="mt-6 rounded-lg border border-error/30 bg-error/10 px-4 py-3 text-sm text-error"
|
|
>
|
|
<p>{error}</p>
|
|
<Link
|
|
to="/login"
|
|
className="mt-3 inline-block font-medium text-blue-400 hover:text-blue-300"
|
|
>
|
|
Return to login
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<div className="mt-6 rounded-lg border border-surface-border bg-surface-elevated px-4 py-3 text-sm text-text-secondary">
|
|
If the redirect does not start automatically, return to the login page and try again.
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|