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>
3.1 KiB
3.1 KiB
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
- User visits
http://localhost:8000/platforms/loyalty/store/FASHIONHUB/login - PlatformContextMiddleware detects
loyaltyand rewrites path to/store/FASHIONHUB/login— correct - Login page renders with loyalty platform context — correct
- JavaScript
login.jsPOSTs toapiClient.post('/store/auth/login', ...)which resolves to/api/v1/store/auth/login— no platform prefix - PlatformContextMiddleware sees
/api/v1/store/auth/login, doesn't match/platforms/*, defaults to "main" platform - Store auth endpoint calls
get_current_platform(request)→ gets "main" (id=2) instead of "loyalty" (id=3) - 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_codein request body (noSTORE_PLATFORM_CODEon 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 § "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+Pon any store page (localhost only) — showswindow.STORE_PLATFORM_CODE,localStorage.store_platform, JWT decoded platform,/auth/meresponse, and consistency checks