Add comprehensive session security to admin panel

Security improvements:
- Auto-logout after 30 minutes of inactivity
- Session warning 5 minutes before expiry with option to extend
- Activity-based session extension on user interaction
- Session validation on page load and API calls
- Periodic session validity checks every minute
- Secure cleanup of tokens and timers on logout
- Protection against expired session usage

This prevents unauthorized access if admin leaves session open or if tokens are compromised.
This commit is contained in:
Deco Vander 2025-07-03 20:53:44 -04:00
parent d9559f71fe
commit 570fd92d00

View file

@ -2,6 +2,12 @@ document.addEventListener('DOMContentLoaded', () => {
let authToken = localStorage.getItem('adminToken');
let allLocations = [];
let editingRowId = null;
let sessionTimeout = null;
let warningTimeout = null;
// Session timeout settings (in milliseconds)
const SESSION_DURATION = 30 * 60 * 1000; // 30 minutes
const WARNING_TIME = 5 * 60 * 1000; // Show warning 5 minutes before expiry
const loginSection = document.getElementById('login-section');
const adminSection = document.getElementById('admin-section');
@ -11,10 +17,19 @@ document.addEventListener('DOMContentLoaded', () => {
const refreshBtn = document.getElementById('refresh-btn');
const locationsTableBody = document.getElementById('locations-tbody');
// Check if already logged in
// Check if already logged in and session is still valid
if (authToken) {
showAdminSection();
loadLocations();
const loginTime = localStorage.getItem('adminLoginTime');
const now = Date.now();
if (loginTime && (now - parseInt(loginTime)) < SESSION_DURATION) {
showAdminSection();
loadLocations();
startSessionTimer();
} else {
// Session expired
logout('Session expired. Please log in again.');
}
}
// Login form handler
@ -34,8 +49,10 @@ document.addEventListener('DOMContentLoaded', () => {
if (response.ok) {
authToken = data.token;
localStorage.setItem('adminToken', authToken);
localStorage.setItem('adminLoginTime', Date.now().toString());
showAdminSection();
loadLocations();
startSessionTimer();
} else {
showMessage(loginMessage, data.error, 'error');
}
@ -46,9 +63,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Logout handler
logoutBtn.addEventListener('click', () => {
authToken = null;
localStorage.removeItem('adminToken');
showLoginSection();
logout('Logged out successfully.');
});
// Refresh button handler
@ -78,6 +93,101 @@ document.addEventListener('DOMContentLoaded', () => {
function hideMessage(element) {
element.style.display = 'none';
}
// Session management functions
function startSessionTimer() {
clearSessionTimers();
// Set warning timer (5 minutes before expiry)
warningTimeout = setTimeout(() => {
showSessionWarning();
}, SESSION_DURATION - WARNING_TIME);
// Set logout timer (30 minutes)
sessionTimeout = setTimeout(() => {
logout('Session expired due to inactivity.');
}, SESSION_DURATION);
}
function clearSessionTimers() {
if (sessionTimeout) {
clearTimeout(sessionTimeout);
sessionTimeout = null;
}
if (warningTimeout) {
clearTimeout(warningTimeout);
warningTimeout = null;
}
}
function showSessionWarning() {
const extend = confirm('Your admin session will expire in 5 minutes. Click OK to extend your session, or Cancel to log out now.');
if (extend) {
extendSession();
} else {
logout('Session ended by user.');
}
}
function extendSession() {
// Update login time and restart timers
localStorage.setItem('adminLoginTime', Date.now().toString());
startSessionTimer();
console.log('Admin session extended for 30 minutes');
}
function logout(message = 'Logged out.') {
clearSessionTimers();
authToken = null;
localStorage.removeItem('adminToken');
localStorage.removeItem('adminLoginTime');
showLoginSection();
if (message) {
showMessage(loginMessage, message, 'error');
}
}
// Reset session timer on user activity
function resetSessionTimer() {
if (authToken && adminSection.style.display !== 'none') {
const loginTime = localStorage.getItem('adminLoginTime');
const now = Date.now();
// Only reset if we're within the session duration
if (loginTime && (now - parseInt(loginTime)) < SESSION_DURATION) {
localStorage.setItem('adminLoginTime', now.toString());
startSessionTimer();
}
}
}
// Listen for user activity to reset session timer
const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'];
let activityTimer = null;
activityEvents.forEach(event => {
document.addEventListener(event, () => {
// Throttle activity detection to once per minute
if (!activityTimer) {
activityTimer = setTimeout(() => {
resetSessionTimer();
activityTimer = null;
}, 60000); // 1 minute throttle
}
}, true);
});
// Check session validity periodically
setInterval(() => {
if (authToken) {
const loginTime = localStorage.getItem('adminLoginTime');
const now = Date.now();
if (!loginTime || (now - parseInt(loginTime)) >= SESSION_DURATION) {
logout('Session expired.');
}
}
}, 60000); // Check every minute
async function loadLocations() {
try {
@ -87,9 +197,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (response.status === 401) {
// Token expired or invalid
authToken = null;
localStorage.removeItem('adminToken');
showLoginSection();
logout('Session expired. Please log in again.');
return;
}