docs(auth): record 2026-05-31 401-redirect review + dev/prod symmetry proposal
Some checks failed
Some checks failed
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>
This commit is contained in:
109
docs/proposals/auth-redirect-dev-prod-symmetry.md
Normal file
109
docs/proposals/auth-redirect-dev-prod-symmetry.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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:
|
||||||
|
|
||||||
|
1. **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.
|
||||||
|
2. **The `next` value.** `next` is built from `path + search`. Confirm it
|
||||||
|
should carry the *original* prefixed path (so the user lands back on the dev
|
||||||
|
URL) — the `login.js` basePath normalization already re-derives the prefix,
|
||||||
|
so passing the stripped path is likely cleaner and avoids double-prefixing.
|
||||||
|
Verify against `store/js/login.js`'s `storePathMatch` logic.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- On localhost, an expired-session 401 from an apiClient call on each of:
|
||||||
|
- `/platforms/{platform}/store/{code}/...`
|
||||||
|
- `/platforms/{code}/storefront/{slug}/account/loyalty`
|
||||||
|
redirects 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 `next` decision above.
|
||||||
|
|
||||||
|
## Verification log
|
||||||
|
|
||||||
|
- **2026-05-31** — Confirmed prod has no gap: storefront `/loyalty/join` uses
|
||||||
|
only public endpoints; authenticated storefront pages are under `/account/`.
|
||||||
|
Logged the dev-only gap; agreed to defer behind this proposal.
|
||||||
@@ -329,6 +329,7 @@ nav:
|
|||||||
- Module Dependency Redesign: proposals/SESSION_NOTE_2026-02-03_module-dependency-redesign.md
|
- Module Dependency Redesign: proposals/SESSION_NOTE_2026-02-03_module-dependency-redesign.md
|
||||||
- Import-002 Cross-Module Deps: proposals/SESSION_NOTE_2026-02-09_import002-cross-module-deps.md
|
- Import-002 Cross-Module Deps: proposals/SESSION_NOTE_2026-02-09_import002-cross-module-deps.md
|
||||||
- Android Terminal Implementation: proposals/android-terminal-implementation.md
|
- Android Terminal Implementation: proposals/android-terminal-implementation.md
|
||||||
|
- Auth-Redirect Dev/Prod Symmetry: proposals/auth-redirect-dev-prod-symmetry.md
|
||||||
- Backward Compatibility Cleanup: proposals/backward-compatibility-cleanup.md
|
- Backward Compatibility Cleanup: proposals/backward-compatibility-cleanup.md
|
||||||
- CMS Redesign Alignment: proposals/cms-redesign-alignment.md
|
- CMS Redesign Alignment: proposals/cms-redesign-alignment.md
|
||||||
- Decouple Modules: proposals/decouple-modules.md
|
- Decouple Modules: proposals/decouple-modules.md
|
||||||
|
|||||||
Reference in New Issue
Block a user