Files
thrilltrack-explorer/supabase/functions/request-account-deletion/index.ts
gpt-engineer-app[bot] 82b85e3284 Add system phase 4 audits
- Add audit logging for system maintenance operations (cache/orphaned images/manual cleanup)
- Log account deletion request handling (requests/confirm/cancel)
- Log security actions (admin password resets, MFA enforcement changes, account lockouts)
2025-11-11 14:49:11 +00:00

159 lines
5.8 KiB
TypeScript

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { corsHeaders } from '../_shared/cors.ts';
import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts';
import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts';
// Apply standard rate limiting (20 req/min) for account deletion requests
const handler = createEdgeFunction(
{
name: 'request-account-deletion',
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', 'request_deletion');
// Check for existing active deletion request (pending or confirmed)
const { data: existingRequest } = await supabaseClient
.from('account_deletion_requests')
.select('*')
.eq('user_id', context.userId)
.in('status', ['pending', 'confirmed'])
.maybeSingle();
if (existingRequest) {
const errorMsg = existingRequest.status === 'confirmed'
? 'Your account is already scheduled for deletion. You can cancel it from your account settings.'
: 'You already have a pending deletion request. Check your email for the confirmation code.';
return new Response(
JSON.stringify({
error: errorMsg,
request: existingRequest,
}),
{
status: 400,
headers: { 'Content-Type': 'application/json' },
}
);
}
// Generate confirmation code
const { data: codeData, error: codeError } = await supabaseClient
.rpc('generate_deletion_confirmation_code');
if (codeError) {
throw codeError;
}
const confirmationCode = codeData as string;
const scheduledDeletionAt = new Date();
scheduledDeletionAt.setDate(scheduledDeletionAt.getDate() + 14);
// Create deletion request
const { data: deletionRequest, error: requestError } = await supabaseClient
.from('account_deletion_requests')
.insert({
user_id: context.userId,
confirmation_code: confirmationCode,
confirmation_code_sent_at: new Date().toISOString(),
scheduled_deletion_at: scheduledDeletionAt.toISOString(),
status: 'pending',
})
.select()
.single();
if (requestError) {
throw requestError;
}
// Send confirmation email
const userEmail = (await supabaseClient.auth.getUser()).data.user?.email;
const forwardEmailKey = Deno.env.get('FORWARDEMAIL_API_KEY');
const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com';
// Log to system activity log
await supabaseClient.rpc('log_system_activity', {
_user_id: context.userId,
_action: 'account_deletion_requested',
_details: {
request_id: deletionRequest.id,
scheduled_deletion_at: scheduledDeletionAt.toISOString(),
}
});
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 Requested - Confirmation Code Inside',
html: `
<h2>Account Deletion Requested</h2>
<p>Hello,</p>
<p>We received a request to delete your account on ${new Date().toLocaleDateString()}.</p>
<h3>IMPORTANT INFORMATION:</h3>
<p>You must enter the confirmation code within 24 hours. Once confirmed, your account will be deactivated and permanently deleted on <strong>${scheduledDeletionAt.toLocaleDateString()}</strong> (14 days from confirmation).</p>
<h4>What will be DELETED:</h4>
<ul>
<li>✗ Your profile information (username, bio, avatar, etc.)</li>
<li>✗ Your reviews and ratings</li>
<li>✗ Your personal preferences and settings</li>
</ul>
<h4>What will be PRESERVED:</h4>
<ul>
<li>✓ Your database submissions (park creations, ride additions, edits)</li>
<li>✓ Photos you've uploaded (will be shown as "Submitted by [deleted user]")</li>
<li>✓ Edit history and contributions</li>
</ul>
<h3>CONFIRMATION CODE: <strong>${confirmationCode}</strong></h3>
<p><strong>IMPORTANT:</strong> You have 24 hours to enter this code to confirm the deletion. After entering the code, your account will be deactivated and you'll have 14 days to cancel before permanent deletion.</p>
<p><strong>Need to cancel?</strong> You can cancel at any time - before OR after confirming - during the 14-day period.</p>
<p><strong>Changed your mind?</strong> Simply log in to your account settings and click "Cancel Deletion".</p>
`,
}),
});
} catch (emailError) {
// Non-blocking email failure
}
}
return new Response(
JSON.stringify({
success: true,
message: 'Account deletion request created successfully',
scheduled_deletion_at: scheduledDeletionAt.toISOString(),
request_id: deletionRequest.id,
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
}
);
}
);
export default withRateLimit(handler, rateLimiters.standard, corsHeaders);