import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; import { corsHeaders } from '../_shared/cors.ts'; import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { validateUUID } from '../_shared/typeValidation.ts'; export default createEdgeFunction( { name: 'mfa-unenroll', requireAuth: true, corsHeaders: corsHeaders }, async (req, context) => { const supabaseClient = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_ANON_KEY')!, { global: { headers: { Authorization: req.headers.get('Authorization')! } } } ); context.span.setAttribute('action', 'mfa_unenroll'); // Phase 1: Check AAL level const { data: { session } } = await supabaseClient.auth.getSession(); const { data: aalData } = await supabaseClient.auth.mfa.getAuthenticatorAssuranceLevel(); const aal = aalData?.currentLevel || 'aal1'; if (aal !== 'aal2') { return new Response( JSON.stringify({ error: 'AAL2 required to remove MFA' }), { status: 403, headers: { 'Content-Type': 'application/json' } } ); } // Phase 2: Check if user's role requires MFA const { data: roles } = await supabaseClient .from('user_roles') .select('role') .eq('user_id', context.userId); const requiresMFA = roles?.some(r => ['admin', 'moderator', 'superuser'].includes(r.role)); if (requiresMFA) { return new Response( JSON.stringify({ error: 'Your role requires MFA and it cannot be disabled' }), { status: 403, headers: { 'Content-Type': 'application/json' } } ); } // Phase 3: Check rate limit (2 attempts per 24 hours) const { data: recentAttempts } = await supabaseClient .from('admin_audit_log') .select('created_at') .eq('admin_user_id', context.userId) .eq('action', 'mfa_disabled') .gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()); if (recentAttempts && recentAttempts.length >= 2) { return new Response( JSON.stringify({ error: 'Rate limit exceeded. Try again in 24 hours.' }), { status: 429, headers: { 'Content-Type': 'application/json' } } ); } // Get factor ID from request const { factorId } = await req.json(); validateUUID(factorId, 'factorId', { userId: context.userId, requestId: context.requestId }); // Phase 4: Proceed with unenrollment const { error: unenrollError } = await supabaseClient.auth.mfa.unenroll({ factorId }); if (unenrollError) { throw new Error(unenrollError.message); } // Audit log the action await supabaseClient.from('admin_audit_log').insert({ admin_user_id: context.userId, target_user_id: context.userId, action: 'mfa_disabled', details: { factorId, timestamp: new Date().toISOString(), user_agent: req.headers.get('user-agent') || 'unknown', aal_level: aal } }); // Send security notification try { await supabaseClient.functions.invoke('trigger-notification', { body: { userId: context.userId, workflowId: 'security-alert', payload: { action: 'MFA Disabled', message: 'Two-factor authentication has been disabled on your account.', timestamp: new Date().toISOString() } } }); } catch (notifError) { // Non-blocking notification failure } return new Response( JSON.stringify({ success: true }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } );