229 lines
7.7 KiB
JavaScript
229 lines
7.7 KiB
JavaScript
/**
|
|
* Fociz Session Guard (Release v1.6)
|
|
* 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
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
// Script configuratie uitlezen
|
|
var scriptTag = document.currentScript || document.querySelector('script[src*="session-guard.js"]');
|
|
var rawTimeout = scriptTag ? parseInt(scriptTag.getAttribute('data-timeout'), 10) : 1800;
|
|
var idleTimeout = (!isNaN(rawTimeout) && rawTimeout > 0) ? rawTimeout : 1800;
|
|
|
|
// 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 isWarningShowing = false;
|
|
var keepaliveInFlight = false;
|
|
|
|
// Synchronisatie met andere tabbladen
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Modal DOM elementen
|
|
var modalEl = null;
|
|
var countdownEl = null;
|
|
var keepaliveBtn = null;
|
|
var logoutBtn = null;
|
|
|
|
function initDom() {
|
|
modalEl = document.getElementById('focizSessionModal');
|
|
countdownEl = document.getElementById('focizSessionCountdown');
|
|
keepaliveBtn = document.getElementById('focizSessionKeepaliveBtn');
|
|
logoutBtn = document.getElementById('focizSessionLogoutBtn');
|
|
|
|
if (keepaliveBtn) {
|
|
keepaliveBtn.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
sendKeepalive(true);
|
|
});
|
|
}
|
|
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
performLogout();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Luister naar tabblad synchronisatie
|
|
window.addEventListener('storage', function (e) {
|
|
if (e.key === 'fociz_session_last_activity') {
|
|
var updated = parseInt(e.newValue, 10);
|
|
if (!isNaN(updated) && updated > lastActivity) {
|
|
lastActivity = updated;
|
|
if (isWarningShowing) {
|
|
hideModal();
|
|
}
|
|
}
|
|
} else if (e.key === 'fociz_session_logged_out') {
|
|
window.location.href = '/auth/login.php?expired=1';
|
|
}
|
|
});
|
|
|
|
// Formatteer seconden naar mm:ss
|
|
function formatTime(seconds) {
|
|
var m = Math.floor(seconds / 60);
|
|
var s = seconds % 60;
|
|
return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
|
|
}
|
|
|
|
// Toon modal
|
|
function showModal(remainingSeconds) {
|
|
if (!modalEl) initDom();
|
|
if (!modalEl) return;
|
|
|
|
isWarningShowing = true;
|
|
modalEl.classList.remove('hidden');
|
|
modalEl.classList.add('flex');
|
|
|
|
if (countdownEl) {
|
|
countdownEl.textContent = formatTime(remainingSeconds);
|
|
}
|
|
}
|
|
|
|
// Verberg modal
|
|
function hideModal() {
|
|
if (!modalEl) return;
|
|
isWarningShowing = false;
|
|
modalEl.classList.add('hidden');
|
|
modalEl.classList.remove('flex');
|
|
}
|
|
|
|
// Verzend keepalive naar de backend
|
|
function sendKeepalive(manualClick) {
|
|
if (keepaliveInFlight) return;
|
|
keepaliveInFlight = true;
|
|
|
|
if (manualClick && keepaliveBtn) {
|
|
keepaliveBtn.disabled = true;
|
|
keepaliveBtn.textContent = 'Verlengen...';
|
|
}
|
|
|
|
fetch('/api/session.php?action=keepalive', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'Accept': 'application/json'
|
|
}
|
|
})
|
|
.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';
|
|
return null;
|
|
}
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
keepaliveInFlight = false;
|
|
if (manualClick && keepaliveBtn) {
|
|
keepaliveBtn.disabled = false;
|
|
keepaliveBtn.textContent = 'Blijf Ingelogd';
|
|
}
|
|
|
|
if (data && data.success) {
|
|
lastActivity = Date.now();
|
|
lastPingTime = Date.now();
|
|
localStorage.setItem('fociz_session_last_activity', lastActivity.toString());
|
|
hideModal();
|
|
}
|
|
})
|
|
.catch(function (err) {
|
|
keepaliveInFlight = false;
|
|
if (manualClick && keepaliveBtn) {
|
|
keepaliveBtn.disabled = false;
|
|
keepaliveBtn.textContent = 'Blijf Ingelogd';
|
|
}
|
|
console.error('Session keepalive error:', err);
|
|
});
|
|
}
|
|
|
|
// Direct uitloggen
|
|
function performLogout() {
|
|
localStorage.setItem('fociz_session_logged_out', Date.now().toString());
|
|
fetch('/api/session.php?action=logout', {
|
|
method: 'POST',
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
}).finally(function () {
|
|
window.location.href = '/auth/logout.php';
|
|
});
|
|
}
|
|
|
|
// Registreer gebruikersactiviteit (throttled)
|
|
function registerUserActivity() {
|
|
var now = Date.now();
|
|
|
|
// Als de waarschuwingsmodal actief is, negeren we vage muisbewegingen;
|
|
// de gebruiker moet expliciet op 'Blijf Ingelogd' klikken conform beveiligingsnormen.
|
|
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());
|
|
}
|
|
|
|
// 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.
|
|
var minPingInterval = Math.min(60000, Math.max(15000, (idleTimeout * 1000) / 4));
|
|
if (now - lastPingTime > minPingInterval) {
|
|
lastPingTime = now;
|
|
sendKeepalive(false);
|
|
}
|
|
}
|
|
|
|
// Event listeners voor menselijke interactie
|
|
var events = ['mousemove', 'keydown', 'click', 'scroll', 'touchstart'];
|
|
events.forEach(function (evt) {
|
|
window.addEventListener(evt, registerUserActivity, { passive: true });
|
|
});
|
|
|
|
// Hoofdtimer interval (elke seconde)
|
|
function tick() {
|
|
var now = Date.now();
|
|
var elapsedSec = Math.floor((now - 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';
|
|
return;
|
|
}
|
|
|
|
if (remainingSec <= warningSeconds) {
|
|
// Binnen waarschuwingsvenster
|
|
showModal(remainingSec);
|
|
} else if (isWarningShowing) {
|
|
// Waarschuwing niet meer van toepassing (bijv. verlengd vanuit ander tabblad)
|
|
hideModal();
|
|
}
|
|
}
|
|
|
|
// Starten zodra DOM gereed is
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initDom();
|
|
setInterval(tick, 1000);
|
|
});
|
|
} else {
|
|
initDom();
|
|
setInterval(tick, 1000);
|
|
}
|
|
})();
|
|
|