diff --git a/supabase/functions/_shared/cors.ts b/supabase/functions/_shared/cors.ts new file mode 100644 index 00000000..fe1996dd --- /dev/null +++ b/supabase/functions/_shared/cors.ts @@ -0,0 +1,119 @@ +/** + * Centralized CORS configuration for all edge functions + * Provides consistent header handling across the application + */ + +// Standard headers that should be allowed across all functions +const STANDARD_HEADERS = [ + 'authorization', + 'x-client-info', + 'apikey', + 'content-type', +]; + +// Tracing headers for distributed tracing and request tracking +const TRACING_HEADERS = [ + 'traceparent', + 'x-request-id', +]; + +// All headers combined +const ALL_HEADERS = [...STANDARD_HEADERS, ...TRACING_HEADERS]; + +/** + * Basic CORS headers - allows all origins + * Use for most edge functions that need public access + */ +export const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': STANDARD_HEADERS.join(', '), +}; + +/** + * Extended CORS headers - includes tracing headers + * Use for functions that participate in distributed tracing + */ +export const corsHeadersWithTracing = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': ALL_HEADERS.join(', '), +}; + +/** + * CORS headers with methods - for functions with multiple HTTP verbs + */ +export const corsHeadersWithMethods = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': ALL_HEADERS.join(', '), + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', +}; + +/** + * CORS headers with credentials - for authenticated requests requiring cookies + */ +export const corsHeadersWithCredentials = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': ALL_HEADERS.join(', '), + 'Access-Control-Allow-Credentials': 'true', +}; + +/** + * Environment-aware CORS configuration + * Validates origin against allowlist (production) or localhost (development) + */ +export const getAllowedOrigin = (requestOrigin: string | null): string | null => { + // If no origin header, it's not a CORS request (same-origin or server-to-server) + if (!requestOrigin) { + return null; + } + + const environment = Deno.env.get('ENVIRONMENT') || 'development'; + + // Production allowlist - configure via ALLOWED_ORIGINS environment variable + const allowedOriginsEnv = Deno.env.get('ALLOWED_ORIGINS') || ''; + const allowedOrigins = allowedOriginsEnv.split(',').filter(origin => origin.trim()); + + // In development, only allow localhost and Replit domains + if (environment === 'development') { + if ( + requestOrigin.includes('localhost') || + requestOrigin.includes('127.0.0.1') || + requestOrigin.includes('.repl.co') || + requestOrigin.includes('.replit.dev') + ) { + return requestOrigin; + } + return null; + } + + // In production, only allow specific domains from environment variable + if (allowedOrigins.includes(requestOrigin)) { + return requestOrigin; + } + + return null; +}; + +/** + * Get CORS headers with validated origin + * Use for functions requiring strict origin validation (e.g., upload-image) + */ +export const getCorsHeaders = (allowedOrigin: string | null): Record => { + if (!allowedOrigin) { + return {}; + } + + return { + 'Access-Control-Allow-Origin': allowedOrigin, + 'Access-Control-Allow-Headers': ALL_HEADERS.join(', '), + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + 'Access-Control-Allow-Credentials': 'true', + }; +}; + +/** + * Handle OPTIONS preflight request + * Returns a Response with appropriate CORS headers + */ +export const handleCorsPreFlight = (corsHeaders: Record): Response => { + return new Response(null, { headers: corsHeaders }); +}; diff --git a/supabase/functions/admin-delete-user/index.ts b/supabase/functions/admin-delete-user/index.ts index fe816f89..ad6679b1 100644 --- a/supabase/functions/admin-delete-user/index.ts +++ b/supabase/functions/admin-delete-user/index.ts @@ -1,12 +1,8 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface DeleteUserRequest { targetUserId: string; } diff --git a/supabase/functions/cancel-account-deletion/index.ts b/supabase/functions/cancel-account-deletion/index.ts index 2b33c0f4..b10b4710 100644 --- a/supabase/functions/cancel-account-deletion/index.ts +++ b/supabase/functions/cancel-account-deletion/index.ts @@ -1,13 +1,9 @@ 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 { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - serve(async (req) => { const tracking = startRequest(); diff --git a/supabase/functions/cancel-email-change/index.ts b/supabase/functions/cancel-email-change/index.ts index 518580dc..a1b52fd8 100644 --- a/supabase/functions/cancel-email-change/index.ts +++ b/supabase/functions/cancel-email-change/index.ts @@ -1,12 +1,8 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - Deno.serve(async (req) => { const tracking = startRequest(); diff --git a/supabase/functions/check-transaction-status/index.ts b/supabase/functions/check-transaction-status/index.ts index 5342e1ee..d9eb5770 100644 --- a/supabase/functions/check-transaction-status/index.ts +++ b/supabase/functions/check-transaction-status/index.ts @@ -8,13 +8,9 @@ */ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface StatusRequest { idempotencyKey: string; } diff --git a/supabase/functions/cleanup-old-versions/index.ts b/supabase/functions/cleanup-old-versions/index.ts index 35a6b224..67a3ff00 100644 --- a/supabase/functions/cleanup-old-versions/index.ts +++ b/supabase/functions/cleanup-old-versions/index.ts @@ -1,12 +1,8 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface CleanupStats { item_edit_history_deleted: number; orphaned_records_deleted: number; diff --git a/supabase/functions/confirm-account-deletion/index.ts b/supabase/functions/confirm-account-deletion/index.ts index 1f7bac64..9e466b27 100644 --- a/supabase/functions/confirm-account-deletion/index.ts +++ b/supabase/functions/confirm-account-deletion/index.ts @@ -1,12 +1,8 @@ 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 { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - serve(async (req) => { const tracking = startRequest(); diff --git a/supabase/functions/create-novu-subscriber/index.ts b/supabase/functions/create-novu-subscriber/index.ts index 6ae4332a..1c55731f 100644 --- a/supabase/functions/create-novu-subscriber/index.ts +++ b/supabase/functions/create-novu-subscriber/index.ts @@ -1,13 +1,9 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { Novu } from "npm:@novu/api@1.6.0"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - // Simple request tracking const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() }); const endRequest = (tracking: { start: number }) => Date.now() - tracking.start; diff --git a/supabase/functions/detect-location/index.ts b/supabase/functions/detect-location/index.ts index 293fbc21..502c9a45 100644 --- a/supabase/functions/detect-location/index.ts +++ b/supabase/functions/detect-location/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; import { formatEdgeError } from "../_shared/errorFormatter.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - interface IPLocationResponse { country: string; countryCode: string; diff --git a/supabase/functions/export-user-data/index.ts b/supabase/functions/export-user-data/index.ts index 6a32dc58..9aff6162 100644 --- a/supabase/functions/export-user-data/index.ts +++ b/supabase/functions/export-user-data/index.ts @@ -1,14 +1,10 @@ import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { sanitizeError } from '../_shared/errorSanitizer.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface ExportOptions { include_reviews: boolean; include_lists: boolean; diff --git a/supabase/functions/manage-moderator-topic/index.ts b/supabase/functions/manage-moderator-topic/index.ts index a6ef002d..2dcb880f 100644 --- a/supabase/functions/manage-moderator-topic/index.ts +++ b/supabase/functions/manage-moderator-topic/index.ts @@ -1,14 +1,10 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; import { Novu } from "npm:@novu/api@1.6.0"; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; import { withEdgeRetry } from '../_shared/retryHelper.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - const TOPICS = { MODERATION_SUBMISSIONS: 'moderation-submissions', MODERATION_REPORTS: 'moderation-reports', diff --git a/supabase/functions/merge-contact-tickets/index.ts b/supabase/functions/merge-contact-tickets/index.ts index f8e50cba..0eabdcdf 100644 --- a/supabase/functions/merge-contact-tickets/index.ts +++ b/supabase/functions/merge-contact-tickets/index.ts @@ -1,13 +1,9 @@ import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { createErrorResponse, sanitizeError } from '../_shared/errorSanitizer.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface MergeTicketsRequest { primaryTicketId: string; mergeTicketIds: string[]; diff --git a/supabase/functions/mfa-unenroll/index.ts b/supabase/functions/mfa-unenroll/index.ts index c31d272b..967778e0 100644 --- a/supabase/functions/mfa-unenroll/index.ts +++ b/supabase/functions/mfa-unenroll/index.ts @@ -1,12 +1,8 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - Deno.serve(async (req) => { const tracking = startRequest(); diff --git a/supabase/functions/migrate-novu-users/index.ts b/supabase/functions/migrate-novu-users/index.ts index 2aab1651..7cad78ba 100644 --- a/supabase/functions/migrate-novu-users/index.ts +++ b/supabase/functions/migrate-novu-users/index.ts @@ -1,13 +1,9 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; import { Novu } from "npm:@novu/api@1.6.0"; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); diff --git a/supabase/functions/notify-moderators-report/index.ts b/supabase/functions/notify-moderators-report/index.ts index 245c9b11..841f82df 100644 --- a/supabase/functions/notify-moderators-report/index.ts +++ b/supabase/functions/notify-moderators-report/index.ts @@ -1,13 +1,9 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; import { withEdgeRetry } from '../_shared/retryHelper.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - interface NotificationPayload { reportId: string; reportType: string; diff --git a/supabase/functions/notify-moderators-submission/index.ts b/supabase/functions/notify-moderators-submission/index.ts index 6fc66710..a4fcc992 100644 --- a/supabase/functions/notify-moderators-submission/index.ts +++ b/supabase/functions/notify-moderators-submission/index.ts @@ -1,13 +1,9 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { withEdgeRetry } from '../_shared/retryHelper.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface NotificationPayload { submission_id: string; submission_type: string; diff --git a/supabase/functions/notify-system-announcement/index.ts b/supabase/functions/notify-system-announcement/index.ts index f0cfa0b6..54e4259e 100644 --- a/supabase/functions/notify-system-announcement/index.ts +++ b/supabase/functions/notify-system-announcement/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - interface AnnouncementPayload { title: string; message: string; diff --git a/supabase/functions/notify-user-submission-status/index.ts b/supabase/functions/notify-user-submission-status/index.ts index d6fde93b..857f0501 100644 --- a/supabase/functions/notify-user-submission-status/index.ts +++ b/supabase/functions/notify-user-submission-status/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - interface RequestBody { submission_id: string; user_id: string; diff --git a/supabase/functions/novu-webhook/index.ts b/supabase/functions/novu-webhook/index.ts index c916f1c4..c53957cf 100644 --- a/supabase/functions/novu-webhook/index.ts +++ b/supabase/functions/novu-webhook/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - // Simple request tracking const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() }); const endRequest = (tracking: { start: number }) => Date.now() - tracking.start; diff --git a/supabase/functions/process-expired-bans/index.ts b/supabase/functions/process-expired-bans/index.ts index 062e39a9..53b739d2 100644 --- a/supabase/functions/process-expired-bans/index.ts +++ b/supabase/functions/process-expired-bans/index.ts @@ -1,11 +1,7 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - Deno.serve(async (req) => { // Handle CORS preflight if (req.method === 'OPTIONS') { diff --git a/supabase/functions/process-oauth-profile/index.ts b/supabase/functions/process-oauth-profile/index.ts index 0110762d..ed15d387 100644 --- a/supabase/functions/process-oauth-profile/index.ts +++ b/supabase/functions/process-oauth-profile/index.ts @@ -1,12 +1,8 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - const CLOUDFLARE_ACCOUNT_ID = Deno.env.get('CLOUDFLARE_ACCOUNT_ID'); const CLOUDFLARE_API_TOKEN = Deno.env.get('CLOUDFLARE_IMAGES_API_TOKEN'); diff --git a/supabase/functions/process-scheduled-deletions/index.ts b/supabase/functions/process-scheduled-deletions/index.ts index bf889e34..f1b54010 100644 --- a/supabase/functions/process-scheduled-deletions/index.ts +++ b/supabase/functions/process-scheduled-deletions/index.ts @@ -1,12 +1,8 @@ import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); diff --git a/supabase/functions/process-selective-approval/cors.ts b/supabase/functions/process-selective-approval/cors.ts deleted file mode 100644 index 8708b3b4..00000000 --- a/supabase/functions/process-selective-approval/cors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, traceparent, x-request-id', -}; diff --git a/supabase/functions/process-selective-approval/index.ts b/supabase/functions/process-selective-approval/index.ts index e0bfe17d..f992cf40 100644 --- a/supabase/functions/process-selective-approval/index.ts +++ b/supabase/functions/process-selective-approval/index.ts @@ -1,6 +1,6 @@ import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { corsHeaders } from './cors.ts'; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts'; import { edgeLogger, diff --git a/supabase/functions/process-selective-rejection/cors.ts b/supabase/functions/process-selective-rejection/cors.ts deleted file mode 100644 index 8708b3b4..00000000 --- a/supabase/functions/process-selective-rejection/cors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, traceparent, x-request-id', -}; diff --git a/supabase/functions/process-selective-rejection/index.ts b/supabase/functions/process-selective-rejection/index.ts index 9cb817cb..050aeae3 100644 --- a/supabase/functions/process-selective-rejection/index.ts +++ b/supabase/functions/process-selective-rejection/index.ts @@ -1,6 +1,6 @@ import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { corsHeaders } from './cors.ts'; +import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts'; import { edgeLogger, diff --git a/supabase/functions/receive-inbound-email/index.ts b/supabase/functions/receive-inbound-email/index.ts index eec30ca8..b891895c 100644 --- a/supabase/functions/receive-inbound-email/index.ts +++ b/supabase/functions/receive-inbound-email/index.ts @@ -1,14 +1,10 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; import { createErrorResponse } from "../_shared/errorSanitizer.ts"; import { formatEdgeError } from "../_shared/errorFormatter.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface InboundEmailPayload { from: string; to: string; diff --git a/supabase/functions/remove-novu-subscriber/index.ts b/supabase/functions/remove-novu-subscriber/index.ts index 1be45579..cdcc9e0f 100644 --- a/supabase/functions/remove-novu-subscriber/index.ts +++ b/supabase/functions/remove-novu-subscriber/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { Novu } from "npm:@novu/api@1.6.0"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); diff --git a/supabase/functions/request-account-deletion/index.ts b/supabase/functions/request-account-deletion/index.ts index 76b8efe4..fdfa7aba 100644 --- a/supabase/functions/request-account-deletion/index.ts +++ b/supabase/functions/request-account-deletion/index.ts @@ -1,12 +1,8 @@ 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 { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - serve(async (req) => { const tracking = startRequest(); diff --git a/supabase/functions/resend-deletion-code/index.ts b/supabase/functions/resend-deletion-code/index.ts index fe6140f8..ec589344 100644 --- a/supabase/functions/resend-deletion-code/index.ts +++ b/supabase/functions/resend-deletion-code/index.ts @@ -1,12 +1,8 @@ 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 { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - serve(async (req) => { const tracking = startRequest(); diff --git a/supabase/functions/run-cleanup-jobs/index.ts b/supabase/functions/run-cleanup-jobs/index.ts index 8a58421e..92dd8169 100644 --- a/supabase/functions/run-cleanup-jobs/index.ts +++ b/supabase/functions/run-cleanup-jobs/index.ts @@ -11,13 +11,9 @@ */ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface CleanupResult { idempotency_keys?: { deleted: number; diff --git a/supabase/functions/scheduled-maintenance/index.ts b/supabase/functions/scheduled-maintenance/index.ts index 7f12379d..49fb31f1 100644 --- a/supabase/functions/scheduled-maintenance/index.ts +++ b/supabase/functions/scheduled-maintenance/index.ts @@ -1,13 +1,9 @@ import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - serve(async (req: Request) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); diff --git a/supabase/functions/seed-test-data/index.ts b/supabase/functions/seed-test-data/index.ts index 517f9744..f9bfd4f8 100644 --- a/supabase/functions/seed-test-data/index.ts +++ b/supabase/functions/seed-test-data/index.ts @@ -1,11 +1,7 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface SeedOptions { preset: 'small' | 'medium' | 'large' | 'stress'; entityTypes: string[]; diff --git a/supabase/functions/send-admin-email-reply/index.ts b/supabase/functions/send-admin-email-reply/index.ts index 02aedee0..977d120d 100644 --- a/supabase/functions/send-admin-email-reply/index.ts +++ b/supabase/functions/send-admin-email-reply/index.ts @@ -1,14 +1,10 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; import { createErrorResponse } from "../_shared/errorSanitizer.ts"; import { formatEdgeError } from "../_shared/errorFormatter.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface AdminReplyRequest { submissionId: string; replyBody: string; diff --git a/supabase/functions/send-contact-message/index.ts b/supabase/functions/send-contact-message/index.ts index c342824e..13add149 100644 --- a/supabase/functions/send-contact-message/index.ts +++ b/supabase/functions/send-contact-message/index.ts @@ -1,14 +1,10 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from "../_shared/logger.ts"; import { createErrorResponse } from "../_shared/errorSanitizer.ts"; import { formatEdgeError } from "../_shared/errorFormatter.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface ContactSubmission { name: string; email: string; diff --git a/supabase/functions/send-escalation-notification/index.ts b/supabase/functions/send-escalation-notification/index.ts index fce0ae80..b3e7569c 100644 --- a/supabase/functions/send-escalation-notification/index.ts +++ b/supabase/functions/send-escalation-notification/index.ts @@ -1,13 +1,9 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { withEdgeRetry } from '../_shared/retryHelper.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface EscalationRequest { submissionId: string; escalationReason: string; diff --git a/supabase/functions/send-password-added-email/index.ts b/supabase/functions/send-password-added-email/index.ts index 1a2c42eb..b39e8bc0 100644 --- a/supabase/functions/send-password-added-email/index.ts +++ b/supabase/functions/send-password-added-email/index.ts @@ -1,12 +1,8 @@ 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 { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - interface EmailRequest { email: string; displayName?: string; diff --git a/supabase/functions/sync-all-moderators-to-topic/index.ts b/supabase/functions/sync-all-moderators-to-topic/index.ts index eb759da7..8c3a9823 100644 --- a/supabase/functions/sync-all-moderators-to-topic/index.ts +++ b/supabase/functions/sync-all-moderators-to-topic/index.ts @@ -1,14 +1,10 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; import { Novu } from "npm:@novu/api@1.6.0"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; import { withEdgeRetry } from '../_shared/retryHelper.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - const TOPICS = { MODERATION_SUBMISSIONS: 'moderation-submissions', MODERATION_REPORTS: 'moderation-reports', diff --git a/supabase/functions/trigger-notification/index.ts b/supabase/functions/trigger-notification/index.ts index 42023f9a..18f41948 100644 --- a/supabase/functions/trigger-notification/index.ts +++ b/supabase/functions/trigger-notification/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { Novu } from "npm:@novu/api@1.6.0"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); diff --git a/supabase/functions/update-novu-preferences/index.ts b/supabase/functions/update-novu-preferences/index.ts index 344b462a..561ecb33 100644 --- a/supabase/functions/update-novu-preferences/index.ts +++ b/supabase/functions/update-novu-preferences/index.ts @@ -1,13 +1,9 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { Novu } from "npm:@novu/api@1.6.0"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - serve(async (req) => { const tracking = startRequest('update-novu-preferences'); diff --git a/supabase/functions/update-novu-subscriber/index.ts b/supabase/functions/update-novu-subscriber/index.ts index 8e7b6158..2471f277 100644 --- a/supabase/functions/update-novu-subscriber/index.ts +++ b/supabase/functions/update-novu-subscriber/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { Novu } from "npm:@novu/api@1.6.0"; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); diff --git a/supabase/functions/upload-image/index.ts b/supabase/functions/upload-image/index.ts index 313fe5c0..c14174c2 100644 --- a/supabase/functions/upload-image/index.ts +++ b/supabase/functions/upload-image/index.ts @@ -1,62 +1,10 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' +import { getAllowedOrigin, getCorsHeaders } from '../_shared/cors.ts' import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts' import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts' import { formatEdgeError } from '../_shared/errorFormatter.ts' -// Environment-aware CORS configuration -const getAllowedOrigin = (requestOrigin: string | null): string | null => { - // If no origin header, it's not a CORS request (same-origin or server-to-server) - if (!requestOrigin) { - return null; - } - - const environment = Deno.env.get('ENVIRONMENT') || 'development'; - - // Production allowlist - configure via ALLOWED_ORIGINS environment variable - // Format: comma-separated list of origins, e.g., "https://example.com,https://www.example.com" - const allowedOriginsEnv = Deno.env.get('ALLOWED_ORIGINS') || ''; - const allowedOrigins = allowedOriginsEnv.split(',').filter(origin => origin.trim()); - - // In development, only allow localhost and Replit domains - nothing else - if (environment === 'development') { - if ( - requestOrigin.includes('localhost') || - requestOrigin.includes('127.0.0.1') || - requestOrigin.includes('.repl.co') || - requestOrigin.includes('.replit.dev') - ) { - return requestOrigin; - } - // Origin not allowed in development - log and deny - edgeLogger.warn('CORS origin not allowed in development mode', { origin: requestOrigin }); - return null; - } - - // In production, only allow specific domains from environment variable - if (allowedOrigins.includes(requestOrigin)) { - return requestOrigin; - } - - // Origin not allowed in production - log and deny - edgeLogger.warn('CORS origin not allowed in production mode', { origin: requestOrigin }); - return null; -}; - -const getCorsHeaders = (allowedOrigin: string | null): Record => { - // If no allowed origin, return empty headers (no CORS access) - if (!allowedOrigin) { - return {}; - } - - return { - 'Access-Control-Allow-Origin': allowedOrigin, - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', - 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS', - 'Access-Control-Allow-Credentials': 'true', - }; -}; - // Helper to create authenticated Supabase client const createAuthenticatedSupabaseClient = (authHeader: string) => { const supabaseUrl = Deno.env.get('SUPABASE_URL') diff --git a/supabase/functions/validate-email-backend/index.ts b/supabase/functions/validate-email-backend/index.ts index 5bab24aa..39462779 100644 --- a/supabase/functions/validate-email-backend/index.ts +++ b/supabase/functions/validate-email-backend/index.ts @@ -1,13 +1,9 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.3'; +import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from "../_shared/logger.ts"; import { formatEdgeError } from "../_shared/errorFormatter.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - // Simple request tracking const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() }); const endRequest = (tracking: { start: number }) => Date.now() - tracking.start; diff --git a/supabase/functions/validate-email/index.ts b/supabase/functions/validate-email/index.ts index 07af9146..31235bb2 100644 --- a/supabase/functions/validate-email/index.ts +++ b/supabase/functions/validate-email/index.ts @@ -1,12 +1,8 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; +import { corsHeaders } from '../_shared/cors.ts'; import { startRequest, endRequest, edgeLogger } from "../_shared/logger.ts"; import { formatEdgeError } from "../_shared/errorFormatter.ts"; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', -}; - // Comprehensive list of disposable email domains const DISPOSABLE_DOMAINS = new Set([ // Popular disposable email services