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
|
||||
- Import-002 Cross-Module Deps: proposals/SESSION_NOTE_2026-02-09_import002-cross-module-deps.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
|
||||
- CMS Redesign Alignment: proposals/cms-redesign-alignment.md
|
||||
- Decouple Modules: proposals/decouple-modules.md
|
||||
|
||||
Reference in New Issue
Block a user