48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
import { redirect } from 'next/navigation';
|
|
import { createSupabaseServerClient } from '@/lib/supabase/server';
|
|
import { safeNextPath } from '@/lib/auth/mfa';
|
|
import { ChallengeClient } from './challenge-client';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
/**
|
|
* AAL2 step-up challenge. The middleware sends authenticated aal1 users here
|
|
* when they have verified factors. Users with no verified factors are sent to
|
|
* enrollment instead; users already at aal2 are forwarded to their target.
|
|
*/
|
|
export default async function ChallengePage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: { next?: string };
|
|
}) {
|
|
const supabase = createSupabaseServerClient();
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser();
|
|
if (!user) redirect('/login');
|
|
|
|
const next = safeNextPath(searchParams.next);
|
|
|
|
const { data: aal } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
|
|
if (aal?.currentLevel === 'aal2') redirect(next);
|
|
|
|
const { data: factors } = await supabase.auth.mfa.listFactors();
|
|
const verified = factors?.all.filter((f) => f.status === 'verified') ?? [];
|
|
if (verified.length === 0) redirect('/security/enroll');
|
|
|
|
const totp = verified.find((f) => f.factor_type === 'totp');
|
|
const passkey = verified.find((f) => f.factor_type === 'webauthn');
|
|
|
|
return (
|
|
<main className="container">
|
|
<h1>Verify it's you</h1>
|
|
<p className="muted">Complete two-factor authentication to continue.</p>
|
|
<ChallengeClient
|
|
totpFactorId={totp?.id ?? null}
|
|
passkeyFactorId={passkey?.id ?? null}
|
|
next={next}
|
|
/>
|
|
</main>
|
|
);
|
|
}
|