From 13626922cc7e57ced427a7dae425210e1a927442 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:57:30 +0000 Subject: [PATCH] feat: Create edge function to cancel email changes --- src/components/settings/AccountProfileTab.tsx | 24 ++-- supabase/config.toml | 3 + .../functions/cancel-email-change/index.ts | 103 ++++++++++++++++++ 3 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 supabase/functions/cancel-email-change/index.ts diff --git a/src/components/settings/AccountProfileTab.tsx b/src/components/settings/AccountProfileTab.tsx index 47246372..9480e495 100644 --- a/src/components/settings/AccountProfileTab.tsx +++ b/src/components/settings/AccountProfileTab.tsx @@ -148,12 +148,16 @@ export function AccountProfileTab() { setCancellingEmail(true); try { - // Reset email to current email (effectively cancels the pending change) - const { error: updateError } = await supabase.auth.updateUser({ - email: user.email + // Call the edge function to cancel the email change with admin privileges + const { data, error } = await supabase.functions.invoke('cancel-email-change', { + method: 'POST', }); - if (updateError) throw updateError; + if (error) throw error; + + if (!data?.success) { + throw new Error(data?.error || 'Failed to cancel email change'); + } // Update Novu subscriber back to current email if (notificationService.isEnabled()) { @@ -164,18 +168,6 @@ export function AccountProfileTab() { }); } - // Log the cancellation in audit log - await supabase.from('admin_audit_log').insert({ - admin_user_id: user.id, - target_user_id: user.id, - action: 'email_change_cancelled', - details: { - cancelled_email: pendingEmail, - current_email: user.email, - timestamp: new Date().toISOString() - } - }); - sonnerToast.success('Email change cancelled', { description: 'Your email change request has been cancelled.' }); diff --git a/supabase/config.toml b/supabase/config.toml index 69b49fbe..6cff6a14 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -22,4 +22,7 @@ verify_jwt = false verify_jwt = true [functions.send-escalation-notification] +verify_jwt = true + +[functions.cancel-email-change] verify_jwt = true \ No newline at end of file diff --git a/supabase/functions/cancel-email-change/index.ts b/supabase/functions/cancel-email-change/index.ts new file mode 100644 index 00000000..acfdba11 --- /dev/null +++ b/supabase/functions/cancel-email-change/index.ts @@ -0,0 +1,103 @@ +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', +}; + +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) { + throw new Error('No authorization header'); + } + + const token = authHeader.replace('Bearer ', ''); + const { data: { user }, error: userError } = await supabaseAdmin.auth.getUser(token); + + if (userError || !user) { + throw new Error('Unauthorized'); + } + + console.log(`Cancelling email change for user ${user.id}`); + + // Use admin client to update the user and clear email_change fields + const { data: updatedUser, error: updateError } = await supabaseAdmin.auth.admin.updateUserById( + user.id, + { + email_confirm_change: user.email, // Reset to current email + } + ); + + if (updateError) { + console.error('Error cancelling email change:', updateError); + throw updateError; + } + + console.log(`Successfully cancelled email change for user ${user.id}`); + + // Log the cancellation in admin_audit_log + const { error: auditError } = await supabaseAdmin + .from('admin_audit_log') + .insert({ + admin_user_id: user.id, + target_user_id: user.id, + action: 'email_change_cancelled', + details: { + cancelled_at: new Date().toISOString(), + current_email: user.email, + }, + }); + + 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: updatedUser.user.id, + email: updatedUser.user.email, + new_email: updatedUser.user.new_email, + }, + }), + { + 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.message, + }), + { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + } + ); + } +});