pwaLUPMIS2/docs/SSO_Session_Refresh_Proposal.md
ekke d8ddbbc910 Fix stale district + offline-safe logout; GPS UTM format; touch-cursor gating; SW v12
Session / district correctness
- public/index.php: add a /?logout=1 endpoint that destroys the PWA's own PHP
  session (session_destroy + expire PHPSESSID + clear sso_auth_token, then
  redirect to the SSO portal). Logout previously cleared only the SSO cookie,
  leaving the PHPSESSID session — and its frozen district_id — intact, which is
  why a reassigned user kept loading the old district across logout/login.
- SSO token is validated once per session, at login (unchanged first-login
  logic). A district transfer is now picked up on the next logout→login, which
  is correct precisely because logout finally tears the session down. No
  periodic SSO polling.
- main.js: the menu Logout button routes through /?logout=1 and wipes
  district-scoped local caches first. Logout is blocked while offline — a
  session can only be created online, so an offline logout would strand the
  user with no way back in (and would not actually reach the server).
- main.js: enforceDistrictConsistency() clears district-scoped caches when the
  session district changes between loads; the district boundary is cached under
  a per-district key (district_boundary_<id>) so one district's geometry can
  never be served for another.

GPS coordinate format
- New "GPS Coordinate Format" setting (Lat/Lon · UTM · Both) in the Settings
  panel; the navbar read-out renders the chosen format and repaints the current
  fix immediately on change. Self-contained WGS84→UTM converter in
  geo-utils.js, verified against an independent Redfearn-series implementation.

ol-ext touch cursor
- MapView gates the TouchCursor to genuine touch-only devices via matchMedia
  (any-pointer: fine / any-hover: hover); hybrid touchscreen laptops keep the
  normal cursor. Reactive to pointer-capability changes.

- Service worker v11 → v12 (new shell). docs/SSO_Session_Refresh_Proposal.md
  documents the implemented approach.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 11:58:36 +00:00

98 lines
4.3 KiB
Markdown

# 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.