From 93a2c10b5edbd5486063ed28da04d1401f551cb7 Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Fri, 24 Apr 2026 20:46:07 -0700 Subject: [PATCH 1/7] harden(auth): enforce production cookie origin config Resolve secure cookie configuration explicitly and refuse production auth deployments that are not HTTPS same-origin for credentialed requests. Made-with: Cursor --- README.md | 25 ++++++++++++ lib/appFactory.js | 61 +++++++++++++++++++++++++--- server.js | 12 +++++- tests/appFactory.config.test.js | 72 +++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 tests/appFactory.config.test.js diff --git a/README.md b/README.md index 4c248a9..56915dd 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,31 @@ All configuration is via environment variables. `.env.example` is the source of | `SYSCOIN_RPC_USER`, `SYSCOIN_RPC_PASS` | Fallback static creds for remote RPC nodes | | `SYSCOIN_NETWORK`, `SYSCOIN_BLOCKBOOK_URL` | Enables the Pay-with-Pali collateral PSBT path | +## Production authenticated deployment + +Real voting-key custody should use a same-origin deployment for the authenticated API surface. Serve the SPA and proxy `/auth`, `/vault`, and `/gov` from the same public HTTPS origin: + +```text +https://sysnode.info/ -> sysnode-info build +https://sysnode.info/auth/* -> sysnode-backend +https://sysnode.info/vault/* -> sysnode-backend +https://sysnode.info/gov/* -> sysnode-backend +``` + +This keeps the `sid` and `csrf` cookies host-only with `Secure; SameSite=Lax`, and lets the SPA read the `csrf` cookie from the same host before mirroring it into `X-CSRF-Token`. Do not deploy the real-key auth/vault/voting surface as `sysnode.info` plus a cross-site API host. + +For production, set at least: + +```bash +NODE_ENV=production +FRONTEND_URL=https://sysnode.info +CORS_ORIGIN=https://sysnode.info +TRUST_PROXY=1 # or the exact trusted proxy/CIDR for your edge +SYSNODE_AUTH_PEPPER=<32-byte-hex-secret> +``` + +Production startup refuses non-secure cookies, non-HTTPS `FRONTEND_URL`, or a credentialed CORS origin that differs from `FRONTEND_URL`. + ### Cookie vs static RPC auth The backend supports both authentication modes and picks **cookie over static** when both are configured (with a one-line warning at boot). Cookie auth is zero-secret-management: `syscoind` rewrites the cookie on every restart, and the backend picks up the new token automatically via a 401-driven replay. Use it for any deployment where the backend runs on the same host as `syscoind`. diff --git a/lib/appFactory.js b/lib/appFactory.js index 70a2d66..22fe7da 100644 --- a/lib/appFactory.js +++ b/lib/appFactory.js @@ -23,6 +23,45 @@ const { createGovRouter } = require('../routes/gov'); const { createGovProposalsRouter } = require('../routes/govProposals'); const securityLog = require('./securityLog'); +function parseBooleanEnv(value, name) { + if (value === undefined || value === '') return undefined; + if (value === 'true') return true; + if (value === 'false') return false; + throw new Error(`${name}_invalid`); +} + +function isProduction() { + return process.env.NODE_ENV === 'production'; +} + +function resolveSecureCookies(explicit) { + const fromEnv = parseBooleanEnv( + process.env.SYSNODE_SECURE_COOKIES, + 'SYSNODE_SECURE_COOKIES' + ); + if (typeof explicit === 'boolean') return explicit; + if (typeof fromEnv === 'boolean') return fromEnv; + return isProduction(); +} + +function assertSecureCookieConfig(secureCookies) { + if (isProduction() && secureCookies !== true) { + throw new Error('secure_cookies_required_in_production'); + } +} + +function assertProductionAuthConfig({ secureCookies, corsOrigin, frontendUrl }) { + assertSecureCookieConfig(secureCookies); + if (!isProduction()) return; + + if (!frontendUrl || !/^https:\/\//i.test(frontendUrl)) { + throw new Error('frontend_https_url_required_in_production'); + } + if (!corsOrigin || corsOrigin !== frontendUrl) { + throw new Error('same_origin_cors_required_in_production'); + } +} + // Build the stateful services (repos + middlewares) around a DB handle. // Pure-ish: no Express side effects yet, so the same object graph can be // mounted onto either a dedicated test app (see `createApp`) or the legacy @@ -30,9 +69,11 @@ const securityLog = require('./securityLog'); function buildServices({ db, now = () => Date.now(), - secureCookies = process.env.NODE_ENV === 'production', + secureCookies, } = {}) { if (!db) throw new Error('buildServices: db is required'); + const resolvedSecureCookies = resolveSecureCookies(secureCookies); + assertSecureCookieConfig(resolvedSecureCookies); return { users: createUsersRepo(db, { now }), sessions: createSessionStore(db, { now }), @@ -43,8 +84,8 @@ function buildServices({ proposalDrafts: createProposalDraftsRepo(db, { now }), proposalSubmissions: createProposalSubmissionsRepo(db, { now }), sessionMw: null, // finalized once we know `users` - csrfMw: createCsrfMiddleware({ secureCookies }), - secureCookies, + csrfMw: createCsrfMiddleware({ secureCookies: resolvedSecureCookies }), + secureCookies: resolvedSecureCookies, // Shared atomic-write helper. Route handlers that touch multiple // repos (/verify-email = pendingRegistrations.redeem + // users.create; /change-password = users.updateAuthHash + @@ -243,7 +284,7 @@ function createApp({ db, mailer, now = () => Date.now(), - secureCookies = process.env.NODE_ENV === 'production', + secureCookies, corsOrigin = process.env.CORS_ORIGIN || 'http://localhost:3000', baseUrl = process.env.BASE_URL || 'http://localhost:3001', frontendUrl = process.env.FRONTEND_URL || @@ -271,7 +312,16 @@ function createApp({ if (!db) throw new Error('appFactory: db is required'); if (!mailer) throw new Error('appFactory: mailer is required'); - const services = finalizeSessionMw(buildServices({ db, now, secureCookies })); + const resolvedSecureCookies = resolveSecureCookies(secureCookies); + assertProductionAuthConfig({ + secureCookies: resolvedSecureCookies, + corsOrigin, + frontendUrl, + }); + + const services = finalizeSessionMw( + buildServices({ db, now, secureCookies: resolvedSecureCookies }) + ); const app = express(); app.use(helmet()); @@ -429,6 +479,7 @@ function createApp({ } module.exports = { + assertProductionAuthConfig, buildServices, finalizeSessionMw, mountAuthAndVault, diff --git a/server.js b/server.js index 53d5f69..df500e1 100644 --- a/server.js +++ b/server.js @@ -23,6 +23,7 @@ const { createMailer } = require('./lib/mailer'); const { selectMailTransport } = require('./lib/mailTransport'); const { assertPepperConfigured } = require('./lib/kdf'); const { + assertProductionAuthConfig, buildServices, finalizeSessionMw, mountAuthAndVault, @@ -105,8 +106,12 @@ app.use(cookieParser()); // legacy surface evolves. // ----------------------------------------------------------------------------- const legacyCors = cors({ origin: '*', optionsSuccessStatus: 200 }); +const AUTH_ORIGIN = + process.env.CORS_ORIGIN || + process.env.FRONTEND_URL || + 'http://localhost:3000'; const authCors = cors({ - origin: process.env.CORS_ORIGIN || 'http://localhost:3000', + origin: AUTH_ORIGIN, credentials: true, }); const { isCredentialedPath } = require('./lib/credentialedPaths'); @@ -182,6 +187,11 @@ const mailer = createMailer({ publicBaseUrl: PUBLIC_BASE_URL, }); const services = finalizeSessionMw(buildServices({ db })); +assertProductionAuthConfig({ + secureCookies: services.secureCookies, + corsOrigin: AUTH_ORIGIN, + frontendUrl: PUBLIC_BASE_URL, +}); // Session parsing must cover every route that reads `req.user`. /gov // uses `requireAuth` in its router; without parse running here first diff --git a/tests/appFactory.config.test.js b/tests/appFactory.config.test.js new file mode 100644 index 0000000..1651bff --- /dev/null +++ b/tests/appFactory.config.test.js @@ -0,0 +1,72 @@ +const { + assertProductionAuthConfig, + buildServices, +} = require('../lib/appFactory'); +const { openDatabase } = require('../lib/db'); +const { _resetPepperForTests } = require('../lib/kdf'); + +describe('appFactory production auth config', () => { + const originalNodeEnv = process.env.NODE_ENV; + const originalSecureCookies = process.env.SYSNODE_SECURE_COOKIES; + + beforeEach(() => { + _resetPepperForTests(); + process.env.SYSNODE_AUTH_PEPPER = 'f'.repeat(64); + delete process.env.SYSNODE_SECURE_COOKIES; + }); + + afterEach(() => { + process.env.NODE_ENV = originalNodeEnv; + if (originalSecureCookies === undefined) { + delete process.env.SYSNODE_SECURE_COOKIES; + } else { + process.env.SYSNODE_SECURE_COOKIES = originalSecureCookies; + } + }); + + test('production refuses non-secure cookies', () => { + process.env.NODE_ENV = 'production'; + expect(() => + assertProductionAuthConfig({ + secureCookies: false, + corsOrigin: 'https://sysnode.info', + frontendUrl: 'https://sysnode.info', + }) + ).toThrow('secure_cookies_required_in_production'); + }); + + test('production requires same frontend and credentialed CORS origin', () => { + process.env.NODE_ENV = 'production'; + expect(() => + assertProductionAuthConfig({ + secureCookies: true, + corsOrigin: 'https://api.sysnode.info', + frontendUrl: 'https://sysnode.info', + }) + ).toThrow('same_origin_cors_required_in_production'); + }); + + test('production requires an https frontend origin', () => { + process.env.NODE_ENV = 'production'; + expect(() => + assertProductionAuthConfig({ + secureCookies: true, + corsOrigin: 'http://sysnode.info', + frontendUrl: 'http://sysnode.info', + }) + ).toThrow('frontend_https_url_required_in_production'); + }); + + test('SYSNODE_SECURE_COOKIES=false is rejected in production', () => { + process.env.NODE_ENV = 'production'; + process.env.SYSNODE_SECURE_COOKIES = 'false'; + const db = openDatabase(':memory:'); + try { + expect(() => buildServices({ db })).toThrow( + 'secure_cookies_required_in_production' + ); + } finally { + db.close(); + } + }); +}); From 3163f0623afd3460440f51d0cb8f2d97bd8dcac1 Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Fri, 24 Apr 2026 20:50:23 -0700 Subject: [PATCH 2/7] fix(auth): normalize production origin guard Compare credentialed CORS and frontend settings by HTTPS origin so equivalent same-origin URLs do not fail production startup. Made-with: Cursor --- lib/appFactory.js | 16 ++++++++++++++-- tests/appFactory.config.test.js | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/appFactory.js b/lib/appFactory.js index 22fe7da..08305be 100644 --- a/lib/appFactory.js +++ b/lib/appFactory.js @@ -50,14 +50,26 @@ function assertSecureCookieConfig(secureCookies) { } } +function normalizeHttpsOrigin(value) { + try { + const url = new URL(value); + if (url.protocol !== 'https:') return null; + return url.origin; + } catch (_err) { + return null; + } +} + function assertProductionAuthConfig({ secureCookies, corsOrigin, frontendUrl }) { assertSecureCookieConfig(secureCookies); if (!isProduction()) return; - if (!frontendUrl || !/^https:\/\//i.test(frontendUrl)) { + const frontendOrigin = normalizeHttpsOrigin(frontendUrl); + const corsNormalizedOrigin = normalizeHttpsOrigin(corsOrigin); + if (!frontendOrigin) { throw new Error('frontend_https_url_required_in_production'); } - if (!corsOrigin || corsOrigin !== frontendUrl) { + if (!corsNormalizedOrigin || corsNormalizedOrigin !== frontendOrigin) { throw new Error('same_origin_cors_required_in_production'); } } diff --git a/tests/appFactory.config.test.js b/tests/appFactory.config.test.js index 1651bff..cbb5789 100644 --- a/tests/appFactory.config.test.js +++ b/tests/appFactory.config.test.js @@ -46,6 +46,17 @@ describe('appFactory production auth config', () => { ).toThrow('same_origin_cors_required_in_production'); }); + test('production accepts equivalent same-origin URLs after normalization', () => { + process.env.NODE_ENV = 'production'; + expect(() => + assertProductionAuthConfig({ + secureCookies: true, + corsOrigin: 'https://sysnode.info/', + frontendUrl: 'https://sysnode.info/path-that-is-not-used', + }) + ).not.toThrow(); + }); + test('production requires an https frontend origin', () => { process.env.NODE_ENV = 'production'; expect(() => From 84d206ceb43004ccd80a182336efc82a61f86050 Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Fri, 24 Apr 2026 20:57:28 -0700 Subject: [PATCH 3/7] fix(auth): align standalone CORS origin fallback Let createApp derive credentialed CORS origin from FRONTEND_URL when CORS_ORIGIN is unset, matching the production server path. Made-with: Cursor --- lib/appFactory.js | 4 +++- tests/appFactory.config.test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/appFactory.js b/lib/appFactory.js index 08305be..7aeb980 100644 --- a/lib/appFactory.js +++ b/lib/appFactory.js @@ -297,7 +297,9 @@ function createApp({ mailer, now = () => Date.now(), secureCookies, - corsOrigin = process.env.CORS_ORIGIN || 'http://localhost:3000', + corsOrigin = process.env.CORS_ORIGIN || + process.env.FRONTEND_URL || + 'http://localhost:3000', baseUrl = process.env.BASE_URL || 'http://localhost:3001', frontendUrl = process.env.FRONTEND_URL || process.env.CORS_ORIGIN || diff --git a/tests/appFactory.config.test.js b/tests/appFactory.config.test.js index cbb5789..8514dc9 100644 --- a/tests/appFactory.config.test.js +++ b/tests/appFactory.config.test.js @@ -1,13 +1,17 @@ const { assertProductionAuthConfig, buildServices, + createApp, } = require('../lib/appFactory'); const { openDatabase } = require('../lib/db'); +const { createMailer } = require('../lib/mailer'); const { _resetPepperForTests } = require('../lib/kdf'); describe('appFactory production auth config', () => { const originalNodeEnv = process.env.NODE_ENV; const originalSecureCookies = process.env.SYSNODE_SECURE_COOKIES; + const originalCorsOrigin = process.env.CORS_ORIGIN; + const originalFrontendUrl = process.env.FRONTEND_URL; beforeEach(() => { _resetPepperForTests(); @@ -22,6 +26,16 @@ describe('appFactory production auth config', () => { } else { process.env.SYSNODE_SECURE_COOKIES = originalSecureCookies; } + if (originalCorsOrigin === undefined) { + delete process.env.CORS_ORIGIN; + } else { + process.env.CORS_ORIGIN = originalCorsOrigin; + } + if (originalFrontendUrl === undefined) { + delete process.env.FRONTEND_URL; + } else { + process.env.FRONTEND_URL = originalFrontendUrl; + } }); test('production refuses non-secure cookies', () => { @@ -80,4 +94,17 @@ describe('appFactory production auth config', () => { db.close(); } }); + + test('standalone createApp accepts FRONTEND_URL as production CORS origin fallback', () => { + process.env.NODE_ENV = 'production'; + delete process.env.CORS_ORIGIN; + process.env.FRONTEND_URL = 'https://sysnode.info'; + const db = openDatabase(':memory:'); + const mailer = createMailer({ transport: 'memory', from: 't@x.com' }); + try { + expect(() => createApp({ db, mailer })).not.toThrow(); + } finally { + db.close(); + } + }); }); From 9f44ee695e30d9789f4d27a275368e1ff5897bff Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Fri, 24 Apr 2026 21:07:22 -0700 Subject: [PATCH 4/7] test(auth): restore node env safely Delete NODE_ENV during cleanup when it was originally unset so config tests do not leak stringified undefined state. Made-with: Cursor --- tests/appFactory.config.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/appFactory.config.test.js b/tests/appFactory.config.test.js index 8514dc9..0179c50 100644 --- a/tests/appFactory.config.test.js +++ b/tests/appFactory.config.test.js @@ -20,7 +20,11 @@ describe('appFactory production auth config', () => { }); afterEach(() => { - process.env.NODE_ENV = originalNodeEnv; + if (originalNodeEnv === undefined) { + delete process.env.NODE_ENV; + } else { + process.env.NODE_ENV = originalNodeEnv; + } if (originalSecureCookies === undefined) { delete process.env.SYSNODE_SECURE_COOKIES; } else { From 730a6bfd4c62ba6185de2e9987bb54bec1eb6a94 Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Fri, 24 Apr 2026 21:14:44 -0700 Subject: [PATCH 5/7] fix(auth): normalize production CORS middleware origin Reuse the HTTPS origin normalization for credentialed CORS so accepted same-origin config emits a browser-matching Access-Control-Allow-Origin value. Made-with: Cursor --- lib/appFactory.js | 9 ++++++++- server.js | 8 +++++--- tests/appFactory.config.test.js | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/appFactory.js b/lib/appFactory.js index 7aeb980..c4cb8dd 100644 --- a/lib/appFactory.js +++ b/lib/appFactory.js @@ -60,6 +60,11 @@ function normalizeHttpsOrigin(value) { } } +function normalizeProductionCorsOrigin(corsOrigin) { + if (!isProduction()) return corsOrigin; + return normalizeHttpsOrigin(corsOrigin); +} + function assertProductionAuthConfig({ secureCookies, corsOrigin, frontendUrl }) { assertSecureCookieConfig(secureCookies); if (!isProduction()) return; @@ -332,6 +337,7 @@ function createApp({ corsOrigin, frontendUrl, }); + const effectiveCorsOrigin = normalizeProductionCorsOrigin(corsOrigin); const services = finalizeSessionMw( buildServices({ db, now, secureCookies: resolvedSecureCookies }) @@ -339,7 +345,7 @@ function createApp({ const app = express(); app.use(helmet()); - app.use(cors({ origin: corsOrigin, credentials: true })); + app.use(cors({ origin: effectiveCorsOrigin, credentials: true })); app.use(express.json({ limit: '256kb' })); app.use(cookieParser()); app.use(services.sessionMw.parse); @@ -497,5 +503,6 @@ module.exports = { buildServices, finalizeSessionMw, mountAuthAndVault, + normalizeProductionCorsOrigin, createApp, }; diff --git a/server.js b/server.js index df500e1..e54aaa3 100644 --- a/server.js +++ b/server.js @@ -27,6 +27,7 @@ const { buildServices, finalizeSessionMw, mountAuthAndVault, + normalizeProductionCorsOrigin, } = require('./lib/appFactory'); const dataStore = require('./data/dataStore'); const { client, rpcServices } = require('./services/rpcClient'); @@ -106,10 +107,11 @@ app.use(cookieParser()); // legacy surface evolves. // ----------------------------------------------------------------------------- const legacyCors = cors({ origin: '*', optionsSuccessStatus: 200 }); -const AUTH_ORIGIN = +const AUTH_ORIGIN = normalizeProductionCorsOrigin( process.env.CORS_ORIGIN || - process.env.FRONTEND_URL || - 'http://localhost:3000'; + process.env.FRONTEND_URL || + 'http://localhost:3000' +); const authCors = cors({ origin: AUTH_ORIGIN, credentials: true, diff --git a/tests/appFactory.config.test.js b/tests/appFactory.config.test.js index 0179c50..964a0d0 100644 --- a/tests/appFactory.config.test.js +++ b/tests/appFactory.config.test.js @@ -2,10 +2,12 @@ const { assertProductionAuthConfig, buildServices, createApp, + normalizeProductionCorsOrigin, } = require('../lib/appFactory'); const { openDatabase } = require('../lib/db'); const { createMailer } = require('../lib/mailer'); const { _resetPepperForTests } = require('../lib/kdf'); +const request = require('supertest'); describe('appFactory production auth config', () => { const originalNodeEnv = process.env.NODE_ENV; @@ -111,4 +113,37 @@ describe('appFactory production auth config', () => { db.close(); } }); + + test('standalone createApp emits normalized production CORS origin', async () => { + process.env.NODE_ENV = 'production'; + const db = openDatabase(':memory:'); + const mailer = createMailer({ transport: 'memory', from: 't@x.com' }); + const { app } = createApp({ + db, + mailer, + corsOrigin: 'https://sysnode.info/', + frontendUrl: 'https://sysnode.info/path-that-is-not-used', + }); + try { + const res = await request(app) + .get('/health') + .set('Origin', 'https://sysnode.info'); + expect(res.headers['access-control-allow-origin']).toBe( + 'https://sysnode.info' + ); + } finally { + db.close(); + } + }); + + test('normalizes production CORS origins and preserves development values', () => { + process.env.NODE_ENV = 'production'; + expect(normalizeProductionCorsOrigin('https://sysnode.info/')).toBe( + 'https://sysnode.info' + ); + process.env.NODE_ENV = 'development'; + expect(normalizeProductionCorsOrigin('http://localhost:3000')).toBe( + 'http://localhost:3000' + ); + }); }); From 488a48700a46761b3ed68abd60c74210216181e6 Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Fri, 24 Apr 2026 21:32:32 -0700 Subject: [PATCH 6/7] fix(auth): prefer explicit secure cookie config Skip SYSNODE_SECURE_COOKIES parsing when callers provide an explicit boolean so embedded app setup is not coupled to unrelated env values. Made-with: Cursor --- lib/appFactory.js | 2 +- tests/appFactory.config.test.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/appFactory.js b/lib/appFactory.js index c4cb8dd..21208e6 100644 --- a/lib/appFactory.js +++ b/lib/appFactory.js @@ -35,11 +35,11 @@ function isProduction() { } function resolveSecureCookies(explicit) { + if (typeof explicit === 'boolean') return explicit; const fromEnv = parseBooleanEnv( process.env.SYSNODE_SECURE_COOKIES, 'SYSNODE_SECURE_COOKIES' ); - if (typeof explicit === 'boolean') return explicit; if (typeof fromEnv === 'boolean') return fromEnv; return isProduction(); } diff --git a/tests/appFactory.config.test.js b/tests/appFactory.config.test.js index 964a0d0..841dabc 100644 --- a/tests/appFactory.config.test.js +++ b/tests/appFactory.config.test.js @@ -101,6 +101,17 @@ describe('appFactory production auth config', () => { } }); + test('explicit secureCookies option ignores malformed env override', () => { + process.env.NODE_ENV = 'test'; + process.env.SYSNODE_SECURE_COOKIES = '0'; + const db = openDatabase(':memory:'); + try { + expect(() => buildServices({ db, secureCookies: false })).not.toThrow(); + } finally { + db.close(); + } + }); + test('standalone createApp accepts FRONTEND_URL as production CORS origin fallback', () => { process.env.NODE_ENV = 'production'; delete process.env.CORS_ORIGIN; From e87acc8e5d206637054eadc83a6e933341c494ee Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Fri, 24 Apr 2026 21:38:46 -0700 Subject: [PATCH 7/7] fix(auth): normalize CORS origin consistently Serialize configured CORS URLs to origins in all environments so trailing slashes or paths do not break credentialed responses. Made-with: Cursor --- lib/appFactory.js | 7 +++++-- tests/appFactory.config.test.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/appFactory.js b/lib/appFactory.js index 21208e6..6cfa923 100644 --- a/lib/appFactory.js +++ b/lib/appFactory.js @@ -61,8 +61,11 @@ function normalizeHttpsOrigin(value) { } function normalizeProductionCorsOrigin(corsOrigin) { - if (!isProduction()) return corsOrigin; - return normalizeHttpsOrigin(corsOrigin); + try { + return new URL(corsOrigin).origin; + } catch (_err) { + return corsOrigin; + } } function assertProductionAuthConfig({ secureCookies, corsOrigin, frontendUrl }) { diff --git a/tests/appFactory.config.test.js b/tests/appFactory.config.test.js index 841dabc..d0cfba9 100644 --- a/tests/appFactory.config.test.js +++ b/tests/appFactory.config.test.js @@ -153,7 +153,7 @@ describe('appFactory production auth config', () => { 'https://sysnode.info' ); process.env.NODE_ENV = 'development'; - expect(normalizeProductionCorsOrigin('http://localhost:3000')).toBe( + expect(normalizeProductionCorsOrigin('http://localhost:3000/app')).toBe( 'http://localhost:3000' ); });