mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 11:51:14 -05:00
Migrate 3 edge functions to wrapper
- Refactor validate-email, receive-inbound-email, and send-admin-email-reply to use createEdgeFunction wrapper with automatic error handling, tracing, and reduced boilerplate. - enrich wrapper to support service-role usage and role-based authorization context for complex flows.
This commit is contained in:
@@ -21,10 +21,13 @@ import {
|
||||
} from './logger.ts';
|
||||
import { formatEdgeError, toError } from './errorFormatter.ts';
|
||||
import { ValidationError, logValidationError } from './typeValidation.ts';
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||
|
||||
export interface EdgeFunctionConfig {
|
||||
name: string;
|
||||
requireAuth?: boolean;
|
||||
requiredRoles?: string[];
|
||||
useServiceRole?: boolean;
|
||||
corsHeaders?: HeadersInit;
|
||||
logRequests?: boolean;
|
||||
logResponses?: boolean;
|
||||
@@ -34,6 +37,8 @@ export interface EdgeFunctionContext {
|
||||
requestId: string;
|
||||
span: Span;
|
||||
userId?: string;
|
||||
user?: any;
|
||||
supabase: any;
|
||||
}
|
||||
|
||||
export type EdgeFunctionHandler = (
|
||||
@@ -51,6 +56,8 @@ export function wrapEdgeFunction(
|
||||
const {
|
||||
name,
|
||||
requireAuth = true,
|
||||
requiredRoles = [],
|
||||
useServiceRole = false,
|
||||
corsHeaders = {},
|
||||
logRequests = true,
|
||||
logResponses = true,
|
||||
@@ -100,13 +107,39 @@ export function wrapEdgeFunction(
|
||||
|
||||
try {
|
||||
// ====================================================================
|
||||
// STEP 4: Authentication (if required)
|
||||
// STEP 4: Create Supabase client
|
||||
// ====================================================================
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const authHeader = req.headers.get('Authorization');
|
||||
|
||||
let supabase;
|
||||
if (useServiceRole) {
|
||||
// Use service role key for backend operations
|
||||
const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
supabase = createClient(supabaseUrl, serviceRoleKey);
|
||||
addSpanEvent(span, 'supabase_client_created', { type: 'service_role' });
|
||||
} else if (authHeader) {
|
||||
// Use anon key with user's auth header
|
||||
const anonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
|
||||
supabase = createClient(supabaseUrl, anonKey, {
|
||||
global: { headers: { Authorization: authHeader } }
|
||||
});
|
||||
addSpanEvent(span, 'supabase_client_created', { type: 'authenticated' });
|
||||
} else {
|
||||
// Use anon key without auth
|
||||
const anonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
|
||||
supabase = createClient(supabaseUrl, anonKey);
|
||||
addSpanEvent(span, 'supabase_client_created', { type: 'anonymous' });
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// STEP 5: Authentication (if required)
|
||||
// ====================================================================
|
||||
let userId: string | undefined;
|
||||
let user: any = undefined;
|
||||
|
||||
if (requireAuth) {
|
||||
addSpanEvent(span, 'authentication_start');
|
||||
const authHeader = req.headers.get('Authorization');
|
||||
|
||||
if (!authHeader) {
|
||||
addSpanEvent(span, 'authentication_failed', { reason: 'missing_header' });
|
||||
@@ -125,21 +158,15 @@ export function wrapEdgeFunction(
|
||||
);
|
||||
}
|
||||
|
||||
// Extract user ID from JWT (simplified - extend as needed)
|
||||
try {
|
||||
// Note: In production, validate the JWT properly
|
||||
const token = authHeader.replace('Bearer ', '');
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
userId = payload.sub;
|
||||
|
||||
addSpanEvent(span, 'authentication_success', { userId });
|
||||
span.attributes['user.id'] = userId;
|
||||
} catch (error) {
|
||||
// Get user from Supabase
|
||||
const { data: { user: authUser }, error: userError } = await supabase.auth.getUser();
|
||||
|
||||
if (userError || !authUser) {
|
||||
addSpanEvent(span, 'authentication_failed', {
|
||||
reason: 'invalid_token',
|
||||
error: formatEdgeError(error)
|
||||
error: formatEdgeError(userError)
|
||||
});
|
||||
endSpan(span, 'error', error);
|
||||
endSpan(span, 'error', userError);
|
||||
logSpan(span);
|
||||
|
||||
return new Response(
|
||||
@@ -153,10 +180,55 @@ export function wrapEdgeFunction(
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
user = authUser;
|
||||
userId = authUser.id;
|
||||
|
||||
addSpanEvent(span, 'authentication_success', { userId });
|
||||
span.attributes['user.id'] = userId;
|
||||
|
||||
// ====================================================================
|
||||
// STEP 6: Role verification (if required)
|
||||
// ====================================================================
|
||||
if (requiredRoles.length > 0) {
|
||||
addSpanEvent(span, 'role_check_start', { requiredRoles });
|
||||
|
||||
let hasRequiredRole = false;
|
||||
for (const role of requiredRoles) {
|
||||
const { data: hasRole } = await supabase
|
||||
.rpc('has_role', { _user_id: userId, _role: role });
|
||||
|
||||
if (hasRole) {
|
||||
hasRequiredRole = true;
|
||||
addSpanEvent(span, 'role_check_success', { role });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRequiredRole) {
|
||||
addSpanEvent(span, 'role_check_failed', {
|
||||
userId,
|
||||
requiredRoles
|
||||
});
|
||||
endSpan(span, 'error');
|
||||
logSpan(span);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Insufficient permissions',
|
||||
requestId
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// STEP 5: Execute handler
|
||||
// STEP 7: Execute handler
|
||||
// ====================================================================
|
||||
addSpanEvent(span, 'handler_start');
|
||||
|
||||
@@ -164,6 +236,8 @@ export function wrapEdgeFunction(
|
||||
requestId,
|
||||
span,
|
||||
userId,
|
||||
user,
|
||||
supabase,
|
||||
};
|
||||
|
||||
const response = await handler(req, context);
|
||||
|
||||
Reference in New Issue
Block a user