Some checks failed
- Add admin SQL query tool with saved queries, schema explorer presets, and collapsible category sections (dev_tools module) - Add platform debug tool for admin diagnostics - Add loyalty settings page with owner-only access control - Fix loyalty settings owner check (use currentUser instead of window.__userData) - Replace HTTPException with AuthorizationException in loyalty routes - Expand loyalty module with PIN service, Apple Wallet, program management - Improve store login with platform detection and multi-platform support - Update billing feature gates and subscription services - Add store platform sync improvements and remove is_primary column - Add unit tests for loyalty (PIN, points, stamps, program services) - Update i18n translations across dev_tools locales Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
55 lines
3.1 KiB
Markdown
55 lines
3.1 KiB
Markdown
# Store Login: JWT Token Gets Wrong Platform
|
|
|
|
**Status:** Resolved
|
|
**Date:** 2026-02-24
|
|
**Resolved:** 2026-03-10
|
|
|
|
## Problem
|
|
|
|
When a user logs in to a store via `/platforms/loyalty/store/FASHIONHUB/login`, the JWT token should encode `platform_id=3` (loyalty). Instead, it gets `platform_id=2` (main). This causes the store sidebar menu to show the wrong modules — e.g., analytics (from "main") appears while loyalty (from "loyalty") is missing.
|
|
|
|
### Root Cause Chain
|
|
|
|
1. User visits `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/login`
|
|
2. PlatformContextMiddleware detects `loyalty` and rewrites path to `/store/FASHIONHUB/login` — **correct**
|
|
3. Login page renders with loyalty platform context — **correct**
|
|
4. JavaScript `login.js` POSTs to `apiClient.post('/store/auth/login', ...)` which resolves to `/api/v1/store/auth/login` — **no platform prefix**
|
|
5. PlatformContextMiddleware sees `/api/v1/store/auth/login`, doesn't match `/platforms/*`, defaults to "main" platform
|
|
6. Store auth endpoint calls `get_current_platform(request)` → gets "main" (id=2) instead of "loyalty" (id=3)
|
|
7. Token encodes `platform_id=2`, all subsequent menu/API calls use the wrong platform
|
|
|
|
## Solution (Implemented)
|
|
|
|
The login endpoint uses a 3-source priority chain to resolve the platform:
|
|
|
|
| Source | How | When it fires |
|
|
|--------|-----|---------------|
|
|
| **Source 1: Middleware** | `request.state.platform` from domain/subdomain/custom-domain | **Production always** — domain carries platform context in every request |
|
|
| **Source 2: Request body** | `platform_code` field in login JSON body | **Dev mode** — JS sends `window.STORE_PLATFORM_CODE \|\| localStorage.store_platform` |
|
|
| **Source 3: Fallback** | `get_first_active_platform_id_for_store()` | **Only** on fresh browser in dev mode (no URL context, no localStorage) |
|
|
|
|
### Files Changed
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `app/modules/tenancy/routes/api/store_auth.py` | Added `platform_code` to `StoreLoginResponse` and `/me` response |
|
|
| `app/modules/tenancy/schemas/auth.py` | Added `platform_code` to `StoreUserResponse` |
|
|
| `app/modules/tenancy/static/store/js/login.js` | Save `platform_code` to localStorage on login; use as fallback in login request |
|
|
|
|
### Why Source 3 Fallback Is Safe
|
|
|
|
Source 3 only fires when **both** Source 1 and Source 2 have nothing — meaning:
|
|
- Not on a platform domain (localhost without `/platforms/` prefix)
|
|
- No `platform_code` in request body (no `STORE_PLATFORM_CODE` on page, no localStorage)
|
|
|
|
This only happens on a completely fresh browser session in dev mode. In production, Source 1 always resolves because the domain itself identifies the platform.
|
|
|
|
### Platform Resolution by URL Pattern
|
|
|
|
See [middleware.md](../architecture/middleware.md) § "Login Platform Resolution" for the complete matrix.
|
|
|
|
## Diagnostic Tools
|
|
|
|
- **Backend trace**: `/admin/platform-debug` — simulates the full middleware + login resolution pipeline for any host/path combo
|
|
- **JS overlay**: `Ctrl+Shift+P` on any store page (localhost only) — shows `window.STORE_PLATFORM_CODE`, `localStorage.store_platform`, JWT decoded platform, `/auth/me` response, and consistency checks
|