Logout: perform full SSO logout via /user-logout

The /?logout=1 endpoint destroyed only the PWA's own PHP session and then
redirected to the bare landing page — which has no session and immediately
blocks access, so the user was never actually logged out of SSO.

- public/index.php: after session_destroy(), redirect to
  https://lupmis4luspa.org/user-logout (the portal's full SSO logout) instead
  of the landing page. Crucially, no longer clear sso_auth_token here —
  /user-logout needs that cookie to identify which SSO session to terminate
  (and it clears the cookie itself). The production access-guard bounce to the
  landing page is unchanged.
- main.js: drop the now-redundant best-effort client call to /sso/logout; the
  server redirect chain (/?logout=1 → /user-logout) owns the SSO logout. Offline
  guard and district-cache wipe unchanged.
- sw.js: update the v12 changelog note (still v12; not yet deployed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ekke 2026-06-25 12:29:45 +00:00
parent d8ddbbc910
commit c335a8987e
9 changed files with 59 additions and 61 deletions

File diff suppressed because one or more lines are too long

View File

@ -919,5 +919,5 @@ Other providers are not affected. The tiles will re-download as you browse onlin
Logging out needs a connection to the sign-in server, and you would not be able to sign back in until you are online again.
Please try again once you have a connection. You can keep working the app stays signed in while offline.`);return}if(!confirm(`Log out, ${n?.full_name||n?.username||"user"}?`))return;const e=document.cookie.split(";").map(t=>t.trim()).find(t=>t.startsWith("sso_auth_token="))?.split("=")[1];if(e)try{await fetch("https://lupmis4luspa.org/sso/logout?token="+encodeURIComponent(e),{method:"GET",mode:"no-cors",credentials:"include",cache:"no-store"})}catch(t){console.warn("[Logout] Best-effort SSO logout call failed:",t)}try{await Io(),localStorage.removeItem(vo)}catch(t){console.warn("[Logout] Cache clear failed (continuing):",t)}window.location.href="/?logout=1"}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",An):An();
//# sourceMappingURL=index-BTRQkY7z.js.map
Please try again once you have a connection. You can keep working the app stays signed in while offline.`);return}if(confirm(`Log out, ${n?.full_name||n?.username||"user"}?`)){try{await Io(),localStorage.removeItem(vo)}catch(e){console.warn("[Logout] Cache clear failed (continuing):",e)}window.location.href="/?logout=1"}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",An):An();
//# sourceMappingURL=index-Ko1mW_fQ.js.map

1
dist/assets/index-Ko1mW_fQ.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/index.html vendored
View File

@ -1601,7 +1601,7 @@
}
}
</style>
<script type="module" crossorigin src="/assets/index-BTRQkY7z.js"></script>
<script type="module" crossorigin src="/assets/index-Ko1mW_fQ.js"></script>
<link rel="modulepreload" crossorigin href="/assets/openlayers-D8ReJJOp.js">
<link rel="modulepreload" crossorigin href="/assets/bootstrap-D1-uvFxm.js">
<link rel="modulepreload" crossorigin href="/assets/ol-ext-P1ircg-B.js">

27
dist/index.php vendored
View File

@ -24,26 +24,33 @@
session_start();
// ────────────────────────────────────────────────────────────────────────────
// Logout — end the PWA's OWN session
// Logout — end the PWA's OWN session, then hand off to the central SSO logout
// ────────────────────────────────────────────────────────────────────────────
// The PWA keeps its own PHP session (PHPSESSID on this host), independent of
// the central SSO portal. Logging out of the SSO portal clears `sso_auth_token`
// but leaves this PHPSESSID session intact, so the previous user's fields
// (incl. district_id) persist. This endpoint is what actually destroys the PWA
// session. Triggered by `/?logout=1` from the in-app menu.
// the central SSO portal, so the previous user's fields (incl. district_id)
// persist until this session is destroyed. We do that here, then redirect to
// the portal's `/user-logout`, which performs the FULL SSO logout (invalidates
// the token server-side and clears the sso_auth_token cookie itself).
//
// Important: we deliberately do NOT clear `sso_auth_token` here. `/user-logout`
// needs that cookie to identify which SSO session to terminate — expiring it
// first would leave the SSO session alive on the server. Redirecting to the
// bare landing page (the old behaviour) only dropped the user on a page that
// then blocked them; it never logged them out of SSO.
//
// Triggered by `/?logout=1` from the in-app menu.
if (isset($_GET['logout'])) {
$_SESSION = [];
// Expire the session cookie itself.
// Expire the PWA's own session cookie (PHPSESSID).
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();
// Clear the shared SSO cookie across all *.lupmis4luspa.org subdomains.
setcookie('sso_auth_token', '', time() - 3600, '/', '.lupmis4luspa.org');
// Bounce to the central LUSPA portal to complete SSO logout.
header('Location: https://lupmis4luspa.org/', true, 302);
// Hand off to the central SSO logout (full logout: invalidates the token
// and clears sso_auth_token). Keep the cookie intact so it can do so.
header('Location: https://lupmis4luspa.org/user-logout', true, 302);
exit;
}

8
dist/sw.js vendored
View File

@ -46,9 +46,11 @@
// sample values + Unicode-bold field names in the mapping dropdown).
// New hashed bundle + updated embed.php shell.
// v12: Session/district correctness — a real /?logout=1 endpoint that destroys
// the PWA's own PHP session (logout previously only cleared the SSO
// cookie, leaving the district_id pinned). SSO is validated once per
// session at login; a transfer is picked up on the next logout→login.
// the PWA's own PHP session and then hands off to the portal's
// /user-logout for a FULL SSO logout (token invalidated + sso_auth_token
// cleared by the portal). Previously logout left the PHP session intact,
// pinning a stale district_id. SSO is validated once per session at login;
// a transfer is picked up on the next logout→login.
// Client-side stale-district guard (wipes district-scoped caches when the
// session district changes) + district-keyed boundary cache; GPS read-out
// UTM coordinate-format setting; ol-ext touch-cursor gated to touch-only

42
main.js
View File

@ -3993,17 +3993,16 @@ function initAccountCard() {
/**
* Logout flow:
* 1. Confirm with the user.
* 2. Best-effort fire-and-forget call to the SSO logout endpoint so the
* server-side token is invalidated (no-cors mode tolerates CORS issues).
* 1. Refuse if offline (login needs the server see guard below).
* 2. Confirm with the user.
* 3. Wipe district-scoped local caches so the next (possibly different) user
* on this device can't briefly see the previous user's cached map data.
* 4. Navigate to the PWA's OWN logout endpoint (/?logout=1). That endpoint
* runs session_destroy() clearing the PHPSESSID session that holds the
* user's district_id clears the sso_auth_token cookie server-side, and
* redirects to the central LUSPA portal. Clearing only the cookie here
* (as before) left the PHP session intact, which is what pinned users to
* a stale district across logout/login.
* user's district_id — and then redirects to the central portal's
* /user-logout, which performs the full SSO logout (invalidates the token
* and clears sso_auth_token). We let the server chain own the SSO side, so
* there is no separate client-side token-invalidation call.
*/
async function handleSignOut(session) {
// Guard: logging back in requires the SSO server (a session can only be
@ -4029,28 +4028,8 @@ async function handleSignOut(session) {
return;
}
// 1. Best-effort: invalidate the SSO token server-side
const cookieToken = document.cookie
.split(';')
.map((c) => c.trim())
.find((c) => c.startsWith('sso_auth_token='))
?.split('=')[1];
if (cookieToken) {
try {
// no-cors swallows CORS errors; we don't read the response
await fetch('https://lupmis4luspa.org/sso/logout?token=' + encodeURIComponent(cookieToken), {
method: 'GET',
mode: 'no-cors',
credentials: 'include',
cache: 'no-store',
});
} catch (err) {
console.warn('[Logout] Best-effort SSO logout call failed:', err);
}
}
// 2. Drop locally-cached, district-scoped layers and the last-district
// marker so a different next user starts clean.
// Drop locally-cached, district-scoped layers and the last-district marker
// so a different next user starts clean.
try {
await clearAllCachedLayers();
localStorage.removeItem(LAST_DISTRICT_KEY);
@ -4058,8 +4037,9 @@ async function handleSignOut(session) {
console.warn('[Logout] Cache clear failed (continuing):', err);
}
// 3. Hand off to the server logout endpoint — it destroys the PHP session,
// clears the SSO cookie, and 302-redirects to the LUSPA portal.
// Hand off to the server logout endpoint: it destroys the PWA's PHP session,
// then redirects to https://lupmis4luspa.org/user-logout for the full SSO
// logout (token invalidation + sso_auth_token cleared by the portal).
window.location.href = '/?logout=1';
}

View File

@ -24,26 +24,33 @@
session_start();
// ────────────────────────────────────────────────────────────────────────────
// Logout — end the PWA's OWN session
// Logout — end the PWA's OWN session, then hand off to the central SSO logout
// ────────────────────────────────────────────────────────────────────────────
// The PWA keeps its own PHP session (PHPSESSID on this host), independent of
// the central SSO portal. Logging out of the SSO portal clears `sso_auth_token`
// but leaves this PHPSESSID session intact, so the previous user's fields
// (incl. district_id) persist. This endpoint is what actually destroys the PWA
// session. Triggered by `/?logout=1` from the in-app menu.
// the central SSO portal, so the previous user's fields (incl. district_id)
// persist until this session is destroyed. We do that here, then redirect to
// the portal's `/user-logout`, which performs the FULL SSO logout (invalidates
// the token server-side and clears the sso_auth_token cookie itself).
//
// Important: we deliberately do NOT clear `sso_auth_token` here. `/user-logout`
// needs that cookie to identify which SSO session to terminate — expiring it
// first would leave the SSO session alive on the server. Redirecting to the
// bare landing page (the old behaviour) only dropped the user on a page that
// then blocked them; it never logged them out of SSO.
//
// Triggered by `/?logout=1` from the in-app menu.
if (isset($_GET['logout'])) {
$_SESSION = [];
// Expire the session cookie itself.
// Expire the PWA's own session cookie (PHPSESSID).
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();
// Clear the shared SSO cookie across all *.lupmis4luspa.org subdomains.
setcookie('sso_auth_token', '', time() - 3600, '/', '.lupmis4luspa.org');
// Bounce to the central LUSPA portal to complete SSO logout.
header('Location: https://lupmis4luspa.org/', true, 302);
// Hand off to the central SSO logout (full logout: invalidates the token
// and clears sso_auth_token). Keep the cookie intact so it can do so.
header('Location: https://lupmis4luspa.org/user-logout', true, 302);
exit;
}

View File

@ -46,9 +46,11 @@
// sample values + Unicode-bold field names in the mapping dropdown).
// New hashed bundle + updated embed.php shell.
// v12: Session/district correctness — a real /?logout=1 endpoint that destroys
// the PWA's own PHP session (logout previously only cleared the SSO
// cookie, leaving the district_id pinned). SSO is validated once per
// session at login; a transfer is picked up on the next logout→login.
// the PWA's own PHP session and then hands off to the portal's
// /user-logout for a FULL SSO logout (token invalidated + sso_auth_token
// cleared by the portal). Previously logout left the PHP session intact,
// pinning a stale district_id. SSO is validated once per session at login;
// a transfer is picked up on the next logout→login.
// Client-side stale-district guard (wipes district-scoped caches when the
// session district changes) + district-keyed boundary cache; GPS read-out
// UTM coordinate-format setting; ol-ext touch-cursor gated to touch-only