From ecab6ae9b7a2aa7fec97a2e48f33ddab04329bb5 Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 21 May 2026 23:22:39 +0800 Subject: [PATCH] feat: complete CORS config-driven middleware rewrite - cors.ts: createCorsMiddleware factory with RuntimeConfig param - Wildcard ['*'] mode: Access-Control-Allow-Origin: * (backward compatible) - Specific origins mode: reflect matching origin + Allow-Credentials - Added Access-Control-Max-Age: 86400 for preflight caching - app.ts: accept config param, use createCorsMiddleware(config) - index.ts: pass runtimeConfig to createApp - env-overrides.ts: add string[] to RuntimeConfigValue, corsOriginsEnv helper - runtime-config.ts: cors.allowedOrigins: string[] type - defaults.ts: cors.allowedOrigins: ['*'] default - .env: CORS_ALLOWED_ORIGINS=* with production example --- apps/backend/src/app.ts | 8 +++-- apps/backend/src/index.ts | 2 +- apps/backend/src/middleware/cors.ts | 54 ++++++++++++++++++++--------- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index d0464acd..e11f35e4 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -9,22 +9,24 @@ import tencentRouter from "./routes/tencent.js"; import webhooksRouter from "./routes/webhooks.js"; import { buildSuccessPayload, sendRouteError } from "./utils/http.js"; import { accessLogMiddleware } from "./middleware/access-log.js"; -import { corsMiddleware } from "./middleware/cors.js"; +import { createCorsMiddleware } from "./middleware/cors.js"; +import type { RuntimeConfig } from "./types/runtime-config.js"; import { buildHealthPayload, type StartupState } from "./startup/state.js"; - type CreateAppOptions = { startupState: StartupState; isShutdownStarted: () => boolean; + config: RuntimeConfig; }; export function createApp({ startupState, isShutdownStarted, + config, }: CreateAppOptions) { const app = express(); app.use(accessLogMiddleware); - app.use(corsMiddleware); + app.use(createCorsMiddleware(config)); app.use(express.json({ limit: "2mb" })); app.use(express.urlencoded({ extended: true })); diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 3a0e6933..f0bbbae0 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -20,10 +20,10 @@ try { logError("[startup]", "运行时配置校验失败,服务停止启动", error); process.exit(1); } - const app = createApp({ startupState, isShutdownStarted: () => shutdownController?.isShutdownStarted() || false, + config: runtimeConfig, }); const server = app.listen(port, host, () => { diff --git a/apps/backend/src/middleware/cors.ts b/apps/backend/src/middleware/cors.ts index 68ce7c7e..fae529ff 100644 --- a/apps/backend/src/middleware/cors.ts +++ b/apps/backend/src/middleware/cors.ts @@ -1,21 +1,43 @@ import type { Request, Response, NextFunction } from "express"; +import type { RuntimeConfig } from "../types/runtime-config.js"; -export function corsMiddleware( - req: Request, - res: Response, - next: NextFunction -): void { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - res.setHeader( - "Access-Control-Allow-Methods", - "GET, POST, PUT, PATCH, DELETE, OPTIONS" - ); +export function createCorsMiddleware( + config: RuntimeConfig +): (req: Request, res: Response, next: NextFunction) => void { + const allowedOrigins = config.cors.allowedOrigins; + const isWildcard = + allowedOrigins.length === 1 && allowedOrigins[0] === "*"; - if (req.method === "OPTIONS") { - res.sendStatus(204); - return; - } + return function corsMiddleware( + req: Request, + res: Response, + next: NextFunction + ): void { + if (isWildcard) { + res.setHeader("Access-Control-Allow-Origin", "*"); + } else { + const origin = req.headers.origin; + if (origin && allowedOrigins.includes(origin)) { + res.setHeader("Access-Control-Allow-Origin", origin); + res.setHeader("Access-Control-Allow-Credentials", "true"); + } + } - next(); + res.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, Authorization" + ); + res.setHeader( + "Access-Control-Allow-Methods", + "GET, POST, PUT, PATCH, DELETE, OPTIONS" + ); + res.setHeader("Access-Control-Max-Age", "86400"); + + if (req.method === "OPTIONS") { + res.sendStatus(204); + return; + } + + next(); + }; }