Reviewed commit 4423f0a5 (generalize 401 redirect to all 4 personas) and
verified production has no coverage gap: the only uncovered storefront page
(/loyalty/join) hits exclusively public, no-auth endpoints that can't 401,
and authenticated storefront pages live under /account/ which is already
covered. Remaining gap is dev/path-based-mode only (/platforms/...) — captured
as a deferred enhancement in docs/proposals/auth-redirect-dev-prod-symmetry.md
with the proposed one-line prefix-strip fix and two open implementation
details. No code shipped this session. mkdocs --strict clean; architecture
validation passed (0 new findings).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.9 KiB
Auth-Redirect Dev/Prod Symmetry
Date: 2026-05-31 Status: Proposed (deferred — low priority, dev-only impact) Owner: TBD Effort: ~1–2h including a local smoke pass
Motivation
Commit 4423f0a5 ("fix(api-client): generalize 401 redirect from /account/*
to all 4 personas") made redirectIfUnauthorized() in
static/shared/js/api-client.js dispatch a mid-session 401 to the right
persona login page with a ?next= return URL, killing the wrong-state UI flash
on token expiry. It is correct and complete for production, where every
persona is served from its bare path prefix:
| Persona | Prod path matched |
|---|---|
| customer | /account/* (not /account/login) |
| admin | /admin/* (not /admin/login) |
| merchant | /merchants/* (not /merchants/login) |
| store | /store/{code}/* (not /store/{code}/login) |
A follow-up verification (2026-05-31) confirmed there is no production gap:
the only authenticated storefront pages (/account/loyalty,
/account/loyalty/history) fall under the /account/ branch, and the one
uncovered storefront page (/loyalty/join) makes exclusively public,
no-auth API calls (/storefront/loyalty/program, /storefront/loyalty/enroll,
/storefront/cms/pages/{slug}) that cannot return 401.
The remaining gap is dev/path-based mode only.
The gap
In dev/localhost, personas are served behind a /platforms/{...}/ prefix
instead of a subdomain (see app/core/frontend_detector.py and
app/modules/core/utils/page_context.py):
- tenancy store →
/platforms/{platform}/store/{code}/... - storefront →
/platforms/{code}/storefront/{slug}/...(e.g. the dashboard at/platforms/loyalty/storefront/FASHIONHUB/account/loyalty)
redirectIfUnauthorized() matches only the bare prefixes (/store/,
/account/, …), so a path-based-mode URL falls through to return false →
the caller throws → the same wrong-state flash the commit fixed still happens.
Note the asymmetry the commit already introduced: the three login.js files
do normalize the /platforms/{...}/ basePath when honouring ?next=, but
the redirect helper that sets ?next= does not — so in dev the two halves
disagree.
Impact
Dev/localhost only. Cosmetic — a developer hitting an expired-session 401 on a
path-based page sees the old generic error instead of a clean redirect. No
production user is ever on a /platforms/... URL. This is why it is deferred,
not shipped with the original fix.
Proposed change
Strip the dev prefix before the existing branch matching in
redirectIfUnauthorized(), so the same four branches handle both modes:
redirectIfUnauthorized() {
// Dev/path-based mode serves personas behind /platforms/{...}/.
// Strip it so the prod-shaped branch matching below works for both.
const path = window.location.pathname.replace(/^\/platforms\/[^/]+/, '');
// ...existing /account, /admin, /merchants, /store branch matching...
}
Two details to settle during implementation:
- Storefront branch. Today the helper has no storefront branch because in
prod the storefront mounts at
/and authenticated pages already match/account/. After stripping the dev prefix, a path-based storefront URL becomes/storefront/{slug}/account/loyalty— still not/account/*. Decide whether to add a/storefront/branch (mapping to the customer/account/login) or to additionally strip the/storefront/{slug}segment so it collapses to/account/.... The second is closer to how prod behaves. - The
nextvalue.nextis built frompath + search. Confirm it should carry the original prefixed path (so the user lands back on the dev URL) — thelogin.jsbasePath normalization already re-derives the prefix, so passing the stripped path is likely cleaner and avoids double-prefixing. Verify againststore/js/login.js'sstorePathMatchlogic.
Acceptance criteria
- On localhost, an expired-session 401 from an apiClient call on each of:
/platforms/{platform}/store/{code}/.../platforms/{code}/storefront/{slug}/account/loyaltyredirects to the correct persona login with a working?next=, and re-auth lands the user back on the originating page.
- No change to production behaviour (subdomain mode paths are unaffected by the
prefix strip — the regex is a no-op when there is no
/platforms/prefix). - No new architecture-validation findings.
Out of scope
- The original production fix (done in
4423f0a5). - Any change to login.js basePath handling beyond what's needed to honour the
stripped/un-stripped
nextdecision above.
Verification log
- 2026-05-31 — Confirmed prod has no gap: storefront
/loyalty/joinuses only public endpoints; authenticated storefront pages are under/account/. Logged the dev-only gap; agreed to defer behind this proposal.