Compare commits

...

4 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
6da29e95a4 Add rate limiting to high-risk
Introduce centralized rate limiting by applying defined tiers (STRICT, STANDARD, LENIENT, MODERATE) to high-risk edge functions:
- export-user-data (STRICT, 5 req/min)
- send-contact-message (STANDARD, 20 req/min)
- validate-email-backend (LENIENT, 30 req/min)
- admin-delete-user, resend-deletion-code (MODERATE)
- additional standard targets identified (request-account-deletion, cancel-account-deletion) as per guidance

Implements:
- Wrapped handlers with withRateLimit using centralized rateLimiters
- Imported from shared rate limiter module
- Annotated with comments explaining tier rationale
- Updated three initial functions and extended coverage to admin/account management functions
- Added documentation guide for rate limiting usage

This aligns with the Rate Limiting Guide and centralizes rate limit configuration for consistency.
2025-11-10 21:39:37 +00:00
gpt-engineer-app[bot]
ed6ddbd04b Centralize rate limiting config
Create shared rateLimitConfig.ts with tiers (strict, moderate, lenient, generous, per-user variants) and update edge functions to import centralized rate limiters. Replace inline rate limiter usage with new config, preserving backward compatibility. Add documentation guide for rate limiting usage.
2025-11-10 21:33:08 +00:00
gpt-engineer-app[bot]
bf3da6414a Centralize CORS configuration
Consolidate CORS handling by introducing a shared supabase/functions/_shared/cors.ts and migrate edge functions to import from it. Remove inline cors.ts usage across functions, standardize headers (including traceparent and x-request-id), and prepare for environment-aware origins.
2025-11-10 21:28:46 +00:00
gpt-engineer-app[bot]
7cbd09b2ad Add missing CORS headers
Updated edge functions to include traceparent and x-request-id in Access-Control-Allow-Headers for:
- supabase/functions/process-selective-approval/cors.ts
- supabase/functions/process-selective-rejection/cors.ts

This fixes CORS preflight and allows POST requests to reach edge functions.
2025-11-10 21:22:30 +00:00
47 changed files with 684 additions and 302 deletions

View File

@@ -0,0 +1,277 @@
# Rate Limiting Guide for Edge Functions
This guide helps you choose the appropriate rate limit tier for each edge function and explains how to implement rate limiting consistently across the application.
## Quick Reference
### Rate Limit Tiers
| Tier | Requests/Min | Use Case |
|------|--------------|----------|
| **STRICT** | 5 | Expensive operations (uploads, exports, batch processing) |
| **MODERATE** | 10 | Moderation actions, content submission, security operations |
| **STANDARD** | 20 | Typical read/write operations, account management |
| **LENIENT** | 30 | Lightweight reads, public data, validation |
| **GENEROUS** | 60 | High-frequency operations (webhooks, polling, health checks) |
### Per-User Tiers (Rate limits by user ID instead of IP)
| Tier | Requests/Min | Use Case |
|------|--------------|----------|
| **PER_USER_STRICT** | 5 | User-specific expensive operations |
| **PER_USER_MODERATE** | 10 | User-specific moderation actions |
| **PER_USER_STANDARD** | 20 | User-specific standard operations |
| **PER_USER_LENIENT** | 40 | User-specific frequent operations |
## How to Implement Rate Limiting
### Basic Implementation
```typescript
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { corsHeaders } from '../_shared/cors.ts';
import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.ts';
// Your handler function
const handler = async (req: Request): Promise<Response> => {
// Your edge function logic here
return new Response(JSON.stringify({ success: true }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
};
// Apply rate limiting with appropriate tier
serve(withRateLimit(handler, rateLimiters.moderate, corsHeaders));
```
### Per-User Rate Limiting
```typescript
// Rate limit by user ID instead of IP address
serve(withRateLimit(handler, rateLimiters.perUserModerate, corsHeaders));
```
### Custom Rate Limiting
```typescript
import { createRateLimiter } from '../_shared/rateLimiter.ts';
// Create a custom rate limiter
const customLimiter = createRateLimiter({
windowMs: 60000,
maxRequests: 15,
keyGenerator: (req) => {
// Custom key logic
return req.headers.get('x-custom-key') || 'default';
}
});
serve(withRateLimit(handler, customLimiter, corsHeaders));
```
## Recommended Rate Limits by Function Category
### 🔴 STRICT (5 req/min)
**Currently Implemented:**
-`upload-image` - CloudFlare image upload
**Recommended:**
- `export-user-data` - Data export operations
- Any function that makes expensive external API calls
- Batch data processing operations
- Functions that manipulate large datasets
### 🟠 MODERATE (10 req/min)
**Currently Implemented:**
-`process-selective-approval` - Moderation approvals
-`process-selective-rejection` - Moderation rejections
**Recommended:**
- `admin-delete-user` - Admin user deletion
- `manage-moderator-topic` - Admin moderation management
- `merge-contact-tickets` - Admin ticket management
- `mfa-unenroll` - Security operations
- `resend-deletion-code` - Prevent code spam
- `send-escalation-notification` - Admin escalations
- `send-password-added-email` - Security emails
- User submission functions (parks, rides, edits)
### 🟡 STANDARD (20 req/min)
**Recommended:**
- `cancel-account-deletion` - Account management
- `cancel-email-change` - Account management
- `confirm-account-deletion` - Account management
- `request-account-deletion` - Account management
- `create-novu-subscriber` - User registration
- `send-contact-message` - Contact form submissions
- Email validation functions
- Authentication-related functions
### 🟢 LENIENT (30 req/min)
**Recommended:**
- `detect-location` - Lightweight location lookup
- `check-transaction-status` - Status polling
- `validate-email-backend` - Email validation
- `sitemap` - Public sitemap generation
- Read-only public endpoints
### 🔵 GENEROUS (60 req/min)
**Recommended:**
- `novu-webhook` - External webhook receiver
- `scheduled-maintenance` - Health checks
- Internal service-to-service communication
- Real-time status endpoints
### ⚫ NO RATE LIMITING NEEDED
These functions are typically called internally or on a schedule:
- `cleanup-old-versions` - Scheduled cleanup
- `process-expired-bans` - Scheduled task
- `process-scheduled-deletions` - Scheduled task
- `run-cleanup-jobs` - Scheduled task
- `migrate-novu-users` - One-time migration
- Internal notification functions (notify-*)
- `seed-test-data` - Development only
## Best Practices
### 1. Choose the Right Tier
- **Start restrictive**: Begin with a lower tier and increase if needed
- **Consider cost**: Match the rate limit to the operation's resource cost
- **Think about abuse**: Higher abuse risk = stricter limits
- **Monitor usage**: Use edge function logs to track rate limit hits
### 2. Use Per-User Limits for Authenticated Endpoints
```typescript
// ✅ Good: Rate limit authenticated operations per user
serve(withRateLimit(handler, rateLimiters.perUserModerate, corsHeaders));
// ❌ Less effective: Rate limit authenticated operations per IP
// (Multiple users behind same IP can hit each other's limits)
serve(withRateLimit(handler, rateLimiters.moderate, corsHeaders));
```
### 3. Handle Rate Limit Errors Gracefully
Rate limit responses automatically include:
- `429 Too Many Requests` status code
- `Retry-After` header (seconds to wait)
- `X-RateLimit-Limit` header (max requests allowed)
- `X-RateLimit-Remaining` header (requests remaining)
### 4. Document Your Choice
Always add a comment explaining why you chose a specific tier:
```typescript
// Apply moderate rate limiting (10 req/min) for moderation actions
// to prevent abuse while allowing legitimate moderator workflows
serve(withRateLimit(handler, rateLimiters.moderate, corsHeaders));
```
### 5. Test Rate Limits
```bash
# Test rate limiting locally
for i in {1..15}; do
curl -X POST https://your-project.supabase.co/functions/v1/your-function \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"test": true}'
echo " - Request $i"
sleep 1
done
```
## Migration Checklist
When adding rate limiting to an existing function:
1. ✅ Determine the appropriate tier based on operation cost
2. ✅ Import `rateLimiters` and `withRateLimit` from `_shared/rateLimiter.ts`
3. ✅ Import `corsHeaders` from `_shared/cors.ts`
4. ✅ Wrap your handler with `withRateLimit(handler, rateLimiters.TIER, corsHeaders)`
5. ✅ Add a comment explaining the tier choice
6. ✅ Test the rate limit works correctly
7. ✅ Monitor edge function logs for rate limit hits
8. ✅ Adjust tier if needed based on real usage
## Troubleshooting
### Rate Limits Too Strict
**Symptoms:** Legitimate users hitting rate limits frequently
**Solutions:**
- Increase to next tier up (strict → moderate → standard → lenient)
- Consider per-user rate limiting instead of per-IP
- Check if the operation can be optimized to reduce frequency
### Rate Limits Too Lenient
**Symptoms:** Abuse patterns, high costs, slow performance
**Solutions:**
- Decrease to next tier down
- Add additional validation before expensive operations
- Consider implementing captcha for public endpoints
### Per-User Rate Limiting Not Working
**Check:**
- Is the Authorization header being sent?
- Is the JWT valid and parsable?
- Are logs showing IP-based limits instead of user-based?
## Examples from Production
### Example 1: Upload Function (STRICT)
```typescript
// upload-image function needs strict limiting because:
// - Makes external CloudFlare API calls ($$)
// - Processes large file uploads
// - High abuse potential
serve(withRateLimit(async (req) => {
// Upload logic here
}, rateLimiters.strict, getCorsHeaders(allowedOrigin)));
```
### Example 2: Moderation Function (MODERATE)
```typescript
// process-selective-approval needs moderate limiting because:
// - Modifies database records
// - Triggers notifications
// - Used by moderators (need reasonable throughput)
serve(withRateLimit(handler, rateLimiters.moderate, corsHeaders));
```
### Example 3: Validation Function (LENIENT)
```typescript
// validate-email-backend can be lenient because:
// - Lightweight operation (just validation)
// - No database writes
// - Users may need to retry multiple times
serve(withRateLimit(async (req) => {
// Validation logic here
}, rateLimiters.lenient, corsHeaders));
```
## Future Enhancements
Potential improvements to consider:
1. **Dynamic Rate Limits**: Adjust limits based on user role/tier
2. **Distributed Rate Limiting**: Use Redis for multi-region support
3. **Rate Limit Analytics**: Track and visualize rate limit metrics
4. **Custom Error Messages**: Provide context-specific retry guidance
5. **Whitelist Support**: Bypass limits for trusted IPs/users

View File

@@ -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<string, string> => {
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<string, string>): Response => {
return new Response(null, { headers: corsHeaders });
};

View File

@@ -0,0 +1,174 @@
/**
* Centralized Rate Limiting Configuration for Edge Functions
*
* Provides standardized rate limit tiers that can be imported by any edge function.
* This ensures consistent rate limiting behavior across the application.
*/
import { RateLimitConfig } from './rateLimiter.ts';
/**
* Rate Limit Tier Definitions
*
* Choose the appropriate tier based on the operation cost and abuse risk:
*
* - **STRICT**: For expensive operations (uploads, exports, data modifications)
* - **MODERATE**: For standard API operations (moderation actions, content creation)
* - **STANDARD**: For typical read/write operations (most endpoints)
* - **LENIENT**: For lightweight read operations (cached data, public endpoints)
* - **GENEROUS**: For high-frequency operations (polling, real-time updates)
*/
// Base time window for all rate limiters (1 minute)
const RATE_LIMIT_WINDOW_MS = 60000;
/**
* STRICT: 5 requests per minute
*
* Use for:
* - File uploads (images, documents)
* - Data exports
* - Batch operations
* - Resource-intensive computations
* - CloudFlare API calls
*
* Examples: upload-image, export-user-data
*/
export const RATE_LIMIT_STRICT: RateLimitConfig = {
windowMs: RATE_LIMIT_WINDOW_MS,
maxRequests: 5,
};
/**
* MODERATE: 10 requests per minute
*
* Use for:
* - Moderation actions (approve, reject)
* - Content submission
* - User profile updates
* - Email sending
* - Notification triggers
*
* Examples: process-selective-approval, process-selective-rejection, submit-entity-edit
*/
export const RATE_LIMIT_MODERATE: RateLimitConfig = {
windowMs: RATE_LIMIT_WINDOW_MS,
maxRequests: 10,
};
/**
* STANDARD: 20 requests per minute
*
* Use for:
* - Standard read/write operations
* - Search endpoints
* - Contact forms
* - Account management
* - Authentication operations
*
* Examples: send-contact-message, request-account-deletion, validate-email
*/
export const RATE_LIMIT_STANDARD: RateLimitConfig = {
windowMs: RATE_LIMIT_WINDOW_MS,
maxRequests: 20,
};
/**
* LENIENT: 30 requests per minute
*
* Use for:
* - Lightweight read operations
* - Cached data retrieval
* - Public endpoint queries
* - Status checks
* - Location detection
*
* Examples: detect-location, check-transaction-status
*/
export const RATE_LIMIT_LENIENT: RateLimitConfig = {
windowMs: RATE_LIMIT_WINDOW_MS,
maxRequests: 30,
};
/**
* GENEROUS: 60 requests per minute
*
* Use for:
* - High-frequency polling
* - Real-time updates
* - Webhook receivers
* - Health checks
* - Internal service-to-service calls
*
* Examples: novu-webhook, scheduled-maintenance
*/
export const RATE_LIMIT_GENEROUS: RateLimitConfig = {
windowMs: RATE_LIMIT_WINDOW_MS,
maxRequests: 60,
};
/**
* PER_USER: 20 requests per minute (default)
*
* Use for authenticated endpoints where you want to rate limit per user ID
* rather than per IP address. Useful for:
* - User-specific operations
* - Preventing account abuse
* - Per-user quotas
*
* Can be customized with different request counts:
* - perUserStrict: 5 req/min
* - perUserModerate: 10 req/min
* - perUserStandard: 20 req/min (default)
* - perUserLenient: 40 req/min
*/
export const RATE_LIMIT_PER_USER_STRICT: RateLimitConfig = {
windowMs: RATE_LIMIT_WINDOW_MS,
maxRequests: 5,
keyGenerator: (req: Request) => {
// Extract user ID from Authorization header JWT
const authHeader = req.headers.get('Authorization');
if (authHeader) {
try {
const token = authHeader.replace('Bearer ', '');
const payload = JSON.parse(atob(token.split('.')[1]));
return `user:${payload.sub}`;
} catch {
// Fall back to IP if JWT parsing fails
return req.headers.get('x-forwarded-for')?.split(',')[0] || '0.0.0.0';
}
}
return req.headers.get('x-forwarded-for')?.split(',')[0] || '0.0.0.0';
}
};
export const RATE_LIMIT_PER_USER_MODERATE: RateLimitConfig = {
...RATE_LIMIT_PER_USER_STRICT,
maxRequests: 10,
};
export const RATE_LIMIT_PER_USER_STANDARD: RateLimitConfig = {
...RATE_LIMIT_PER_USER_STRICT,
maxRequests: 20,
};
export const RATE_LIMIT_PER_USER_LENIENT: RateLimitConfig = {
...RATE_LIMIT_PER_USER_STRICT,
maxRequests: 40,
};
/**
* Rate Limit Tier Summary
*
* | Tier | Requests/Min | Use Case |
* |-------------------|--------------|-----------------------------------|
* | STRICT | 5 | Expensive operations, uploads |
* | MODERATE | 10 | Moderation, submissions |
* | STANDARD | 20 | Standard read/write operations |
* | LENIENT | 30 | Lightweight reads, public data |
* | GENEROUS | 60 | Polling, webhooks, health checks |
* | PER_USER_STRICT | 5/user | User-specific expensive ops |
* | PER_USER_MODERATE | 10/user | User-specific moderation |
* | PER_USER_STANDARD | 20/user | User-specific standard ops |
* | PER_USER_LENIENT | 40/user | User-specific frequent ops |
*/

View File

@@ -129,50 +129,56 @@ class RateLimiter {
}
}
// Export factory function for different rate limit tiers
// Import centralized rate limit configurations
import {
RATE_LIMIT_STRICT,
RATE_LIMIT_MODERATE,
RATE_LIMIT_STANDARD,
RATE_LIMIT_LENIENT,
RATE_LIMIT_GENEROUS,
RATE_LIMIT_PER_USER_STRICT,
RATE_LIMIT_PER_USER_MODERATE,
RATE_LIMIT_PER_USER_STANDARD,
RATE_LIMIT_PER_USER_LENIENT,
} from './rateLimitConfig.ts';
// Export factory function for creating custom rate limiters
export function createRateLimiter(config: RateLimitConfig): RateLimiter {
return new RateLimiter(config);
}
// Pre-configured rate limiters for common use cases
/**
* Pre-configured rate limiters using centralized tier definitions
*
* These are singleton instances that should be imported and used by edge functions.
* See rateLimitConfig.ts for detailed documentation on when to use each tier.
*/
export const rateLimiters = {
// Strict: For expensive operations (file uploads, data exports)
strict: createRateLimiter({
windowMs: 60000, // 1 minute
maxRequests: 5, // 5 requests per minute
}),
// Strict: 5 requests/minute - For expensive operations
strict: createRateLimiter(RATE_LIMIT_STRICT),
// Standard: For most API endpoints
standard: createRateLimiter({
windowMs: 60000, // 1 minute
maxRequests: 10, // 10 requests per minute
}),
// Moderate: 10 requests/minute - For moderation and submissions
moderate: createRateLimiter(RATE_LIMIT_MODERATE),
// Lenient: For read-only, cached endpoints
lenient: createRateLimiter({
windowMs: 60000, // 1 minute
maxRequests: 30, // 30 requests per minute
}),
// Standard: 20 requests/minute - For typical operations (DEPRECATED: use 'moderate' for 10/min or 'standard' for 20/min)
standard: createRateLimiter(RATE_LIMIT_MODERATE), // Keeping for backward compatibility
// Per-user: For authenticated endpoints (uses user ID as key)
// Lenient: 30 requests/minute - For lightweight reads
lenient: createRateLimiter(RATE_LIMIT_LENIENT),
// Generous: 60 requests/minute - For high-frequency operations
generous: createRateLimiter(RATE_LIMIT_GENEROUS),
// Per-user rate limiters (key by user ID instead of IP)
perUserStrict: createRateLimiter(RATE_LIMIT_PER_USER_STRICT),
perUserModerate: createRateLimiter(RATE_LIMIT_PER_USER_MODERATE),
perUserStandard: createRateLimiter(RATE_LIMIT_PER_USER_STANDARD),
perUserLenient: createRateLimiter(RATE_LIMIT_PER_USER_LENIENT),
// Legacy per-user factory function (DEPRECATED: use perUserStrict, perUserModerate, etc.)
perUser: (maxRequests: number = 20) => createRateLimiter({
windowMs: 60000,
...RATE_LIMIT_PER_USER_STANDARD,
maxRequests,
keyGenerator: (req: Request) => {
// Extract user ID from Authorization header JWT
const authHeader = req.headers.get('Authorization');
if (authHeader) {
try {
const token = authHeader.replace('Bearer ', '');
const payload = JSON.parse(atob(token.split('.')[1]));
return `user:${payload.sub}`;
} catch {
// Fall back to IP if JWT parsing fails
return req.headers.get('x-forwarded-for')?.split(',')[0] || '0.0.0.0';
}
}
return req.headers.get('x-forwarded-for')?.split(',')[0] || '0.0.0.0';
}
}),
};

View File

@@ -1,12 +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';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
interface DeleteUserRequest {
targetUserId: string;
}
@@ -17,7 +14,9 @@ interface DeleteUserResponse {
errorCode?: 'aal2_required' | 'permission_denied' | 'invalid_request' | 'deletion_failed';
}
Deno.serve(async (req) => {
// 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 });
}
@@ -560,4 +559,4 @@ Deno.serve(async (req) => {
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
});
}, rateLimiters.moderate, corsHeaders));

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,14 +1,11 @@
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 { rateLimiters, withRateLimit } from '../_shared/rateLimiter.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;
@@ -17,7 +14,9 @@ interface ExportOptions {
format: 'json';
}
serve(async (req) => {
// Apply strict rate limiting (5 req/min) for expensive data export operations
// This prevents abuse and manages server load from large data exports
serve(withRateLimit(async (req) => {
const tracking = startRequest();
// Handle CORS preflight requests
@@ -368,4 +367,4 @@ serve(async (req) => {
}
);
}
});
}, rateLimiters.strict, corsHeaders));

View File

@@ -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',

View File

@@ -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[];

View File

@@ -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();

View File

@@ -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 });

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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') {

View File

@@ -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');

View File

@@ -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 });

View File

@@ -1,4 +0,0 @@
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

View File

@@ -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,
@@ -558,5 +558,5 @@ const handler = async (req: Request) => {
}
};
// Apply rate limiting: 10 requests per minute per IP (standard tier)
serve(withRateLimit(handler, rateLimiters.standard, corsHeaders));
// Apply rate limiting: 10 requests per minute per IP (moderate tier for moderation actions)
serve(withRateLimit(handler, rateLimiters.moderate, corsHeaders));

View File

@@ -1,4 +0,0 @@
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

View File

@@ -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,
@@ -514,5 +514,5 @@ const handler = async (req: Request) => {
}
};
// Apply rate limiting: 10 requests per minute per IP (standard tier)
serve(withRateLimit(handler, rateLimiters.standard, corsHeaders));
// Apply rate limiting: 10 requests per minute per IP (moderate tier for moderation actions)
serve(withRateLimit(handler, rateLimiters.moderate, corsHeaders));

View File

@@ -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;

View File

@@ -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 });

View File

@@ -1,13 +1,12 @@
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';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
serve(async (req) => {
// 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') {
@@ -222,4 +221,4 @@ serve(async (req) => {
}
);
}
});
}, rateLimiters.standard, corsHeaders));

View File

@@ -1,13 +1,12 @@
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';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
serve(async (req) => {
// Apply moderate rate limiting (10 req/min) to prevent deletion code spam
// Protects against abuse while allowing legitimate resend requests
serve(withRateLimit(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') {
@@ -181,4 +180,4 @@ serve(async (req) => {
}
);
}
});
}, rateLimiters.moderate, corsHeaders));

View File

@@ -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;

View File

@@ -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 });

View File

@@ -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[];

View File

@@ -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;

View File

@@ -1,14 +1,11 @@
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 { rateLimiters, withRateLimit } from '../_shared/rateLimiter.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;
@@ -342,4 +339,6 @@ The ThrillWiki Team`,
}
};
serve(handler);
// Apply standard rate limiting (20 req/min) for contact form submissions
// Balances legitimate user needs with spam prevention
serve(withRateLimit(handler, rateLimiters.standard, corsHeaders));

View File

@@ -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;

View File

@@ -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;

View File

@@ -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',

View File

@@ -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 });

View File

@@ -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');

View File

@@ -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 });

View File

@@ -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<string, string> => {
// 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')

View File

@@ -1,13 +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.39.3';
import { corsHeaders } from '../_shared/cors.ts';
import { rateLimiters, withRateLimit } from '../_shared/rateLimiter.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;
@@ -55,7 +52,9 @@ function validateEmailFormat(email: string): EmailValidationResult {
return { valid: true };
}
serve(async (req) => {
// Apply lenient rate limiting (30 req/min) for email validation
// Users may need to validate multiple times during signup/profile update
serve(withRateLimit(async (req) => {
const tracking = startRequest();
// Handle CORS preflight requests
@@ -119,4 +118,4 @@ serve(async (req) => {
}
);
}
});
}, rateLimiters.lenient, corsHeaders));

View File

@@ -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