From 6ad9c6fd614e218d5cf5ac260ae7f924b239a60b Mon Sep 17 00:00:00 2001 From: sherl Date: Tue, 30 Dec 2025 17:41:35 +0100 Subject: [PATCH] feat: add CORS support with user-sourced trusted origins from .env --- .env.default | 3 ++- package-lock.json | 38 ++++++++++++++++++++++++++++++++++++-- package.json | 2 ++ src/app.ts | 6 ++++-- src/tools/cors.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 src/tools/cors.ts diff --git a/.env.default b/.env.default index 5407a4c..96b7eb6 100644 --- a/.env.default +++ b/.env.default @@ -1,5 +1,6 @@ # Server config -ACCESS_TOKEN_PRIVATE_KEY=CHANGE_ME_TO_SOMETHING_RANDOM +ACCESS_TOKEN_PRIVATE_KEY=CHANGE_ME_TO_SOMETHING_RANDOM # Used to generate user tokens. Make sure this is pretty random. +TRUSTED_ORIGINS=http://localhost:6568 # Comma separated list of trusted origins. Make sure to include your PUBLIC_URL here. # TypeORM specific # Please make sure these match with docker-compose.yml, or your own postgres server. diff --git a/package-lock.json b/package-lock.json index b6a238b..4e7552f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { "name": "kittyBE", - "version": "0.0.1", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kittyBE", - "version": "0.0.1", + "version": "0.0.0", "dependencies": { + "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.1.0", "jsonwebtoken": "^9.0.3", @@ -20,6 +21,7 @@ "zod": "^4.2.1" }, "devDependencies": { + "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/jsonwebtoken": "^9.0.10", "@types/lodash": "^4.17.21", @@ -209,6 +211,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/express": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", @@ -802,6 +814,19 @@ "node": ">=6.6.0" } }, + "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/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1833,6 +1858,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/package.json b/package.json index f5a987c..99f93aa 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Your go-to place for short and memorable URLs.", "type": "commonjs", "devDependencies": { + "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/jsonwebtoken": "^9.0.10", "@types/lodash": "^4.17.21", @@ -13,6 +14,7 @@ "typescript": "^5.8.2" }, "dependencies": { + "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.1.0", "jsonwebtoken": "^9.0.3", diff --git a/src/app.ts b/src/app.ts index 883d33d..5c00ddf 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,10 +3,11 @@ dotenv.config({ quiet: true }); import express from 'express'; import { version } from '../package.json'; -import miscRouter from './routes/miscRoutes'; -import userRouter from './routes/userRoutes'; import { AppDataSource } from './data-source' import inferUser from './middleware/inferUser'; +import miscRouter from './routes/miscRoutes'; +import userRouter from './routes/userRoutes'; +import { getCorsConfig } from './tools/cors'; AppDataSource.initialize().then(async () => { @@ -15,6 +16,7 @@ AppDataSource.initialize().then(async () => { const app: express.Express = express(); app.use(express.json()); + app.use(getCorsConfig()); app.use(inferUser); app.use(miscRouter, userRouter); diff --git a/src/tools/cors.ts b/src/tools/cors.ts new file mode 100644 index 0000000..ce61bc8 --- /dev/null +++ b/src/tools/cors.ts @@ -0,0 +1,44 @@ +import * as dotenv from 'dotenv'; +dotenv.config({ quiet: true }); + +import cors from 'cors'; +import { getEnvString } from './jwt'; + +/** + * Returns user-trusted origins from the .env file. + * Defaults to http://localhost:6568 if no user config is found. + * + * @return {string[]} A list of user-trusted origins. + */ +function getTrustedOrigins(): string[] { + let trustedOrigins: string[] = ['http://localhost:6568']; + const configOriginsString: string | undefined = getEnvString('trustedOrigins', true); + + // No config available. + if (configOriginsString === undefined) { + console.log('WARN: trustedOrigins is unknown. Defaulting to http://localhost:6568. CORS might not work.'); + return trustedOrigins; + } + // Config available + else if (typeof configOriginsString === 'string') + // But if it's empty, return defaults. + if (configOriginsString === '') + return trustedOrigins; + // Otherwise overwrite trustedOrigins with user-provided comma-separated values. + else + trustedOrigins = configOriginsString.split(','); + + return trustedOrigins; +} + +/** + * Retruns the CORS configuration containing user-provided origins. + * If none were found, they default to http://localhost:6568. + * + * @return {any} The cors configuration. + */ +export function getCorsConfig(): any { + return cors({ + origin: getTrustedOrigins() + }); +}