import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { corsHeaders } from '../_shared/cors.ts'; import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { validateString } from '../_shared/typeValidation.ts'; export default createEdgeFunction( { name: 'confirm-account-deletion', requireAuth: true, corsHeaders: corsHeaders }, async (req, context) => { const { confirmation_code } = await req.json(); validateString(confirmation_code, 'confirmation_code', { userId: context.userId, requestId: context.requestId }); 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', 'confirm_deletion'); // Find deletion request const { data: deletionRequest, error: requestError } = await supabaseClient .from('account_deletion_requests') .select('*') .eq('user_id', context.userId) .eq('status', 'pending') .maybeSingle(); if (requestError || !deletionRequest) { throw new Error('No pending deletion request found'); } // Check if there's already a confirmed request const { data: confirmedRequest } = await supabaseClient .from('account_deletion_requests') .select('*') .eq('user_id', context.userId) .eq('status', 'confirmed') .maybeSingle(); if (confirmedRequest) { throw new Error('You already have a confirmed deletion request. Your account is scheduled for deletion.'); } // Verify confirmation code if (deletionRequest.confirmation_code !== confirmation_code) { throw new Error('Invalid confirmation code'); } // Verify code was entered within 24 hours const codeSentAt = new Date(deletionRequest.confirmation_code_sent_at); const now = new Date(); const hoursSinceCodeSent = (now.getTime() - codeSentAt.getTime()) / (1000 * 60 * 60); if (hoursSinceCodeSent > 24) { throw new Error('Confirmation code has expired. Please request a new deletion code.'); } // Deactivate profile const { error: profileError } = await supabaseClient .from('profiles') .update({ deactivated: true, deactivated_at: new Date().toISOString(), deactivation_reason: 'User confirmed account deletion request', }) .eq('user_id', context.userId); if (profileError) { throw profileError; } // Update deletion request status to 'confirmed' const { error: updateError } = await supabaseClient .from('account_deletion_requests') .update({ status: 'confirmed', }) .eq('id', deletionRequest.id); if (updateError) { throw updateError; } // Log to system activity log await supabaseClient.rpc('log_system_activity', { _user_id: context.userId, _action: 'account_deletion_confirmed', _details: { request_id: deletionRequest.id, scheduled_deletion_at: deletionRequest.scheduled_deletion_at, account_deactivated: true, } }); // Send confirmation email const forwardEmailKey = Deno.env.get('FORWARDEMAIL_API_KEY'); const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com'; const userEmail = (await supabaseClient.auth.getUser()).data.user?.email; if (forwardEmailKey && userEmail) { try { await fetch('https://api.forwardemail.net/v1/emails', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${btoa(forwardEmailKey + ':')}`, }, body: JSON.stringify({ from: fromEmail, to: userEmail, subject: 'Account Deletion Confirmed - 14 Days to Cancel', html: `
Your deletion request has been confirmed. Your account is now deactivated and will be permanently deleted on ${new Date(deletionRequest.scheduled_deletion_at).toLocaleDateString()}.
You can cancel at any time during the 14-day waiting period by logging in and clicking "Cancel Deletion" in your account settings.
If you take no action, your account will be automatically deleted after the 14-day waiting period.
`, }), }); } catch (emailError) { // Non-blocking email failure } } return new Response( JSON.stringify({ success: true, message: 'Deletion confirmed. Account deactivated and scheduled for permanent deletion.', scheduled_deletion_at: deletionRequest.scheduled_deletion_at, }), { status: 200, headers: { 'Content-Type': 'application/json' }, } ); } );