mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:31:12 -05:00
Migrate 8 high-priority functions (admin-delete-user, mfa-unenroll, confirm-account-deletion, request-account-deletion, send-contact-message, upload-image, validate-email-backend, process-oauth-profile) to wrapEdgeFunction pattern. Replace manual CORS/auth, add shared validations, integrate standardized error handling, and preserve existing rate limiting where applicable. Update implementations to leverage context span, requestId, and improved logging for consistent error reporting and tracing.
145 lines
5.0 KiB
TypeScript
145 lines
5.0 KiB
TypeScript
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;
|
|
}
|
|
|
|
// 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: `
|
|
<h2>Account Deletion Confirmed</h2>
|
|
<p>Your deletion request has been confirmed. Your account is now <strong>deactivated</strong> and will be permanently deleted on <strong>${new Date(deletionRequest.scheduled_deletion_at).toLocaleDateString()}</strong>.</p>
|
|
|
|
<h3>What happens now?</h3>
|
|
<ul>
|
|
<li>✓ Your account is deactivated immediately</li>
|
|
<li>✓ You have 14 days to cancel before permanent deletion</li>
|
|
<li>✓ To cancel, log in and visit your account settings</li>
|
|
</ul>
|
|
|
|
<h3>Changed Your Mind?</h3>
|
|
<p>You can cancel at any time during the 14-day waiting period by logging in and clicking "Cancel Deletion" in your account settings.</p>
|
|
|
|
<p><strong>If you take no action</strong>, your account will be automatically deleted after the 14-day waiting period.</p>
|
|
`,
|
|
}),
|
|
});
|
|
} 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' },
|
|
}
|
|
);
|
|
}
|
|
);
|