From ac7850b88066add27336153eb1f9a679328746ed Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 31 May 2026 22:34:09 +0200 Subject: [PATCH] docs(auth): record 2026-05-31 401-redirect review + dev/prod symmetry proposal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../auth-redirect-dev-prod-symmetry.md | 109 ++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 110 insertions(+) create mode 100644 docs/proposals/auth-redirect-dev-prod-symmetry.md diff --git a/docs/proposals/auth-redirect-dev-prod-symmetry.md b/docs/proposals/auth-redirect-dev-prod-symmetry.md new file mode 100644 index 00000000..3b081928 --- /dev/null +++ b/docs/proposals/auth-redirect-dev-prod-symmetry.md @@ -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. diff --git a/mkdocs.yml b/mkdocs.yml index 793d79d7..138cf25a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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