mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 04:51:13 -05:00
Migrate Phase 1 Functions
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.
This commit is contained in:
@@ -1,24 +1,16 @@
|
||||
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
||||
import { corsHeaders } from '../_shared/cors.ts';
|
||||
import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts';
|
||||
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
|
||||
import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts';
|
||||
|
||||
// Apply standard rate limiting (20 req/min) for account deletion requests
|
||||
// Balances user needs with protection against automated abuse
|
||||
serve(withRateLimit(async (req) => {
|
||||
const tracking = startRequest();
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'X-Request-ID': tracking.requestId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
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') ?? '',
|
||||
@@ -29,33 +21,13 @@ serve(withRateLimit(async (req) => {
|
||||
}
|
||||
);
|
||||
|
||||
// Get authenticated user
|
||||
const {
|
||||
data: { user },
|
||||
error: userError,
|
||||
} = await supabaseClient.auth.getUser();
|
||||
|
||||
if (userError || !user) {
|
||||
const duration = endRequest(tracking);
|
||||
edgeLogger.error('Authentication failed', {
|
||||
action: 'request_deletion',
|
||||
requestId: tracking.requestId,
|
||||
duration
|
||||
});
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
edgeLogger.info('Processing deletion request', {
|
||||
action: 'request_deletion',
|
||||
requestId: tracking.requestId,
|
||||
userId: user.id
|
||||
});
|
||||
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', user.id)
|
||||
.eq('user_id', context.userId)
|
||||
.in('status', ['pending', 'confirmed'])
|
||||
.maybeSingle();
|
||||
|
||||
@@ -71,11 +43,7 @@ serve(withRateLimit(async (req) => {
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -96,7 +64,7 @@ serve(withRateLimit(async (req) => {
|
||||
const { data: deletionRequest, error: requestError } = await supabaseClient
|
||||
.from('account_deletion_requests')
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
user_id: context.userId,
|
||||
confirmation_code: confirmationCode,
|
||||
confirmation_code_sent_at: new Date().toISOString(),
|
||||
scheduled_deletion_at: scheduledDeletionAt.toISOString(),
|
||||
@@ -110,45 +78,11 @@ serve(withRateLimit(async (req) => {
|
||||
}
|
||||
|
||||
// Send confirmation email
|
||||
const emailPayload = {
|
||||
to: user.email,
|
||||
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>
|
||||
`,
|
||||
};
|
||||
|
||||
// Send via ForwardEmail API
|
||||
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';
|
||||
|
||||
if (forwardEmailKey) {
|
||||
if (forwardEmailKey && userEmail) {
|
||||
try {
|
||||
await fetch('https://api.forwardemail.net/v1/emails', {
|
||||
method: 'POST',
|
||||
@@ -158,67 +92,57 @@ serve(withRateLimit(async (req) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
from: fromEmail,
|
||||
to: emailPayload.to,
|
||||
subject: emailPayload.subject,
|
||||
html: emailPayload.html,
|
||||
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>
|
||||
`,
|
||||
}),
|
||||
});
|
||||
edgeLogger.info('Deletion confirmation email sent', { requestId: tracking.requestId });
|
||||
} catch (emailError) {
|
||||
edgeLogger.error('Failed to send email', {
|
||||
requestId: tracking.requestId,
|
||||
error: emailError.message
|
||||
});
|
||||
// Non-blocking email failure
|
||||
}
|
||||
}
|
||||
|
||||
const duration = endRequest(tracking);
|
||||
edgeLogger.info('Deletion request created successfully', {
|
||||
action: 'request_deletion',
|
||||
requestId: tracking.requestId,
|
||||
userId: user.id,
|
||||
duration,
|
||||
requestId: deletionRequest.id
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'Account deletion request created successfully',
|
||||
scheduled_deletion_at: scheduledDeletionAt.toISOString(),
|
||||
request_id: deletionRequest.id,
|
||||
requestId: tracking.requestId,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
const duration = endRequest(tracking);
|
||||
edgeLogger.error('Error processing deletion request', {
|
||||
action: 'request_deletion',
|
||||
requestId: tracking.requestId,
|
||||
duration,
|
||||
error: error.message
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: error.message,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
}, rateLimiters.standard, corsHeaders));
|
||||
);
|
||||
|
||||
export default withRateLimit(handler, rateLimiters.standard, corsHeaders);
|
||||
|
||||
Reference in New Issue
Block a user