diff --git a/server/src/app.ts b/server/src/app.ts index d21c0d3c..cc83b645 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -53,7 +53,7 @@ export function createApp(): express.Application { const app = express(); // Trust first proxy (nginx/Docker) for correct req.ip - if (process.env.NODE_ENV === 'production' || process.env.TRUST_PROXY) { + if (process.env.NODE_ENV?.toLowerCase() === 'production' || process.env.TRUST_PROXY) { app.set('trust proxy', Number.parseInt(process.env.TRUST_PROXY) || 1); } @@ -67,13 +67,13 @@ export function createApp(): express.Application { if (!origin || allowedOrigins.includes(origin)) callback(null, true); else callback(new Error('Not allowed by CORS')); }; - } else if (process.env.NODE_ENV === 'production') { + } else if (process.env.NODE_ENV?.toLowerCase() === 'production') { corsOrigin = false; } else { corsOrigin = true; } - const shouldForceHttps = process.env.FORCE_HTTPS === 'true'; + const shouldForceHttps = process.env.FORCE_HTTPS?.toLowerCase() === 'true'; // HSTS is worth enabling any time we're serving production traffic, // not only when FORCE_HTTPS is set. Self-hosters behind Traefik / // Caddy / Cloudflare Tunnel typically leave FORCE_HTTPS unset (the diff --git a/server/src/config.ts b/server/src/config.ts index 2941a87f..c9255cc9 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -105,7 +105,7 @@ export const ENCRYPTION_KEY = _encryptionKey; // Must stay in sync with client/src/i18n/supportedLanguages.ts (canonical source). // Kept duplicated here because server and client are separate npm packages. const SUPPORTED_LANG_CODES = ['de', 'en', 'es', 'fr', 'hu', 'nl', 'br', 'cs', 'pl', 'ru', 'zh', 'zh-TW', 'it', 'ar']; -const rawDefaultLang = process.env.DEFAULT_LANGUAGE || 'en'; +const rawDefaultLang = process.env.DEFAULT_LANGUAGE?.toLowerCase() || 'en'; if (!SUPPORTED_LANG_CODES.includes(rawDefaultLang)) { console.warn(`DEFAULT_LANGUAGE="${rawDefaultLang}" is not supported. Falling back to "en". Supported: ${SUPPORTED_LANG_CODES.join(', ')}`); } diff --git a/server/src/db/database.ts b/server/src/db/database.ts index 2578608b..e7057fd4 100644 --- a/server/src/db/database.ts +++ b/server/src/db/database.ts @@ -47,7 +47,7 @@ const db = new Proxy({} as Database.Database, { }, }); -if (process.env.DEMO_MODE === 'true') { +if (process.env.DEMO_MODE?.toLowerCase() === 'true') { try { const { seedDemoData } = require('../demo/demo-seed'); seedDemoData(_db); diff --git a/server/src/db/seeds.ts b/server/src/db/seeds.ts index d6e01d54..299e3f5a 100644 --- a/server/src/db/seeds.ts +++ b/server/src/db/seeds.ts @@ -6,7 +6,7 @@ import crypto from 'crypto'; // are only relevant after the first user exists; at that point seeds have already // finished and skip via the userCount > 0 guard above. function isOidcOnlyConfigured(): boolean { - if (process.env.OIDC_ONLY !== 'true') return false; + if (process.env.OIDC_ONLY?.toLowerCase() !== 'true') return false; return !!(process.env.OIDC_ISSUER && process.env.OIDC_CLIENT_ID); } diff --git a/server/src/index.ts b/server/src/index.ts index 3f1bf8dd..0e699f37 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -29,8 +29,9 @@ const server = app.listen(PORT, () => { const banner = [ '──────────────────────────────────────', ' TREK API started', + ` Version ${process.env.APP_VERSION}`, ` Port: ${PORT}`, - ` Environment: ${process.env.NODE_ENV || 'development'}`, + ` Environment: ${process.env.NODE_ENV?.toLowerCase() || 'development'}`, ` Timezone: ${tz}`, ` Origins: ${origins}`, ` Log level: ${LOG_LVL}`, @@ -40,8 +41,8 @@ const server = app.listen(PORT, () => { '──────────────────────────────────────', ]; banner.forEach(l => console.log(l)); - if (process.env.DEMO_MODE === 'true') sLogInfo('Demo mode: ENABLED'); - if (process.env.DEMO_MODE === 'true' && process.env.NODE_ENV === 'production') { + if (process.env.DEMO_MODE?.toLowerCase() === 'true') sLogInfo('Demo mode: ENABLED'); + if (process.env.DEMO_MODE?.toLowerCase() === 'true' && process.env.NODE_ENV?.toLowerCase() === 'production') { sLogWarn('SECURITY WARNING: DEMO_MODE is enabled in production!'); } scheduler.start(); diff --git a/server/src/middleware/auth.ts b/server/src/middleware/auth.ts index 9f254403..18be7236 100644 --- a/server/src/middleware/auth.ts +++ b/server/src/middleware/auth.ts @@ -105,7 +105,7 @@ const adminOnly = (req: Request, res: Response, next: NextFunction): void => { const demoUploadBlock = (req: Request, res: Response, next: NextFunction): void => { const authReq = req as AuthRequest; - if (process.env.DEMO_MODE === 'true' && isDemoEmail(authReq.user?.email)) { + if (process.env.DEMO_MODE?.toLowerCase() === 'true' && isDemoEmail(authReq.user?.email)) { res.status(403).json({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }); return; } diff --git a/server/src/middleware/mfaPolicy.ts b/server/src/middleware/mfaPolicy.ts index b253acc0..70b00896 100644 --- a/server/src/middleware/mfaPolicy.ts +++ b/server/src/middleware/mfaPolicy.ts @@ -68,7 +68,7 @@ export function enforceGlobalMfaPolicy(req: Request, res: Response, next: NextFu return; } - if (process.env.DEMO_MODE === 'true' && verified.email && DEMO_EMAILS.has(verified.email)) { + if (process.env.DEMO_MODE?.toLowerCase() === 'true' && verified.email && DEMO_EMAILS.has(verified.email)) { next(); return; } diff --git a/server/src/routes/admin.ts b/server/src/routes/admin.ts index 7cd32712..16a5b40b 100644 --- a/server/src/routes/admin.ts +++ b/server/src/routes/admin.ts @@ -449,7 +449,7 @@ router.put('/default-user-settings', (req: Request, res: Response) => { }); // ── Dev-only: test notification endpoints ────────────────────────────────────── -if (process.env.NODE_ENV === 'development') { +if (process.env.NODE_ENV?.toLowerCase() === 'development') { const { send } = require('../services/notificationService'); router.post('/dev/test-notification', async (req: Request, res: Response) => { diff --git a/server/src/routes/backup.ts b/server/src/routes/backup.ts index 2d9ce088..58e5995a 100644 --- a/server/src/routes/backup.ts +++ b/server/src/routes/backup.ts @@ -168,7 +168,7 @@ router.put('/auto-settings', (req: Request, res: Response) => { const msg = err instanceof Error ? err.message : String(err); res.status(500).json({ error: 'Could not save auto-backup settings', - detail: process.env.NODE_ENV !== 'production' ? msg : undefined, + detail: process.env.NODE_ENV?.toLowerCase() !== 'production' ? msg : undefined, }); } }); diff --git a/server/src/routes/oidc.ts b/server/src/routes/oidc.ts index 4a86a340..7aefe593 100644 --- a/server/src/routes/oidc.ts +++ b/server/src/routes/oidc.ts @@ -30,7 +30,7 @@ router.get('/login', async (req: Request, res: Response) => { const config = getOidcConfig(); if (!config) return res.status(400).json({ error: 'OIDC not configured' }); - if (config.issuer && !config.issuer.startsWith('https://') && process.env.NODE_ENV === 'production') { + if (config.issuer && !config.issuer.startsWith('https://') && process.env.NODE_ENV?.toLowerCase() === 'production') { return res.status(400).json({ error: 'OIDC issuer must use HTTPS in production' }); } @@ -85,7 +85,7 @@ router.get('/callback', async (req: Request, res: Response) => { const config = getOidcConfig(); if (!config) return res.redirect(frontendUrl('/login?oidc_error=not_configured')); - if (config.issuer && !config.issuer.startsWith('https://') && process.env.NODE_ENV === 'production') { + if (config.issuer && !config.issuer.startsWith('https://') && process.env.NODE_ENV?.toLowerCase() === 'production') { return res.redirect(frontendUrl('/login?oidc_error=issuer_not_https')); } diff --git a/server/src/scheduler.ts b/server/src/scheduler.ts index 87979ecc..13177b7f 100644 --- a/server/src/scheduler.ts +++ b/server/src/scheduler.ts @@ -139,7 +139,7 @@ let demoTask: ScheduledTask | null = null; function startDemoReset(): void { if (demoTask) { demoTask.stop(); demoTask = null; } - if (process.env.DEMO_MODE !== 'true') return; + if (process.env.DEMO_MODE?.toLowerCase() !== 'true') return; demoTask = cron.schedule('0 * * * *', () => { try { diff --git a/server/src/services/adminService.ts b/server/src/services/adminService.ts index f17dd8ba..f0fc5420 100644 --- a/server/src/services/adminService.ts +++ b/server/src/services/adminService.ts @@ -288,7 +288,7 @@ export function updateOidcSettings(data: { // ── Demo Baseline ────────────────────────────────────────────────────────── export function saveDemoBaseline(): { error?: string; status?: number; message?: string } { - if (process.env.DEMO_MODE !== 'true') { + if (process.env.DEMO_MODE?.toLowerCase() !== 'true') { return { error: 'Not found', status: 404 }; } try { diff --git a/server/src/services/authService.ts b/server/src/services/authService.ts index 0bbcbfc7..ba949481 100644 --- a/server/src/services/authService.ts +++ b/server/src/services/authService.ts @@ -131,7 +131,7 @@ export function resolveAuthToggles(): { oidc_login: get('oidc_login') !== 'false', oidc_registration: get('oidc_registration') !== 'false', }; - if (process.env.OIDC_ONLY === 'true') { + if (process.env.OIDC_ONLY?.toLowerCase() === 'true') { result.password_login = false; result.password_registration = false; } @@ -139,7 +139,7 @@ export function resolveAuthToggles(): { } // Legacy fallback - const oidcOnlyEnabled = process.env.OIDC_ONLY === 'true' || get('oidc_only') === 'true'; + const oidcOnlyEnabled = process.env.OIDC_ONLY?.toLowerCase() === 'true' || get('oidc_only') === 'true'; const oidcConfigured = !!( (process.env.OIDC_ISSUER || get('oidc_issuer')) && (process.env.OIDC_CLIENT_ID || get('oidc_client_id')) @@ -253,7 +253,7 @@ export function getPendingMfaSecret(userId: number): string | null { export function getAppConfig(authenticatedUser: { id: number } | null) { const userCount = (db.prepare('SELECT COUNT(*) as count FROM users').get() as { count: number }).count; - const isDemo = process.env.DEMO_MODE === 'true'; + const isDemo = process.env.DEMO_MODE?.toLowerCase() === 'true'; const toggles = resolveAuthToggles(); const version: string = process.env.APP_VERSION ?? require('../../package.json').version; const hasGoogleKey = !!db.prepare("SELECT maps_api_key FROM users WHERE role = 'admin' AND maps_api_key IS NOT NULL AND maps_api_key != '' LIMIT 1").get(); diff --git a/server/src/services/cookie.ts b/server/src/services/cookie.ts index d1e88d2a..c97e187c 100644 --- a/server/src/services/cookie.ts +++ b/server/src/services/cookie.ts @@ -18,10 +18,10 @@ const COOKIE_NAME = 'trek_session'; * remains the explicit escape hatch for plain-HTTP LAN testing. */ export function cookieOptions(clear = false, req?: Request) { - if (process.env.COOKIE_SECURE === 'false') { + if (process.env.COOKIE_SECURE?.toLowerCase() === 'false') { return buildOptions(clear, false); } - const envSecure = process.env.NODE_ENV === 'production' || process.env.FORCE_HTTPS === 'true'; + const envSecure = process.env.NODE_ENV?.toLowerCase() === 'production' || process.env.FORCE_HTTPS?.toLowerCase() === 'true'; const requestSecure = req?.secure === true; return buildOptions(clear, envSecure || requestSecure); } diff --git a/server/src/services/notificationService.ts b/server/src/services/notificationService.ts index f14e48d8..67f02f2a 100644 --- a/server/src/services/notificationService.ts +++ b/server/src/services/notificationService.ts @@ -170,7 +170,7 @@ export async function send(payload: NotificationPayload): Promise { const configEntry = EVENT_NOTIFICATION_CONFIG[event]; if (!configEntry) { logDebug(`notificationService.send: unknown event type "${event}", using fallback`); - if (process.env.NODE_ENV === 'development' && actorId != null) { + if (process.env.NODE_ENV?.toLowerCase() === 'development' && actorId != null) { const devSender = (db.prepare('SELECT username, avatar FROM users WHERE id = ?').get(actorId) as { username: string; avatar: string | null } | undefined) ?? null; createNotificationForRecipient({ type: 'simple', diff --git a/server/src/utils/ssrfGuard.ts b/server/src/utils/ssrfGuard.ts index d5f2aa3f..19ed98dc 100644 --- a/server/src/utils/ssrfGuard.ts +++ b/server/src/utils/ssrfGuard.ts @@ -1,7 +1,7 @@ import dns from 'node:dns/promises'; import { Agent } from 'undici'; -const ALLOW_INTERNAL_NETWORK = process.env.ALLOW_INTERNAL_NETWORK === 'true'; +const ALLOW_INTERNAL_NETWORK = process.env.ALLOW_INTERNAL_NETWORK?.toLowerCase() === 'true'; export interface SsrfResult { allowed: boolean;