import { NextResponse, type NextRequest } from 'next/server'; import { createSupabaseServerClient } from '@/lib/supabase/server'; import { jsonNoStore } from '@/lib/admin/response'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; /** * Remove one of the current user's MFA factors. Requires an aal2 session. * * Mandatory MFA means every user must keep at least one VERIFIED factor. The * client disables the "Remove" button on the last factor, but that is only a * first line of defense — this route is the real server-side guard: if deleting * the requested factor would leave the user with zero verified factors we * refuse with 409 and never call GoTrue. The factor must belong to the caller. */ export async function POST(request: NextRequest): Promise { const supabase = createSupabaseServerClient(); const { data: { user }, } = await supabase.auth.getUser(); if (!user) return jsonNoStore({ error: 'unauthorized' }, { status: 401 }); const { data: aal } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel(); if (aal?.currentLevel !== 'aal2') { return jsonNoStore({ error: 'aal2_required' }, { status: 403 }); } let body: { factorId?: string }; try { body = (await request.json()) as { factorId?: string }; } catch { return jsonNoStore({ error: 'bad_request' }, { status: 400 }); } const factorId = (body.factorId ?? '').trim(); if (!factorId) { return jsonNoStore({ error: 'invalid_factor' }, { status: 400 }); } const { data: list, error: listErr } = await supabase.auth.mfa.listFactors(); if (listErr) return jsonNoStore({ error: 'failed' }, { status: 500 }); const all = list?.all ?? []; const target = all.find((f) => f.id === factorId); if (!target) { return jsonNoStore({ error: 'invalid_factor' }, { status: 404 }); } // Only VERIFIED factors count as protection. If the user would be left with // no verified factors after this removal, refuse. const verified = all.filter((f) => f.status === 'verified'); const remainingVerified = verified.filter((f) => f.id !== factorId).length; if (target.status === 'verified' && remainingVerified === 0) { return jsonNoStore( { error: 'cannot_remove_last_factor' }, { status: 409 }, ); } const { error: unenrollErr } = await supabase.auth.mfa.unenroll({ factorId }); if (unenrollErr) { return jsonNoStore({ error: unenrollErr.message }, { status: 400 }); } return jsonNoStore({ ok: true }); }