From fbb84b037cd774606c78420af89c0d1a8fc2e14d Mon Sep 17 00:00:00 2001 From: joshua Date: Wed, 26 Aug 2026 14:36:51 +0200 Subject: [PATCH] Add TRUST_PROXY env var for reverse-proxy deployments Fixes express-rate-limit's ERR_ERL_UNEXPECTED_X_FORWARDED_FOR warning and incorrect IP keying when nginx runs on a separate server in front of the app. --- CHANGELOG.md | 6 ++++++ backend/.env.example | 7 +++++++ backend/README.md | 1 + backend/package.json | 2 +- backend/src/index.js | 16 ++++++++++++++++ frontend/package.json | 2 +- package.json | 2 +- 7 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 052f294..f39fff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [1.9.4] - 2026-08-26 + +### Added + +- New `TRUST_PROXY` backend env var — set it when the app runs behind a reverse proxy (e.g. nginx on a separate server) so rate limiting reads the real client IP from `X-Forwarded-For` instead of the proxy's. Accepts a hop count, `true`/`false`, or trusted proxy IP(s)/CIDR(s). + ## [1.9.3] - 2026-08-26 ### Fixed diff --git a/backend/.env.example b/backend/.env.example index fec5d1e..908bff8 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -8,6 +8,13 @@ JWT_SECRET=your_jwt_secret_here_minimum_32_characters PORT=5000 NODE_ENV=development +# Set this if the app runs behind a reverse proxy (e.g. nginx on a separate +# server) so rate limiting reads the real client IP instead of the proxy's. +# Accepts a hop count ("1"), "true"/"false", or comma-separated IP(s)/CIDR(s) +# of your trusted proxy (e.g. "10.0.0.5" or "10.0.0.0/8"). Leave unset if the +# app is not behind a proxy. +# TRUST_PROXY=1 + # ─── CORS ───────────────────────────────────────────────────────────────────── # Comma-separated list of allowed frontend origins FRONTEND_URL=http://localhost:3000 diff --git a/backend/README.md b/backend/README.md index ab865aa..23ec7e6 100644 --- a/backend/README.md +++ b/backend/README.md @@ -84,6 +84,7 @@ Create `backend/.env` from `.env.example`. The only variables you must set are: | `APP_BASE_URL` | — | Public frontend URL — used in email links (fallback if not set via admin panel) | | `BACKEND_URL` | — | Public backend URL — used to serve ticket PDFs over WhatsApp | | `PORT` | — | Port to listen on (default `3000`) | +| `TRUST_PROXY` | — | Set when running behind a reverse proxy (e.g. nginx on a separate server), so `req.ip`/`X-Forwarded-For` are read correctly by rate limiting. Accepts a hop count (`1`), `true`/`false`, or comma-separated trusted proxy IP(s)/CIDR(s). | | `NODE_ENV` | — | `production` or `development` | | `WAWP_ACCESS_TOKEN` | — | WAWP fallback token (preferred: set via Admin → Site Settings) | | `WAWP_INSTANCE_ID` | — | WAWP fallback instance ID (preferred: set via Admin → Site Settings) | diff --git a/backend/package.json b/backend/package.json index 15bb0b4..97e5af5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "event-management-backend", - "version": "1.9.3", + "version": "1.9.4", "description": "Event Management System Backend", "main": "src/index.js", "scripts": { diff --git a/backend/src/index.js b/backend/src/index.js index d63ad7d..4ebf392 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -18,6 +18,22 @@ const prisma = new PrismaClient(); const app = express(); const PORT = process.env.PORT || 3000; +// Trust proxy — required when running behind a reverse proxy (e.g. nginx on a +// separate server) so req.ip / X-Forwarded-For are read correctly by +// express-rate-limit and friends. Accepts a hop count ("1"), "true"/"false", +// or a comma-separated list of trusted proxy IPs/CIDRs. +if (process.env.TRUST_PROXY) { + const raw = process.env.TRUST_PROXY.trim(); + let trustProxyValue; + if (raw === 'true') trustProxyValue = true; + else if (raw === 'false') trustProxyValue = false; + else if (/^\d+$/.test(raw)) trustProxyValue = parseInt(raw, 10); + else if (raw.includes(',')) trustProxyValue = raw.split(',').map((s) => s.trim()); + else trustProxyValue = raw; + app.set('trust proxy', trustProxyValue); + console.log(`[startup] trust proxy set to: ${JSON.stringify(trustProxyValue)}`); +} + // CORS — allow only the configured frontend origin const allowedOrigins = (process.env.FRONTEND_URL || 'http://localhost:3000') .split(',') diff --git a/frontend/package.json b/frontend/package.json index 0045b19..b349eca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "hope-events-frontend", - "version": "1.9.3", + "version": "1.9.4", "private": true, "scripts": { "dev": "next dev --turbopack", diff --git a/package.json b/package.json index 6c0fa0a..2b96564 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hope-events", - "version": "1.9.3", + "version": "1.9.4", "main": "index.js", "scripts": { "dev:backend": "cd backend && npm run dev",