diff --git a/src/core/api/router.js b/src/core/api/router.js index 1b89e73..d6bb5fb 100644 --- a/src/core/api/router.js +++ b/src/core/api/router.js @@ -30,6 +30,22 @@ import { handleGetFile } from './handlers/storage.js'; // Get cookie name from environment or use default const COOKIE_NAME = getSessionCookieName(); +/** + * Resolve the canonical application URL from environment variables. + * + * Priority: + * 1. NEXT_PUBLIC_URL_DEV — used when NODE_ENV=development + * 2. NEXT_PUBLIC_URL — used in production + * + * @returns {string|null} + */ +function resolveAppUrl() { + if (process.env.NODE_ENV === 'development' && process.env.NEXT_PUBLIC_URL_DEV) { + return process.env.NEXT_PUBLIC_URL_DEV; + } + return process.env.NEXT_PUBLIC_URL || null; +} + /** * Verify that state-mutating requests (POST/PUT/PATCH/DELETE) originate from * the expected application origin, blocking cross-site request forgery. @@ -37,9 +53,8 @@ const COOKIE_NAME = getSessionCookieName(); * The check is skipped for safe HTTP methods (GET, HEAD, OPTIONS) which must * not cause side-effects per RFC 7231. * - * If ZEN_APP_URL is not configured the check is bypassed with a warning — this - * guards against locking out misconfigured deployments while making the missing - * configuration visible in logs. + * The expected origin is resolved from environment variables (see resolveAppUrl). + * If no URL is configured the check denies the request and logs an error. * * @param {Request} request * @returns {boolean} true if the request passes CSRF validation @@ -48,9 +63,9 @@ function passesCsrfCheck(request) { const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS']); if (safeMethods.has(request.method)) return true; - const appUrl = process.env.ZEN_APP_URL; + const appUrl = resolveAppUrl(); if (!appUrl) { - console.error('[ZEN CSRF] ZEN_APP_URL is not set — CSRF origin check is ENFORCED DENY. Configure this variable immediately.'); + console.error('[ZEN CSRF] No app URL configured (NEXT_PUBLIC_URL_DEV / NEXT_PUBLIC_URL) — CSRF check denied.'); return false; } @@ -58,7 +73,7 @@ function passesCsrfCheck(request) { try { expectedOrigin = new URL(appUrl).origin; } catch { - console.error('[ZEN CSRF] ZEN_APP_URL is not a valid URL:', appUrl); + console.error('[ZEN CSRF] Configured app URL is not valid:', appUrl); return false; }