Compare commits

...
Author SHA1 Message Date
joshua fbb84b037c 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.
2026-08-26 14:36:51 +02:00
joshua 2dfe8d32c4 Add PM2 ecosystem config for production deployment 2026-08-26 11:59:08 +02:00
joshua 305499ee91 Merge branch 'fix/unhandled-rejection-no-crash' into main
Stops the server from hard-crashing on unhandled promise rejections, and bumps version to 1.9.3.
2026-08-26 09:07:06 +02:00
joshua c2112bf707 Bump version to 1.9.3 2026-08-26 09:06:52 +02:00
joshua e63fb2cc27 Stop crashing the server on unhandled promise rejections
A single missed .catch() anywhere in the notification code (email/WhatsApp
sending) previously took down the whole process via process.exit(1). Log
the error instead and keep running.
2026-08-26 09:03:15 +02:00
8 changed files with 58 additions and 7 deletions
+9
View File
@@ -7,8 +7,17 @@ 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
- The server used to hard-crash (`process.exit(1)`) on any unhandled promise rejection, so a single missed error handler anywhere in the app's fire-and-forget notification code (email/WhatsApp sending) could take the whole server down. It now logs the error and keeps running.
- Accounts created on someone's behalf (at-the-door walk-in registration, or manual registration from the Admin/Supervisor dashboard) now get their activation link (email or WhatsApp, whichever they have) sent immediately when the account is created, instead of only on their first failed login attempt — matching what the Terms of Use already promised.
- Manual registration with a real email address used to create the account already active with a fixed, undisclosed password (`Hope123`) — the visitor had no way to know it. That account is now created inactive and gets the same immediate activation link, so the visitor sets their own password — unless a password was supplied directly (see below), in which case it's activated immediately with no link needed.
- The self-service kiosk's "Create an account" password field never actually worked — the account was always created with a different password behind the scenes, so visitors who set one couldn't log in with it. Manual registration now honours a caller-supplied password and activates the account immediately instead of discarding it.
+7
View File
@@ -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
+1
View File
@@ -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) |
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "event-management-backend",
"version": "1.9.2",
"version": "1.9.4",
"description": "Event Management System Backend",
"main": "src/index.js",
"scripts": {
+21 -4
View File
@@ -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(',')
@@ -1265,11 +1281,12 @@ app.listen(PORT, () => {
}
});
// Handle unhandled promise rejections
// Log unhandled promise rejections without killing the server, since a single
// missed .catch() on fire-and-forget notification code (email/WhatsApp sends)
// would otherwise take the whole app down.
process.on('unhandledRejection', (err) => {
console.log('UNHANDLED REJECTION! Shutting down...');
console.log(err.name, err.message);
process.exit(1);
console.error('UNHANDLED REJECTION!', err?.name, err?.message);
console.error(err?.stack || err);
});
module.exports = { app, prisma };
+17
View File
@@ -0,0 +1,17 @@
module.exports = {
apps: [
{
name: 'hope-events-backend',
cwd: __dirname + '/backend',
script: 'src/index.js',
env: { NODE_ENV: 'production' },
},
{
name: 'hope-events-frontend',
cwd: __dirname + '/frontend',
script: 'npm',
args: 'start -- -p 3001',
env: { NODE_ENV: 'production' },
},
],
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hope-events-frontend",
"version": "1.9.2",
"version": "1.9.4",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hope-events",
"version": "1.9.2",
"version": "1.9.4",
"main": "index.js",
"scripts": {
"dev:backend": "cd backend && npm run dev",