'use client'; import { useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { createSupabaseBrowserClient } from '@/lib/supabase/browser'; import { PasskeyEnrollPanel, RecoveryCodesPanel, TotpEnrollPanel, } from './mfa-components'; type FactorView = { id: string; type: string; friendlyName: string | null; createdAt: string; }; const TYPE_LABEL: Record = { totp: 'Authenticator app', webauthn: 'Passkey', phone: 'Phone', }; /** * Security settings client: list verified factors, add new ones, remove * factors (never the last one — mandatory MFA), and regenerate recovery codes. */ export function SecurityClient({ initialFactors, }: { initialFactors: FactorView[]; }) { const router = useRouter(); const supabase = useMemo(() => createSupabaseBrowserClient(), []); const [factors] = useState(initialFactors); const [adding, setAdding] = useState(null); const [busyId, setBusyId] = useState(null); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [codes, setCodes] = useState(null); const [regenBusy, setRegenBusy] = useState(false); async function remove(id: string) { if (factors.length <= 1) { setError('You must keep at least one two-factor method enabled.'); return; } setError(null); setNotice(null); setBusyId(id); try { const { error } = await supabase.auth.mfa.unenroll({ factorId: id }); if (error) throw error; router.refresh(); } catch (e) { setError((e as Error).message); } finally { setBusyId(null); } } function onAdded() { setAdding(null); setNotice('Two-factor method added.'); router.refresh(); } async function regenerate() { setError(null); setNotice(null); setCodes(null); setRegenBusy(true); try { const res = await fetch('/api/security/recovery/generate', { method: 'POST', }); const json = (await res.json()) as { codes?: string[]; error?: string }; if (!res.ok || !json.codes) { throw new Error(json.error || 'Could not generate recovery codes.'); } setCodes(json.codes); } catch (e) { setError((e as Error).message); } finally { setRegenBusy(false); } } return (
{error &&

{error}

} {notice &&

{notice}

}

Your two-factor methods

{factors.length === 0 ? (

No methods enabled.

) : ( {factors.map((f) => ( ))}
Method Name Added
{TYPE_LABEL[f.type] ?? f.type} {f.friendlyName ?? '—'} {new Date(f.createdAt).toLocaleDateString()}
)}
{adding === 'totp' && ( )} {adding === 'webauthn' && ( )} {adding === null && (

Add a method

)}

Recovery codes

Generate a new set of single-use recovery codes. This invalidates any codes you were issued before.

{codes && }
); }