Session guard refresh 1s

This commit is contained in:
2026-09-12 09:59:36 +02:00
parent fc1a211c73
commit 987bcf5684
4 changed files with 101 additions and 50 deletions
+74 -43
View File
@@ -1,9 +1,10 @@
/**
* Fociz Session Guard (Release v1.6)
* Fociz Session Guard (Release v1.6.1)
* Realtime beveiligingsbeheer voor sessie-inactiviteit conform NIST SP 800-63B / ISO 27001
* - Waarschuwt de gebruiker 2 minuten voor het verlopen met aftelklok
* - Throttled keepalive pings tijdens actieve interactie
* - Synchronisatie over meerdere open browsertabbladen via localStorage
* - Beschermd tegen oneindige herlaad- en redirect-loops
*/
(function () {
@@ -17,20 +18,40 @@
// Waarschuwing 120 seconden voor afloop (of de helft indien timeout kort is)
var warningSeconds = idleTimeout > 240 ? 120 : Math.max(10, Math.floor(idleTimeout / 2));
var lastActivity = Date.now();
var lastPingTime = Date.now();
var now = Date.now();
var lastActivity = now;
var lastPingTime = now;
var isWarningShowing = false;
var keepaliveInFlight = false;
var hasRedirected = false;
// Synchronisatie met andere tabbladen
// Synchronisatie met andere tabbladen:
// Alleen een timestamp overnemen als deze GELDIG is en NIET al verlopen is!
// Aangezien DEZE pagina zojuist succesvol geladen is, is de sessie NU actief.
var storedActivity = localStorage.getItem('fociz_session_last_activity');
if (storedActivity) {
var parsed = parseInt(storedActivity, 10);
if (!isNaN(parsed) && parsed > 0 && parsed <= Date.now()) {
lastActivity = parsed;
if (!isNaN(parsed) && parsed > 0 && parsed <= now && (now - parsed) < (idleTimeout * 1000)) {
lastActivity = Math.max(now, parsed);
}
}
// Direct opslaan zodat alle tabbladen weten dat er zojuist activiteit was
try {
localStorage.setItem('fociz_session_last_activity', lastActivity.toString());
localStorage.removeItem('fociz_session_logged_out');
} catch (e) {}
// Veilige eenmalige redirect helper om redirect loops te voorkomen
function redirectToExpired() {
if (hasRedirected) return;
hasRedirected = true;
try {
localStorage.setItem('fociz_session_logged_out', Date.now().toString());
} catch (e) {}
window.location.href = '/auth/login.php?expired=1';
}
// Modal DOM elementen
var modalEl = null;
var countdownEl = null;
@@ -69,7 +90,13 @@
}
}
} else if (e.key === 'fociz_session_logged_out') {
window.location.href = '/auth/login.php?expired=1';
if (!hasRedirected) {
var loggedOutTime = parseInt(e.newValue, 10);
if (!isNaN(loggedOutTime) && Math.abs(Date.now() - loggedOutTime) < 10000) {
hasRedirected = true;
window.location.href = '/auth/login.php?expired=1';
}
}
}
});
@@ -104,7 +131,7 @@
// Verzend keepalive naar de backend
function sendKeepalive(manualClick) {
if (keepaliveInFlight) return;
if (hasRedirected || keepaliveInFlight) return;
keepaliveInFlight = true;
if (manualClick && keepaliveBtn) {
@@ -121,9 +148,8 @@
})
.then(function (res) {
if (res.status === 401) {
// Reeds verlopen
localStorage.setItem('fociz_session_logged_out', Date.now().toString());
window.location.href = '/auth/login.php?expired=1';
// Reeds verlopen op de server
redirectToExpired();
return null;
}
return res.json();
@@ -136,9 +162,12 @@
}
if (data && data.success) {
lastActivity = Date.now();
lastPingTime = Date.now();
localStorage.setItem('fociz_session_last_activity', lastActivity.toString());
var currentNow = Date.now();
lastActivity = currentNow;
lastPingTime = currentNow;
try {
localStorage.setItem('fociz_session_last_activity', currentNow.toString());
} catch (e) {}
hideModal();
}
})
@@ -154,7 +183,11 @@
// Direct uitloggen
function performLogout() {
localStorage.setItem('fociz_session_logged_out', Date.now().toString());
if (hasRedirected) return;
hasRedirected = true;
try {
localStorage.setItem('fociz_session_logged_out', Date.now().toString());
} catch (e) {}
fetch('/api/session.php?action=logout', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
@@ -165,49 +198,49 @@
// Registreer gebruikersactiviteit (throttled)
function registerUserActivity() {
var now = Date.now();
if (hasRedirected) return;
var currentNow = Date.now();
// Controleer ALTIJD eerst of de sessie al verlopen was terwijl de gebruiker weg was (bijv. laptop slaapstand of inactiviteit)
var elapsedSec = Math.floor((now - lastActivity) / 1000);
// Controleer of de sessie al verlopen is (bijv. laptop uit slaapstand)
var elapsedSec = Math.floor((currentNow - lastActivity) / 1000);
if (elapsedSec >= idleTimeout) {
localStorage.setItem('fociz_session_logged_out', now.toString());
window.location.href = '/auth/login.php?expired=1';
redirectToExpired();
return;
}
// Als de waarschuwingsmodal actief is, negeren we vage muisbewegingen;
// de gebruiker moet expliciet op 'Blijf Ingelogd' klikken conform beveiligingsnormen.
// Als de waarschuwingsmodal actief is, vereisen we expliciete klik op 'Blijf Ingelogd'
if (isWarningShowing) return;
// Lokale activiteit bijwerken (maximaal eens per 3 seconden wegschrijven)
if (now - lastActivity > 3000) {
lastActivity = now;
localStorage.setItem('fociz_session_last_activity', now.toString());
if (currentNow - lastActivity > 3000) {
lastActivity = currentNow;
try {
localStorage.setItem('fociz_session_last_activity', currentNow.toString());
} catch (e) {}
}
// Server keepalive: stuur op de achtergrond een ping als er activiteit is
// én er minimaal 60 seconden (of idleTimeout / 4) sinds de vorige serverping is verstreken.
// Server keepalive ping (maximaal eens per 60 seconden)
var minPingInterval = Math.min(60000, Math.max(15000, (idleTimeout * 1000) / 4));
if (now - lastPingTime > minPingInterval) {
lastPingTime = now;
if (currentNow - lastPingTime > minPingInterval) {
lastPingTime = currentNow;
sendKeepalive(false);
}
}
// Direct controleren bij terugkeer naar tabblad of ontwaken uit slaapstand
function checkIdleOnWakeup() {
var now = Date.now();
var storedActivity = localStorage.getItem('fociz_session_last_activity');
if (storedActivity) {
var parsed = parseInt(storedActivity, 10);
if (!isNaN(parsed) && parsed > 0) {
if (hasRedirected) return;
var currentNow = Date.now();
var stored = localStorage.getItem('fociz_session_last_activity');
if (stored) {
var parsed = parseInt(stored, 10);
if (!isNaN(parsed) && parsed > 0 && parsed <= currentNow) {
lastActivity = Math.max(lastActivity, parsed);
}
}
var elapsedSec = Math.floor((now - lastActivity) / 1000);
var elapsedSec = Math.floor((currentNow - lastActivity) / 1000);
if (elapsedSec >= idleTimeout) {
localStorage.setItem('fociz_session_logged_out', now.toString());
window.location.href = '/auth/login.php?expired=1';
redirectToExpired();
}
}
@@ -226,14 +259,13 @@
// Hoofdtimer interval (elke seconde)
function tick() {
var now = Date.now();
var elapsedSec = Math.floor((now - lastActivity) / 1000);
if (hasRedirected) return;
var currentNow = Date.now();
var elapsedSec = Math.floor((currentNow - lastActivity) / 1000);
var remainingSec = idleTimeout - elapsedSec;
if (remainingSec <= 0) {
// Sessie officieel verlopen
localStorage.setItem('fociz_session_logged_out', now.toString());
window.location.href = '/auth/login.php?expired=1';
redirectToExpired();
return;
}
@@ -257,4 +289,3 @@
setInterval(tick, 1000);
}
})();