mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 17:31: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,8 +1,9 @@
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
|
||||
import { corsHeaders } from '../_shared/cors.ts';
|
||||
import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts';
|
||||
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
|
||||
import { formatEdgeError } from '../_shared/errorFormatter.ts';
|
||||
import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts';
|
||||
import { validateUUID } from '../_shared/typeValidation.ts';
|
||||
import { addSpanEvent } from '../_shared/logger.ts';
|
||||
|
||||
interface DeleteUserRequest {
|
||||
targetUserId: string;
|
||||
@@ -15,88 +16,34 @@ interface DeleteUserResponse {
|
||||
}
|
||||
|
||||
// Apply moderate rate limiting (10 req/min) for admin user deletion
|
||||
// Prevents abuse of this sensitive administrative operation
|
||||
Deno.serve(withRateLimit(async (req) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest();
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
|
||||
try {
|
||||
// Get authorization header
|
||||
const authHeader = req.headers.get('authorization');
|
||||
if (!authHeader) {
|
||||
edgeLogger.warn('Missing authorization header', {
|
||||
requestId: tracking.requestId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
errorCode: 'permission_denied'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
const handler = createEdgeFunction(
|
||||
{
|
||||
name: 'admin-delete-user',
|
||||
requireAuth: true,
|
||||
corsHeaders: corsHeaders
|
||||
},
|
||||
async (req, context) => {
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
|
||||
// Create admin client for privileged operations
|
||||
const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
// Get current user - extract token and verify
|
||||
const token = authHeader.replace('Bearer ', '');
|
||||
const { data: { user }, error: userError } = await supabaseAdmin.auth.getUser(token);
|
||||
if (userError || !user) {
|
||||
edgeLogger.warn('Failed to get user', {
|
||||
requestId: tracking.requestId,
|
||||
error: userError?.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
errorCode: 'permission_denied'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Create client with user's JWT for MFA checks
|
||||
const supabase = createClient(supabaseUrl, Deno.env.get('SUPABASE_ANON_KEY')!, {
|
||||
global: { headers: { Authorization: authHeader } }
|
||||
global: { headers: { Authorization: req.headers.get('Authorization')! } }
|
||||
});
|
||||
|
||||
const adminUserId = user.id;
|
||||
const adminUserId = context.userId;
|
||||
context.span.setAttribute('action', 'admin_delete_user');
|
||||
context.span.setAttribute('admin_user_id', adminUserId);
|
||||
|
||||
// Parse request
|
||||
const { targetUserId }: DeleteUserRequest = await req.json();
|
||||
validateUUID(targetUserId, 'targetUserId', { adminUserId, requestId: context.requestId });
|
||||
context.span.setAttribute('target_user_id', targetUserId);
|
||||
|
||||
if (!targetUserId) {
|
||||
edgeLogger.warn('Missing targetUserId', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Target user ID is required',
|
||||
errorCode: 'invalid_request'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
edgeLogger.info('Admin delete user request', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'delete_request_received', { targetUserId });
|
||||
|
||||
// SECURITY CHECK 1: Verify admin is superuser
|
||||
const { data: adminRoles, error: rolesError } = await supabaseAdmin
|
||||
@@ -105,38 +52,19 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
.eq('user_id', adminUserId);
|
||||
|
||||
if (rolesError || !adminRoles) {
|
||||
edgeLogger.error('Failed to fetch admin roles', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
error: rolesError?.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Permission denied',
|
||||
errorCode: 'permission_denied'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
throw new Error('Permission denied');
|
||||
}
|
||||
|
||||
const isSuperuser = adminRoles.some(r => r.role === 'superuser');
|
||||
if (!isSuperuser) {
|
||||
edgeLogger.warn('Non-superuser attempted admin deletion', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
targetUserId,
|
||||
roles: adminRoles.map(r => r.role),
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'non_superuser_attempt', { roles: adminRoles.map(r => r.role) });
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Only superusers can delete users',
|
||||
errorCode: 'permission_denied'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,33 +73,23 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
const hasMFAEnrolled = factorsData?.totp?.some(f => f.status === 'verified') || false;
|
||||
|
||||
if (hasMFAEnrolled) {
|
||||
// Extract AAL from JWT
|
||||
const token = authHeader.replace('Bearer ', '');
|
||||
const token = req.headers.get('Authorization')!.replace('Bearer ', '');
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
const currentAal = payload.aal || 'aal1';
|
||||
|
||||
if (currentAal !== 'aal2') {
|
||||
edgeLogger.warn('AAL2 required for superuser action', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
currentAal,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'aal2_required', { currentAal });
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'AAL2 verification required for this action',
|
||||
errorCode: 'aal2_required'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
edgeLogger.info('AAL2 verified for superuser action', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'aal2_verified');
|
||||
}
|
||||
|
||||
// SECURITY CHECK 3: Verify target user is not a superuser
|
||||
@@ -181,54 +99,32 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
.eq('user_id', targetUserId);
|
||||
|
||||
if (targetRolesError) {
|
||||
edgeLogger.error('Failed to fetch target user roles', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: targetRolesError.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Failed to verify target user',
|
||||
errorCode: 'deletion_failed'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
throw new Error('Failed to verify target user');
|
||||
}
|
||||
|
||||
const targetIsSuperuser = targetRoles?.some(r => r.role === 'superuser') || false;
|
||||
if (targetIsSuperuser) {
|
||||
edgeLogger.warn('Attempted to delete superuser', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'superuser_protection', { targetUserId });
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Cannot delete other superusers',
|
||||
errorCode: 'permission_denied'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// SECURITY CHECK 4: Verify not deleting self
|
||||
if (adminUserId === targetUserId) {
|
||||
edgeLogger.warn('Attempted self-deletion', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'self_deletion_blocked');
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Cannot delete your own account',
|
||||
errorCode: 'permission_denied'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,13 +139,7 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
const { data: { user: targetAuthUser } } = await supabaseAdmin.auth.admin.getUserById(targetUserId);
|
||||
const targetEmail = targetAuthUser?.email;
|
||||
|
||||
edgeLogger.info('Starting user deletion', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
targetUserId,
|
||||
targetUsername: targetProfile?.username,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'deletion_start', { targetUsername: targetProfile?.username });
|
||||
|
||||
// CLEANUP STEP 1: Delete reviews (CASCADE will handle review_photos)
|
||||
const { error: reviewsError } = await supabaseAdmin
|
||||
@@ -258,18 +148,9 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
.eq('user_id', targetUserId);
|
||||
|
||||
if (reviewsError) {
|
||||
edgeLogger.error('Failed to delete reviews', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: reviewsError.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'reviews_delete_failed', { error: reviewsError.message });
|
||||
} else {
|
||||
edgeLogger.info('Deleted user reviews', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'reviews_deleted');
|
||||
}
|
||||
|
||||
// CLEANUP STEP 2: Anonymize submissions and photos
|
||||
@@ -277,18 +158,9 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
.rpc('anonymize_user_submissions', { target_user_id: targetUserId });
|
||||
|
||||
if (anonymizeError) {
|
||||
edgeLogger.error('Failed to anonymize submissions', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: anonymizeError.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'anonymize_failed', { error: anonymizeError.message });
|
||||
} else {
|
||||
edgeLogger.info('Anonymized user submissions', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'submissions_anonymized');
|
||||
}
|
||||
|
||||
// CLEANUP STEP 3: Delete user roles
|
||||
@@ -298,18 +170,9 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
.eq('user_id', targetUserId);
|
||||
|
||||
if (rolesDeleteError) {
|
||||
edgeLogger.error('Failed to delete user roles', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: rolesDeleteError.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'roles_delete_failed', { error: rolesDeleteError.message });
|
||||
} else {
|
||||
edgeLogger.info('Deleted user roles', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'roles_deleted');
|
||||
}
|
||||
|
||||
// CLEANUP STEP 4: Delete avatar from Cloudflare Images (non-critical)
|
||||
@@ -328,29 +191,11 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
edgeLogger.info('Deleted avatar from Cloudflare', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
imageId: targetProfile.avatar_image_id,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
} else {
|
||||
edgeLogger.warn('Failed to delete avatar from Cloudflare', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
imageId: targetProfile.avatar_image_id,
|
||||
status: response.status,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'avatar_deleted_cloudflare', { imageId: targetProfile.avatar_image_id });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
edgeLogger.warn('Error deleting avatar from Cloudflare', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: formatEdgeError(error),
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
// Non-critical - continue with deletion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,33 +206,16 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
.eq('user_id', targetUserId);
|
||||
|
||||
if (profileError) {
|
||||
edgeLogger.error('Failed to delete profile', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: profileError.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Failed to delete user profile',
|
||||
errorCode: 'deletion_failed'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
throw new Error('Failed to delete user profile');
|
||||
}
|
||||
|
||||
edgeLogger.info('Deleted user profile', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'profile_deleted');
|
||||
|
||||
// CLEANUP STEP 6: Remove Novu subscriber (non-critical)
|
||||
try {
|
||||
const novuApiKey = Deno.env.get('NOVU_API_KEY');
|
||||
if (novuApiKey) {
|
||||
const novuResponse = await fetch(
|
||||
await fetch(
|
||||
`https://api.novu.co/v1/subscribers/${targetUserId}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
@@ -397,59 +225,23 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (novuResponse.ok) {
|
||||
edgeLogger.info('Removed Novu subscriber', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
} else {
|
||||
edgeLogger.warn('Failed to remove Novu subscriber', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
status: novuResponse.status,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
}
|
||||
addSpanEvent(context.span, 'novu_subscriber_removed');
|
||||
}
|
||||
} catch (error) {
|
||||
edgeLogger.warn('Error removing Novu subscriber', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: formatEdgeError(error),
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
// Non-critical
|
||||
}
|
||||
|
||||
// CLEANUP STEP 7: Delete auth user (CRITICAL - must succeed)
|
||||
const { error: authDeleteError } = await supabaseAdmin.auth.admin.deleteUser(targetUserId);
|
||||
|
||||
if (authDeleteError) {
|
||||
edgeLogger.error('Failed to delete auth user', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: authDeleteError.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Failed to delete user account',
|
||||
errorCode: 'deletion_failed'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
throw new Error('Failed to delete user account');
|
||||
}
|
||||
|
||||
edgeLogger.info('Deleted auth user', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'auth_user_deleted');
|
||||
|
||||
// AUDIT LOG: Record admin action
|
||||
const { error: auditError } = await supabaseAdmin
|
||||
await supabaseAdmin
|
||||
.from('admin_audit_log')
|
||||
.insert({
|
||||
admin_user_id: adminUserId,
|
||||
@@ -464,29 +256,14 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
}
|
||||
});
|
||||
|
||||
if (auditError) {
|
||||
edgeLogger.error('Failed to log admin action', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
targetUserId,
|
||||
error: auditError.message,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
} else {
|
||||
edgeLogger.info('Logged admin action', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
targetUserId,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
}
|
||||
addSpanEvent(context.span, 'audit_logged');
|
||||
|
||||
// NOTIFICATION: Send email to deleted user (non-critical)
|
||||
if (targetEmail) {
|
||||
try {
|
||||
const forwardEmailKey = Deno.env.get('FORWARD_EMAIL_API_KEY');
|
||||
if (forwardEmailKey) {
|
||||
const emailResponse = await fetch('https://api.forwardemail.net/v1/emails', {
|
||||
await fetch('https://api.forwardemail.net/v1/emails', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Basic ${btoa(forwardEmailKey + ':')}`,
|
||||
@@ -500,63 +277,20 @@ Deno.serve(withRateLimit(async (req) => {
|
||||
html: `<p>Your ThrillWiki account has been deleted by an administrator.</p><p><strong>Deletion Date:</strong> ${new Date().toLocaleString()}</p><h3>What was deleted:</h3><ul><li>Your profile and personal information</li><li>Your reviews and ratings</li><li>Your account preferences</li></ul><h3>What was preserved:</h3><ul><li>Your content submissions (as anonymous contributions)</li><li>Your uploaded photos (credited as anonymous)</li></ul><p>If you believe this was done in error, please contact <a href="mailto:support@thrillwiki.com">support@thrillwiki.com</a>.</p><p>No action is required from you.</p>`
|
||||
})
|
||||
});
|
||||
|
||||
if (emailResponse.ok) {
|
||||
edgeLogger.info('Sent deletion notification email', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
targetEmail,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
} else {
|
||||
edgeLogger.warn('Failed to send deletion notification email', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
status: emailResponse.status,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
}
|
||||
addSpanEvent(context.span, 'notification_email_sent');
|
||||
}
|
||||
} catch (error) {
|
||||
edgeLogger.warn('Error sending deletion notification email', {
|
||||
requestId: tracking.requestId,
|
||||
targetUserId,
|
||||
error: formatEdgeError(error),
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
// Non-critical
|
||||
}
|
||||
}
|
||||
|
||||
const duration = endRequest(tracking);
|
||||
edgeLogger.info('User deletion completed', {
|
||||
requestId: tracking.requestId,
|
||||
adminUserId,
|
||||
targetUserId,
|
||||
duration,
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
addSpanEvent(context.span, 'deletion_complete');
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ success: true } as DeleteUserResponse),
|
||||
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
const duration = endRequest(tracking);
|
||||
edgeLogger.error('Unexpected error in admin delete user', {
|
||||
requestId: tracking.requestId,
|
||||
duration,
|
||||
error: formatEdgeError(error),
|
||||
action: 'admin_delete_user'
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'An unexpected error occurred',
|
||||
errorCode: 'deletion_failed'
|
||||
} as DeleteUserResponse),
|
||||
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
}, rateLimiters.moderate, corsHeaders));
|
||||
);
|
||||
|
||||
export default withRateLimit(handler, rateLimiters.moderate, corsHeaders);
|
||||
|
||||
Reference in New Issue
Block a user