diff --git a/backend/server.js b/backend/server.js index 14c4cac9..5048b4a1 100644 --- a/backend/server.js +++ b/backend/server.js @@ -568,13 +568,20 @@ app.use('/api/admin/photo-export', require('./src/routes/adminPhotoExport')); app.use('/api/admin/css-templates', require('./src/routes/adminCssTemplates')); app.use('/api/admin/events', require('./src/routes/adminEventRename')); app.use('/api/admin/users', require('./src/routes/adminUsers')); -// Customer portal (#354). When the `customerPortal` feature flag is -// OFF, both the admin-facing /api/admin/customers/* surface AND the -// customer-facing /api/customer/* surface return 410 Gone — turning -// the toggle off in Settings → Features cleanly kills the feature -// everywhere, not just in the UI. The frontend RequireFeature guard -// + AdminSidebar visibility still apply for navigation, but a stale -// tab or third-party API client can't bypass the gate. +// Customer portal (#354). The customerPortal feature flag is enforced +// in TWO places: +// 1. Frontend: RequireFeature guards + AdminSidebar visibility +// (handles navigation cleanly when an admin is using the app). +// 2. Backend: the requireCustomerPortalEnabled middleware below. +// Belt-and-braces — a stale tab, a saved bookmark, or any +// third-party API client trying to hit /api/customer/* or +// /api/admin/customers/* gets a 410 Gone the moment the toggle +// is flipped off. Includes /api/customer/auth/login: flag off +// = nobody can log in until the admin re-enables, including +// already-issued customers (their sessions still have valid +// JWTs but every API call returns 410 → frontend boots them +// out). PR #458 deliberate departure from the prior design +// that left login alive when the rest of the surface was off. const { requireCustomerPortalEnabled, requireCustomerPortalEnabledAdmin, diff --git a/backend/src/routes/customerAuth.js b/backend/src/routes/customerAuth.js index 2f459177..87eb7b4b 100644 --- a/backend/src/routes/customerAuth.js +++ b/backend/src/routes/customerAuth.js @@ -48,12 +48,15 @@ const TOKEN_TTL_SECONDS = 24 * 60 * 60; // mirrors admin tokens // ---- login ------------------------------------------------------------- -// The customerPortal feature flag deliberately does NOT gate this route. -// Flipping the master toggle off in Settings → Features hides the admin -// UI surface (sidebar entry, /admin/customers page) but does not revoke -// access for customers who already accepted an invitation. To lock out -// existing customers, deactivate their accounts individually -// (customer_accounts.is_active = false) — which IS enforced below. +// Flag-gate note: this route IS now gated by the customerPortal feature +// flag via the requireCustomerPortalEnabled middleware mounted in +// server.js (`app.use('/api/customer/auth', requireCustomerPortalEnabled, …)`). +// When the admin flips the toggle off in Settings → Features, every +// customer-side endpoint — including login — returns 410. The previous +// design left login reachable while the rest of the surface was gated; +// that was confusing and asymmetric. Single source of truth wins. +// To lock out a specific customer without disabling the feature for +// everyone, deactivate the account (customer_accounts.is_active = false). router.post('/login', [ body('email').isEmail().normalizeEmail().withMessage('Valid email is required'), body('password').isString().notEmpty(),