From 49b36a03527c5498d5da8e37e85ea559bbb691d3 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Mon, 11 May 2026 09:59:47 +0200 Subject: [PATCH] =?UTF-8?q?chore(migrations):=20renumber=20090=20=E2=86=92?= =?UTF-8?q?=20096=20+=20small=20notes=20from=20#403=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Post-merge cleanups after #403 (customer portal): - Renumber 090_backfill_photo_dimensions_v2.js → 096 to follow #403's 090_add_customer_accounts ... 095_add_customer_portal_flag chain. - customerAccountsService.js: TODO note on must_change_password documenting that the column is decorative until an admin pre-loaded-password flow ships (mirrors what adminAuth does for must_change_password today). - customerAuth.js: doc-comment on the /login route explaining why the customerPortal feature flag deliberately doesn't gate it (toggle off hides UI, doesn't revoke existing-customer access; deactivate individual accounts to lock out). - 095_add_customer_portal_flag.js: header comment said "Migration 094" (copy-paste from 094) — now matches the filename. --- backend/migrations/core/095_add_customer_portal_flag.js | 2 +- ...dimensions_v2.js => 096_backfill_photo_dimensions_v2.js} | 0 backend/src/routes/customerAuth.js | 6 ++++++ backend/src/services/customerAccountsService.js | 6 ++++++ 4 files changed, 13 insertions(+), 1 deletion(-) rename backend/migrations/core/{090_backfill_photo_dimensions_v2.js => 096_backfill_photo_dimensions_v2.js} (100%) diff --git a/backend/migrations/core/095_add_customer_portal_flag.js b/backend/migrations/core/095_add_customer_portal_flag.js index c1c36e5d..d36c97ad 100644 --- a/backend/migrations/core/095_add_customer_portal_flag.js +++ b/backend/migrations/core/095_add_customer_portal_flag.js @@ -1,5 +1,5 @@ /** - * Migration 094: Add `customerPortal` to feature_flags. + * Migration 095: Add `customerPortal` to feature_flags. * * The customer portal (#354) is the foundation feature for the * customer-side UI surface — login, dashboard, profile, password reset, diff --git a/backend/migrations/core/090_backfill_photo_dimensions_v2.js b/backend/migrations/core/096_backfill_photo_dimensions_v2.js similarity index 100% rename from backend/migrations/core/090_backfill_photo_dimensions_v2.js rename to backend/migrations/core/096_backfill_photo_dimensions_v2.js diff --git a/backend/src/routes/customerAuth.js b/backend/src/routes/customerAuth.js index 5321f427..2f459177 100644 --- a/backend/src/routes/customerAuth.js +++ b/backend/src/routes/customerAuth.js @@ -48,6 +48,12 @@ 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. router.post('/login', [ body('email').isEmail().normalizeEmail().withMessage('Valid email is required'), body('password').isString().notEmpty(), diff --git a/backend/src/services/customerAccountsService.js b/backend/src/services/customerAccountsService.js index 3f48593b..1271f58f 100644 --- a/backend/src/services/customerAccountsService.js +++ b/backend/src/services/customerAccountsService.js @@ -220,6 +220,12 @@ async function acceptInvitation({ token, name, password, profile }) { country_code: merged.country_code || null, password_hash: passwordHash, is_active: formatBoolean(true), + // must_change_password is decorative today — accept-invite always + // sets a customer-chosen password, so this flag is never true and + // customerAuth doesn't read it. TODO when we ship an "admin + // pre-loads a temporary password" flow: surface a code in the + // login response (mirroring adminAuth's MUST_CHANGE_PASSWORD) and + // add a /change-password gate to customerAuth. must_change_password: formatBoolean(false), // Leave password_changed_at NULL on initial accept. Setting it here // creates a millisecond/second-rounding race with the JWT issued