@@ -37,6 +37,12 @@ import { createRateLimitMiddleware, RATE_LIMIT_HEADERS } from './services/shared
3737import { applyCompression , compressionPrometheusMetrics } from './services/shared/compression' ;
3838import { wrapWithMonitor , type MonitoredPool } from './services/shared/poolMonitor' ;
3939import { SubscriptionTier } from '../src/types/subscription' ;
40+ import {
41+ processCorsRequest ,
42+ upsertPolicy ,
43+ getCorsAnalytics ,
44+ getViolations ,
45+ } from './services/shared/corsMiddleware' ;
4046
4147export interface StartServerOptions {
4248 port ?: number ;
@@ -284,12 +290,57 @@ export async function startServer(options: StartServerOptions = {}): Promise<Run
284290
285291 const rateLimitMw = buildRateLimitMiddleware ( ) ;
286292
293+ // Seed a default permissive CORS policy for the server's own tenant.
294+ // In production, policies should be loaded from the database per-tenant.
295+ upsertPolicy ( 'default' , {
296+ allowedOrigins : [
297+ { origin : process . env [ 'CORS_ALLOWED_ORIGIN' ] ?? '*' , isWildcard : true } ,
298+ ] ,
299+ allowCredentials : false ,
300+ exposedHeaders : [ 'X-RateLimit-Limit' , 'X-RateLimit-Remaining' , 'X-RateLimit-Reset' ] ,
301+ maxAge : 86400 ,
302+ allowMethods : [ 'GET' , 'POST' , 'PUT' , 'PATCH' , 'DELETE' , 'OPTIONS' ] ,
303+ allowHeaders : [
304+ 'Content-Type' ,
305+ 'Authorization' ,
306+ 'X-Api-Key' ,
307+ 'X-Request-Id' ,
308+ 'X-Subscription-Tier' ,
309+ ] ,
310+ active : true ,
311+ } ) ;
312+
287313 const server = http . createServer ( async ( req , res ) => {
288314 const url = new URL ( req . url ?? '/' , `http://${ req . headers . host ?? 'localhost' } ` ) ;
289315 const { pathname } = url ;
290316 const method = req . method ?? 'GET' ;
291317
292318 try {
319+ // -----------------------------------------------------------------
320+ // CORS – applied to every request before routing
321+ // -----------------------------------------------------------------
322+ const origin = typeof req . headers [ 'origin' ] === 'string' ? req . headers [ 'origin' ] : undefined ;
323+ const requestHeaders = typeof req . headers [ 'access-control-request-headers' ] === 'string'
324+ ? req . headers [ 'access-control-request-headers' ]
325+ : undefined ;
326+ const { headers : corsHeaders , allowed : corsAllowed } = processCorsRequest ( {
327+ origin,
328+ method,
329+ requestHeaders,
330+ tenantId : 'default' ,
331+ } ) ;
332+
333+ for ( const [ name , value ] of Object . entries ( corsHeaders ) ) {
334+ if ( value !== null ) res . setHeader ( name , value ) ;
335+ }
336+
337+ // Short-circuit OPTIONS preflight
338+ if ( method === 'OPTIONS' ) {
339+ res . writeHead ( corsAllowed ? 204 : 403 ) ;
340+ res . end ( ) ;
341+ return ;
342+ }
343+
293344 // -----------------------------------------------------------------
294345 // Health (bypass rate limiting)
295346 // -----------------------------------------------------------------
@@ -341,6 +392,26 @@ export async function startServer(options: StartServerOptions = {}): Promise<Run
341392 return ;
342393 }
343394
395+ // -----------------------------------------------------------------
396+ // CORS analytics GET /cors/analytics
397+ // -----------------------------------------------------------------
398+ if ( pathname === '/cors/analytics' && method === 'GET' ) {
399+ sendJson ( res , 200 , getCorsAnalytics ( ) ) ;
400+ return ;
401+ }
402+
403+ // -----------------------------------------------------------------
404+ // CORS violations GET /cors/violations
405+ // -----------------------------------------------------------------
406+ if ( pathname === '/cors/violations' && method === 'GET' ) {
407+ const tenantId = url . searchParams . get ( 'tenantId' ) ?? undefined ;
408+ const origin = url . searchParams . get ( 'origin' ) ?? undefined ;
409+ const limit = url . searchParams . get ( 'limit' ) ? Number ( url . searchParams . get ( 'limit' ) ) : 100 ;
410+ const since = url . searchParams . get ( 'since' ) ?? undefined ;
411+ sendJson ( res , 200 , getViolations ( { tenantId, origin, limit, since } ) ) ;
412+ return ;
413+ }
414+
344415 // -----------------------------------------------------------------
345416 // Rate-limit analytics GET /rate-limits/analytics
346417 // -----------------------------------------------------------------
@@ -518,6 +589,8 @@ export async function startServer(options: StartServerOptions = {}): Promise<Run
518589 console . info ( `[Server] RateLimit → GET /rate-limits/status/user?userId=...` ) ;
519590 console . info ( `[Server] RateLimit → POST /rate-limits/bypass` ) ;
520591 console . info ( `[Server] RateLimit → POST /rate-limits/config` ) ;
592+ console . info ( `[Server] CORS → GET /cors/analytics` ) ;
593+ console . info ( `[Server] CORS → GET /cors/violations` ) ;
521594 resolve ( ) ;
522595 } ) ;
523596 } ) ;
0 commit comments