revert(customer-portal): make the global flag UI-only, drop the kill-switch middleware
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Customer portal feature-flag gate.
|
||||
*
|
||||
* Blocks every /api/customer/* and /api/admin/customers/* endpoint
|
||||
* when the `customerPortal` flag is off. Returns 410 Gone so the
|
||||
* frontend can distinguish "feature has been disabled" from "you
|
||||
* don't have access" (which would be 403) — useful for the customer
|
||||
* dashboard's auto-redirect on a soft-kill scenario.
|
||||
*
|
||||
* Reads the flag via customerAccountsService.isCustomerPortalEnabled
|
||||
* (which itself reads from the maintainer's feature_flags table),
|
||||
* so a single source of truth.
|
||||
*/
|
||||
|
||||
const customerAccountsService = require('../services/customerAccountsService');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
async function isEnabled() {
|
||||
try {
|
||||
return await customerAccountsService.isCustomerPortalEnabled();
|
||||
} catch (err) {
|
||||
// Defensive: if the lookup throws (DB unavailable, table missing
|
||||
// mid-migration), fail closed so an enabled-by-default fallback
|
||||
// can't accidentally expose customer surfaces during boot.
|
||||
logger.warn('requireCustomerPortal: feature flag lookup failed, treating as off', {
|
||||
error: err?.message,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer-facing endpoints. Returns 410 with a code the frontend
|
||||
* can interpret to clear stale session storage + redirect to
|
||||
* /admin/login.
|
||||
*/
|
||||
async function requireCustomerPortalEnabled(req, res, next) {
|
||||
if (await isEnabled()) return next();
|
||||
return res.status(410).json({
|
||||
error: 'Customer portal is disabled',
|
||||
code: 'CUSTOMER_PORTAL_DISABLED',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin-facing /api/admin/customers/* endpoints. Same gate, same
|
||||
* status code — keeps the contract consistent across both halves of
|
||||
* the customer-portal surface. The sidebar UI already hides the
|
||||
* entry, but a stale tab or direct API call must also be blocked.
|
||||
*/
|
||||
async function requireCustomerPortalEnabledAdmin(req, res, next) {
|
||||
if (await isEnabled()) return next();
|
||||
return res.status(410).json({
|
||||
error: 'Customer portal is disabled',
|
||||
code: 'CUSTOMER_PORTAL_DISABLED',
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requireCustomerPortalEnabled,
|
||||
requireCustomerPortalEnabledAdmin,
|
||||
};
|
||||
@@ -48,15 +48,20 @@ const TOKEN_TTL_SECONDS = 24 * 60 * 60; // mirrors admin tokens
|
||||
|
||||
// ---- login -------------------------------------------------------------
|
||||
|
||||
// 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).
|
||||
// The customerPortal feature flag deliberately does NOT gate this route.
|
||||
// Flipping the master toggle off in Settings → Features hides the
|
||||
// admin-side Clients section (sidebar entry, /admin/clients pages) but
|
||||
// must not revoke access for customers who already accepted an
|
||||
// invitation — that would mean a stray click in the Features tab
|
||||
// locks every paying customer out at once.
|
||||
//
|
||||
// To revoke access at the customer level, use the per-record tools:
|
||||
// - "Deactivate" on the customer detail page → sets
|
||||
// customer_accounts.is_active = false AND bumps password_changed_at,
|
||||
// which customerAuth rejects below + on every protected route.
|
||||
// - "Manage galleries" dialog → removes event_customer_assignments
|
||||
// rows, which verifyGalleryAccess re-checks on customer-minted
|
||||
// gallery JWTs (instant per-gallery revocation).
|
||||
router.post('/login', [
|
||||
body('email').isEmail().normalizeEmail().withMessage('Valid email is required'),
|
||||
body('password').isString().notEmpty(),
|
||||
|
||||
Reference in New Issue
Block a user