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

4.3 KiB

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())

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.