# District-reassignment fix — implemented **Status:** Implemented in `public/index.php`, `main.js`, `index.html`, `public/sw.js` (v12). **Approach chosen:** validate-once-per-session + destroy-session-on-logout (no periodic TTL polling). --- ## 1. The bug When an administrator reassigned a user to a different district (e.g. **47 → 238**), the PWA kept navigating to the old district even after logout, cache clear, and re-login. Two compounding causes: 1. **`public/index.php` validated the SSO token only when no PHP session existed.** Once `$_SESSION['user_id']` was set, the user's fields (`district_id`, …) were frozen for the life of that PHP session. 2. **Nothing ever destroyed the PWA's own PHP session.** The PWA (`pwa.lupmis4luspa.org`) keeps its own `PHPSESSID` session, independent of the central SSO portal. "Logout" cleared only the `sso_auth_token` cookie; the `PHPSESSID` session (holding district 47) survived. Browser "clear cache" does not delete cookies, so the stale `PHPSESSID` kept being reused — and because a session still existed, SSO was never re-validated. Net effect: district 47 stayed pinned until the `PHPSESSID` session was physically destroyed (which nothing did). ## 2. The fix (as implemented) A district changes only on transfer — a rare event — so the validated session is correct for the entire session. We therefore do **not** poll SSO on a timer. Instead: - **Validate once per session, at login** — unchanged first-login validation in `index.php` (`if (!isset($_SESSION['user_id']) && isset($_COOKIE['sso_auth_token']))`). - **Destroy the session on logout** — a new `/?logout=1` endpoint in `index.php` runs `session_destroy()`, expires the `PHPSESSID` cookie, clears `sso_auth_token`, and redirects to the LUSPA portal. A transferred user logs out → the PHP session is destroyed → on the next login there is no session, so SSO is re-validated and the **new** `district_id` is fetched. No periodic SSO calls; the session stays valid the whole time the user is working. ### Logout endpoint (`public/index.php`, immediately after `session_start()`) ```php if (isset($_GET['logout'])) { $_SESSION = []; if (ini_get('session.use_cookies')) { $cp = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $cp['path'], $cp['domain'], $cp['secure'], $cp['httponly']); } session_destroy(); setcookie('sso_auth_token', '', time() - 3600, '/', '.lupmis4luspa.org'); header('Location: https://lupmis4luspa.org/', true, 302); exit; } ``` The SSO validation block below it is the original first-login-only logic. ## 3. Client-side changes - **Logout button** (`index.html` / `main.js`) — the menu's "Logout" button now routes through `/?logout=1` instead of only clearing the cookie, and wipes the device's district-scoped SQLite caches first. - **Stale-district guard** (`main.js` `enforceDistrictConsistency()`) — on load, if the session's `district_id` differs from the last-seen value in `localStorage`, all district-scoped caches (boundary, UPN grid, parcels, zones, roads) are cleared so the map repopulates for the new district. - **District-keyed boundary cache** — the boundary is cached as `district_boundary_`, so one district's geometry can never be served for another. ## 4. Why not a periodic TTL An earlier draft re-validated SSO on a 5-minute TTL. Rejected because: - District changes are rare (transfers only), so polling SSO on every load wastes a blocking `curl` on the page's critical path for no benefit almost all of the time. - The logout→login cycle is the natural, explicit moment a transfer takes effect, and it is now correct because logout destroys the session. ## 5. Operational notes - **Existing stale sessions:** a transferred user who is still on an old session must **log out and log back in** (or have their `PHPSESSID` cleared) once after deploy to pick up the new district. After that the model is self-correcting. - **Assumes the SSO `validate` endpoint returns the new district** — confirmed via an incognito test (fresh session injected the correct `district_id: 238`). - **Edge case:** switching to a different SSO user *without* logging out of the PWA first would reuse the old PHP session. The Logout button is the supported path; this matches the original single-user-per-device assumption.