From 3f387b616ee0e9d1c61cade5b6e8ad9e64223593 Mon Sep 17 00:00:00 2001 From: Ulas Date: Tue, 14 Apr 2026 09:04:06 +0200 Subject: [PATCH] fix: default TRUST_PROXY to 1 for Docker+reverse-proxy setups (#46) With the previous default of 'loopback', Express ignored X-Forwarded-Proto headers from Caddy/nginx when running in Docker (bridge IP, not loopback). This caused req.secure=false, which made express-session silently drop the session cookie on login - resulting in a 401 on every subsequent request. Changing the default to 1 (trust one proxy hop) fixes this for all standard Docker+reverse-proxy deployments without requiring manual configuration. --- CHANGELOG.md | 7 +++++++ docker-compose.yml | 5 ++++- docs/docker-compose.portainer.yml | 5 ++++- package.json | 2 +- server/index.js | 9 ++++++--- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59b726b..0813b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.18.2] - 2026-04-14 + +### Fixed +- Login failure behind Caddy/nginx reverse proxy in Docker: default `TRUST_PROXY` changed from `'loopback'` to `1` (trust one proxy hop). With `'loopback'`, Express ignored `X-Forwarded-Proto: https` from Caddy (which runs on a Docker bridge IP, not loopback), causing `req.secure = false` and express-session to silently drop the session cookie. The new default of `1` correctly handles any single-proxy setup without requiring manual configuration. +- `docker-compose.yml`: added inline comments explaining reverse proxy vs. direct-access configuration +- `docs/docker-compose.portainer.yml`: added explicit `TRUST_PROXY` variable with default `1` + ## [0.18.1] - 2026-04-14 ### Added diff --git a/docker-compose.yml b/docker-compose.yml index 7e4ca7f..cd71eb6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,10 @@ services: environment: - NODE_ENV=production - DB_PATH=/data/oikos.db - # Set to false when not using HTTPS/reverse proxy (direct HTTP access) + # Reverse proxy setup (Caddy, nginx, Traefik): + # - Remove SESSION_SECURE=false (default is true) + # - TRUST_PROXY is automatically set to 1 (trust one proxy hop) + # Direct HTTP access (no reverse proxy): - SESSION_SECURE=false healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"] diff --git a/docs/docker-compose.portainer.yml b/docs/docker-compose.portainer.yml index c8a30ab..dbb8c9a 100644 --- a/docs/docker-compose.portainer.yml +++ b/docs/docker-compose.portainer.yml @@ -31,8 +31,11 @@ services: - DB_PATH=/data/oikos.db - SESSION_SECRET=${SESSION_SECRET:?Set SESSION_SECRET in .env} - DB_ENCRYPTION_KEY=${DB_ENCRYPTION_KEY:?Set DB_ENCRYPTION_KEY in .env} - # Set to true when behind a reverse proxy with HTTPS + # Set to true when behind a reverse proxy with HTTPS (Caddy, nginx, Traefik) - SESSION_SECURE=${SESSION_SECURE:-false} + # Trust proxy hops (default: 1 for Docker+reverse-proxy setups) + # Set to 'loopback' if running without a reverse proxy + - TRUST_PROXY=${TRUST_PROXY:-1} # Weather (optional) - OPENWEATHER_API_KEY=${OPENWEATHER_API_KEY:-} - OPENWEATHER_CITY=${OPENWEATHER_CITY:-Berlin} diff --git a/package.json b/package.json index 772278d..a8eeb2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.18.1", + "version": "0.18.2", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/server/index.js b/server/index.js index 7147517..5f8e0c9 100644 --- a/server/index.js +++ b/server/index.js @@ -65,9 +65,12 @@ app.use(helmet({ } : false, })); -// Trust Proxy: nur aktivieren wenn ein Reverse Proxy vorgeschaltet ist (TRUST_PROXY env var). -// Default 'loopback' akzeptiert nur X-Forwarded-For von localhost - verhindert IP-Spoofing. -app.set('trust proxy', process.env.TRUST_PROXY || 'loopback'); +// Trust Proxy: Default 1 = ersten Proxy-Hop vertrauen (korrekt für Caddy/nginx in Docker). +// Wird auf 'loopback' gesetzt wenn der Server direkt ohne Reverse-Proxy betrieben wird. +// Hintergrund: Bei Docker + Caddy/nginx kommt der Request von einer Bridge-IP (z.B. 172.x.x.x), +// nicht von loopback. Mit 'loopback' ignoriert Express das X-Forwarded-Proto-Header von Caddy, +// req.secure bleibt false, und express-session setzt keinen Session-Cookie (Login schlägt fehl). +app.set('trust proxy', process.env.TRUST_PROXY !== undefined ? process.env.TRUST_PROXY : 1); // -------------------------------------------------------- // Request-Parsing