From 046c9625a2991babb33a81f8a68c38aff3866a95 Mon Sep 17 00:00:00 2001 From: oleksandr-shysh Date: Mon, 9 Jun 2025 16:29:03 +0300 Subject: [PATCH] Configure CORS --- package-lock.json | 26 +++++++++++++++++++++++- package.json | 2 ++ src/server/Master.ts | 5 ++++- src/server/Worker.ts | 3 +++ src/server/cors.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/server/cors.ts diff --git a/package-lock.json b/package-lock.json index faad9e243..f4f95a913 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "binary-loader": "^0.0.1", "colord": "^2.9.3", "copy-webpack-plugin": "^12.0.2", + "cors": "^2.8.5", "crypto": "^1.0.1", "d3": "^7.9.0", "discord.js": "^14.16.3", @@ -81,6 +82,7 @@ "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@types/chai": "^4.3.17", + "@types/cors": "^2.8.19", "@types/d3": "^7.4.3", "@types/jest": "^29.5.12", "@types/jquery": "^3.5.31", @@ -7822,6 +7824,16 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -10626,6 +10638,19 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -16691,7 +16716,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 6eb96b8c3..516332ca9 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@types/chai": "^4.3.17", + "@types/cors": "^2.8.19", "@types/d3": "^7.4.3", "@types/jest": "^29.5.12", "@types/jquery": "^3.5.31", @@ -106,6 +107,7 @@ "binary-loader": "^0.0.1", "colord": "^2.9.3", "copy-webpack-plugin": "^12.0.2", + "cors": "^2.8.5", "crypto": "^1.0.1", "d3": "^7.9.0", "discord.js": "^14.16.3", diff --git a/src/server/Master.ts b/src/server/Master.ts index 388aba19c..11fb11ddb 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from "url"; import { getServerConfigFromServer } from "../core/configuration/ConfigLoader"; import { GameInfo } from "../core/Schemas"; import { generateID } from "../core/Util"; +import { corsMiddleware } from "./cors"; import { gatekeeper, LimiterType } from "./Gatekeeper"; import { logger } from "./Logger"; import { MapPlaylist } from "./MapPlaylist"; @@ -22,7 +23,9 @@ const log = logger.child({ comp: "m" }); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -app.use(express.json()); + +app.use(corsMiddleware); + app.use( express.static(path.join(__dirname, "../../static"), { maxAge: "1y", // Set max-age to 1 year for all static assets diff --git a/src/server/Worker.ts b/src/server/Worker.ts index 5bee6b603..7ef9af6c4 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -17,6 +17,7 @@ import { import { CreateGameInputSchema, GameInputSchema } from "../core/WorkerSchemas"; import { archive, readGameRecord } from "./Archive"; import { Client } from "./Client"; +import { corsMiddleware } from "./cors"; import { GameManager } from "./GameManager"; import { gatekeeper, LimiterType } from "./Gatekeeper"; import { getUserMe, verifyClientToken } from "./jwt"; @@ -70,6 +71,8 @@ export function startWorker() { next(); }); + app.use(corsMiddleware); + app.set("trust proxy", 3); app.use(express.json()); app.use(express.static(path.join(__dirname, "../../out"))); diff --git a/src/server/cors.ts b/src/server/cors.ts new file mode 100644 index 000000000..80c125ffd --- /dev/null +++ b/src/server/cors.ts @@ -0,0 +1,48 @@ +import cors from "cors"; +import os from "os"; + +function getLocalIP() { + const interfaces = os.networkInterfaces(); + for (const interfaceName in interfaces) { + const networkInterface = interfaces[interfaceName]; + if (!networkInterface) continue; + for (const address of networkInterface) { + if (address.family === "IPv4" && !address.internal) { + return address.address; + } + } + } + return null; +} + +const allowedOrigins = [ + "capacitor://localhost", + "https://localhost", + "http://localhost", + "http://localhost:9000", + "https://openfront.io", + "https://openfront.dev", +]; + +const localIp = getLocalIP(); +if (localIp) { + allowedOrigins.push(`http://${localIp}:9000`); +} + +const corsOptions = { + origin: ( + origin: string | undefined, + callback: (err: Error | null, allow?: boolean) => void, + ) => { + // allow requests with no origin (like mobile apps or curl requests) + if (!origin) return callback(null, true); + if (allowedOrigins.indexOf(origin) !== -1) { + callback(null, true); + } else { + callback(new Error("Not allowed by CORS")); + } + }, + credentials: true, +}; + +export const corsMiddleware = cors(corsOptions);