# 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_<id>`, 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.
