feat(auth): mandatory 2FA (TOTP + WebAuthn passkeys) with hard enrollment gate, AAL2 step-up, and single-use recovery codes
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import { createSupabaseServerClient } from '@/lib/supabase/server';
|
||||
import { SecurityClient } from './security-client';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/**
|
||||
* Security settings: manage MFA factors and regenerate recovery codes. The
|
||||
* middleware guarantees the visitor is authenticated and at aal2 (mandatory
|
||||
* MFA), so factor mutations here always run with the required assurance level.
|
||||
*/
|
||||
export default async function SecurityPage() {
|
||||
const supabase = createSupabaseServerClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) redirect('/login');
|
||||
|
||||
const { data: factors } = await supabase.auth.mfa.listFactors();
|
||||
const verified =
|
||||
factors?.all
|
||||
.filter((f) => f.status === 'verified')
|
||||
.map((f) => ({
|
||||
id: f.id,
|
||||
type: f.factor_type,
|
||||
friendlyName: f.friendly_name ?? null,
|
||||
createdAt: f.created_at,
|
||||
})) ?? [];
|
||||
|
||||
return (
|
||||
<main className="container">
|
||||
<h1>Security</h1>
|
||||
<p className="muted">Manage two-factor authentication for your account.</p>
|
||||
<SecurityClient initialFactors={verified} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user