import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; // Helper function to decode JWT and extract user ID // Properly handles base64url encoding used by JWTs function decodeJWT(token: string): { sub: string } | null { try { const parts = token.split('.'); if (parts.length !== 3) return null; // JWT uses base64url encoding, convert to standard base64 let base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/'); // Add padding if needed while (base64.length % 4) { base64 += '='; } // Decode and parse the payload const decoded = atob(base64); const payload = JSON.parse(decoded); return payload; } catch (error) { console.error('JWT decode error:', error); return null; } } Deno.serve(async (req) => { // Handle CORS preflight requests if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } try { // Create admin client with service role key const supabaseAdmin = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '', { auth: { autoRefreshToken: false, persistSession: false } } ); // Get the user from the authorization header const authHeader = req.headers.get('Authorization'); if (!authHeader) { console.error('Missing authorization header'); throw new Error('No authorization header provided. Please ensure you are logged in.'); } const token = authHeader.replace('Bearer ', ''); console.log('Extracting user ID from JWT token...'); // Parse JWT token to get user ID directly const payload = decodeJWT(token); if (!payload || !payload.sub) { console.error('Invalid JWT token structure'); throw new Error('Invalid session token. Please refresh the page and try again.'); } const userId = payload.sub; console.log(`Cancelling email change for user ${userId}`); // Call the database function to clear email change fields // This function has SECURITY DEFINER privileges to access auth.users const { data: cancelled, error: cancelError } = await supabaseAdmin .rpc('cancel_user_email_change', { _user_id: userId }); if (cancelError || !cancelled) { console.error('Error cancelling email change:', cancelError); throw new Error('Unable to cancel email change: ' + (cancelError?.message || 'Unknown error')); } console.log(`Successfully cancelled email change for user ${userId}`); // Log the cancellation in admin_audit_log const { error: auditError } = await supabaseAdmin .from('admin_audit_log') .insert({ admin_user_id: userId, target_user_id: userId, action: 'email_change_cancelled', details: { cancelled_at: new Date().toISOString(), }, }); if (auditError) { console.error('Error logging audit:', auditError); // Don't fail the request if audit logging fails } return new Response( JSON.stringify({ success: true, message: 'Email change cancelled successfully', user: { id: userId, email: null, new_email: null, }, }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 200, } ); } catch (error) { console.error('Error in cancel-email-change function:', error); return new Response( JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'An unknown error occurred', }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 400, } ); } });