import { NextResponse } from 'next/server'; import { createSupabaseServerClient } from '@/lib/supabase/server'; import { getSupabaseAdmin } from '@/lib/supabase/admin'; import { jsonNoStore } from '@/lib/admin/response'; import { generateRecoveryCodes, hashRecoveryCode } from '@/lib/auth/recovery'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; /** * Generate a fresh set of recovery codes for the current user. Requires an * aal2 session (the user just verified a factor). Any previously-issued codes * are replaced. The plaintext codes are returned once and never persisted. */ export async function POST(): 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 }); } const codes = generateRecoveryCodes(10); const admin = getSupabaseAdmin(); const del = await admin .from('mfa_recovery_codes') .delete() .eq('user_id', user.id); if (del.error) return jsonNoStore({ error: 'failed' }, { status: 500 }); const rows = codes.map((c) => ({ user_id: user.id, code_hash: hashRecoveryCode(c), })); const ins = await admin.from('mfa_recovery_codes').insert(rows); if (ins.error) return jsonNoStore({ error: 'failed' }, { status: 500 }); return jsonNoStore({ codes }); }