From fdba8c0d1fc7809856740bca0140414a30c5cde6 Mon Sep 17 00:00:00 2001 From: ort Date: Sat, 15 Aug 2026 16:18:07 -0400 Subject: [PATCH] Don't let a malformed VAPID_SUBJECT crash the whole app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setVapidDetails() throws synchronously on an invalid subject (e.g. missing the mailto:/https: prefix — an easy mistake). It ran unguarded at module load, so a bad value took down the entire server, not just push. Wrapped in try/catch; push now degrades the same way as when it's unconfigured, and /api/push/vapid-public-key reflects actual readiness (post-validation) instead of just whether the env vars were present. --- src/lib/push.js | 16 +++++++++++++--- src/routes/push.js | 3 ++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/lib/push.js b/src/lib/push.js index e9abc68..513f254 100644 --- a/src/lib/push.js +++ b/src/lib/push.js @@ -2,8 +2,18 @@ const webpush = require('web-push'); const db = require('../db'); const config = require('../config'); +// A malformed VAPID_SUBJECT (missing "mailto:"/"https:", etc.) makes +// setVapidDetails throw synchronously — without this try/catch that would +// crash the entire app at boot, not just disable push. Bad config should +// degrade the same way as absent config: push off, everything else fine. +let pushReady = false; if (config.pushConfigured) { - webpush.setVapidDetails(config.vapidSubject, config.vapidPublicKey, config.vapidPrivateKey); + try { + webpush.setVapidDetails(config.vapidSubject, config.vapidPublicKey, config.vapidPrivateKey); + pushReady = true; + } catch (err) { + console.error('[push] Invalid VAPID configuration — push notifications disabled:', err.message); + } } const listSubscriptionsForHouseholdStmt = db.prepare(` @@ -15,7 +25,7 @@ const listSubscriptionsForHouseholdStmt = db.prepare(` const deleteSubscriptionStmt = db.prepare('DELETE FROM push_subscriptions WHERE id = ?'); async function notifyHouseholdParents(householdId, payload) { - if (!config.pushConfigured) return; + if (!pushReady) return; const subs = listSubscriptionsForHouseholdStmt.all(householdId); if (subs.length === 0) return; @@ -44,4 +54,4 @@ async function notifyHouseholdParents(householdId, payload) { return results; } -module.exports = { notifyHouseholdParents }; +module.exports = { notifyHouseholdParents, isPushReady: () => pushReady }; diff --git a/src/routes/push.js b/src/routes/push.js index aa3eca2..49e05af 100644 --- a/src/routes/push.js +++ b/src/routes/push.js @@ -2,6 +2,7 @@ const express = require('express'); const db = require('../db'); const config = require('../config'); const requireAuth = require('../middleware/requireAuth'); +const { isPushReady } = require('../lib/push'); const router = express.Router(); router.use(requireAuth); @@ -21,7 +22,7 @@ const deleteOwnSubscriptionStmt = db.prepare( ); router.get('/vapid-public-key', (req, res) => { - if (!config.pushConfigured) return res.json({ enabled: false }); + if (!isPushReady()) return res.json({ enabled: false }); res.json({ enabled: true, publicKey: config.vapidPublicKey }); });