Bugfix autologoff
All checks were successful
release-tag / release-image (push) Successful in 2m5s
release-main / release-images (push) Successful in 4m20s

This commit is contained in:
2026-08-24 22:54:02 +02:00
parent f369ea5f52
commit 7709b9fd33
3 changed files with 76 additions and 16 deletions

View File

@@ -1,5 +1,11 @@
# Changelog
## v0.5.2 Guacamole logout/recovery follow-up
- Fixed an over-aggressive Guacamole browser helper which treated every Guacamole `loggedOut` state as an explicit user logout.
- Explicit clicks on Guacamole logout actions still perform full SessionGuard/Pocket ID RP-initiated logout.
- Guacamole-only token loss (for example after a worker restart/failover) now preserves the SessionGuard/Pocket ID session and re-enters Guacamole through header authentication.
## v0.5.2 PKCE / logout hotfix
- Added PKCE S256 (`code_challenge` / `code_verifier`) to both SessionGuard OIDC authorization-code flows.

View File

@@ -124,7 +124,7 @@ Use the SessionGuard-built Guacamole image from 0.5.0 or later. The extension co
- every 30 seconds it checks `/_sessionguard/auth/status`;
- if the server-side SessionGuard access session has been revoked/expired, it navigates away from Guacamole, closing browser tunnels/WebSockets;
- after Guacamole's own logout reaches its logged-out state, it redirects to `/_sessionguard/auth/logout`, which also ends the SessionGuard/PocketID session.
- an explicit click on a Guacamole logout action redirects to `/_sessionguard/auth/logout`, which also ends the SessionGuard/PocketID session; generic Guacamole token loss/failover does not trigger IdP logout.
## Migration from traefik-forward-auth
@@ -143,4 +143,4 @@ Do not run both auth middlewares on the Guacamole router. A safe migration is:
- Valid access session: Guacamole receives exactly the PocketID username through `X-Guacamole-User`.
- Session older than `session_hours`: denied even if Guacamole still holds an old auth token.
- PocketID back-channel logout: corresponding SessionGuard sessions are revoked immediately; the browser-side poll closes an already-open Guacamole page within about 30 seconds.
- Guacamole logout button: Guacamole destroys its own token, then the SessionGuard helper performs full OIDC logout.
- Guacamole logout button: an explicit user click performs full SessionGuard/PocketID OIDC logout. If Guacamole merely loses its local token (for example after worker failover/restart), SessionGuard keeps the upstream access session and re-enters Guacamole through header auth.

View File

@@ -31,44 +31,76 @@
window.location.replace(url);
}
function checkAccessSession() {
if (redirecting || !window.fetch) {
return;
function getAccessSessionStatus() {
if (!window.fetch) {
return Promise.reject(new Error('fetch unavailable'));
}
window.fetch(AUTH_BASE + '/status', {
return window.fetch(AUTH_BASE + '/status', {
method: 'GET',
credentials: 'same-origin',
cache: 'no-store',
headers: { 'Accept': 'application/json' }
}).then(function (response) {
});
}
function checkAccessSession() {
if (redirecting) {
return;
}
getAccessSessionStatus().then(function (response) {
if (response.status === 401 || response.status === 403) {
// Navigating away also closes Guacamole WebSocket/tunnel
// connections, so a revoked PocketID/SessionGuard access
// session cannot keep an already-open browser client alive.
// The SessionGuard/PocketID access session itself is no longer
// valid. Start a fresh OIDC flow, but do not treat this as an
// explicit logout request.
redirect(loginURL());
}
}).catch(function () {
// A transient auth-status outage must not destroy an active RDP
// session. Traefik still fail-closes all new HTTP requests through
// ForwardAuth; retry this browser-side check on the next interval.
// session. ForwardAuth still fail-closes new requests and this
// browser-side check will retry on the next interval.
});
}
function watchForGuacamoleLogout() {
function recoverFromGuacamoleLogout() {
if (redirecting) {
return;
}
// Guacamole authentication tokens live in the selected webapp process.
// A worker failover/restart can therefore cause Guacamole to enter its
// logged-out state even though the upstream SessionGuard/PocketID
// browser session is still valid. Never turn that condition into a
// full IdP logout. Re-enter Guacamole and let header auth mint a fresh
// Guacamole token instead.
getAccessSessionStatus().then(function (response) {
if (response.ok) {
redirect(safeReturnURL());
}
else if (response.status === 401 || response.status === 403) {
redirect(loginURL());
}
}).catch(function () {
// Keep Guacamole's normal logged-out UI visible during a transient
// SessionGuard outage instead of forcing any logout/login loop.
});
}
function watchForGuacamoleLoggedOutState() {
function loggedOutModalPresent() {
return document.querySelector('.logged-out-modal') !== null;
}
if (loggedOutModalPresent()) {
redirect(logoutURL());
recoverFromGuacamoleLogout();
return;
}
var observer = new MutationObserver(function () {
if (loggedOutModalPresent()) {
observer.disconnect();
redirect(logoutURL());
recoverFromGuacamoleLogout();
}
});
@@ -78,8 +110,30 @@
});
}
function watchForExplicitLogout() {
// Guacamole assigns the CSS class "logout" to its explicit logout
// actions. Observe the user's click rather than the generic
// .logged-out-modal state: the latter can also result from worker
// failover, token loss, expiry or other non-user-initiated events.
document.addEventListener('click', function (event) {
var element = event.target;
if (!element || typeof element.closest !== 'function') {
return;
}
if (element.closest('.logout')) {
// Let Guacamole's own click handler invalidate its local auth
// token first, then perform SessionGuard/PocketID logout.
window.setTimeout(function () {
redirect(logoutURL());
}, 150);
}
}, false);
}
function start() {
watchForGuacamoleLogout();
watchForExplicitLogout();
watchForGuacamoleLoggedOutState();
checkAccessSession();
window.setInterval(checkAccessSession, STATUS_INTERVAL_MS);
}