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:
parent
d9559f71fe
commit
570fd92d00
1 changed files with 117 additions and 9 deletions
126
public/admin.js
126
public/admin.js
|
@ -2,6 +2,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
let authToken = localStorage.getItem('adminToken');
|
let authToken = localStorage.getItem('adminToken');
|
||||||
let allLocations = [];
|
let allLocations = [];
|
||||||
let editingRowId = null;
|
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 loginSection = document.getElementById('login-section');
|
||||||
const adminSection = document.getElementById('admin-section');
|
const adminSection = document.getElementById('admin-section');
|
||||||
|
@ -11,10 +17,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
const refreshBtn = document.getElementById('refresh-btn');
|
const refreshBtn = document.getElementById('refresh-btn');
|
||||||
const locationsTableBody = document.getElementById('locations-tbody');
|
const locationsTableBody = document.getElementById('locations-tbody');
|
||||||
|
|
||||||
// Check if already logged in
|
// Check if already logged in and session is still valid
|
||||||
if (authToken) {
|
if (authToken) {
|
||||||
showAdminSection();
|
const loginTime = localStorage.getItem('adminLoginTime');
|
||||||
loadLocations();
|
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
|
// Login form handler
|
||||||
|
@ -34,8 +49,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
authToken = data.token;
|
authToken = data.token;
|
||||||
localStorage.setItem('adminToken', authToken);
|
localStorage.setItem('adminToken', authToken);
|
||||||
|
localStorage.setItem('adminLoginTime', Date.now().toString());
|
||||||
showAdminSection();
|
showAdminSection();
|
||||||
loadLocations();
|
loadLocations();
|
||||||
|
startSessionTimer();
|
||||||
} else {
|
} else {
|
||||||
showMessage(loginMessage, data.error, 'error');
|
showMessage(loginMessage, data.error, 'error');
|
||||||
}
|
}
|
||||||
|
@ -46,9 +63,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
// Logout handler
|
// Logout handler
|
||||||
logoutBtn.addEventListener('click', () => {
|
logoutBtn.addEventListener('click', () => {
|
||||||
authToken = null;
|
logout('Logged out successfully.');
|
||||||
localStorage.removeItem('adminToken');
|
|
||||||
showLoginSection();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Refresh button handler
|
// Refresh button handler
|
||||||
|
@ -78,6 +93,101 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
function hideMessage(element) {
|
function hideMessage(element) {
|
||||||
element.style.display = 'none';
|
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() {
|
async function loadLocations() {
|
||||||
try {
|
try {
|
||||||
|
@ -87,9 +197,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
// Token expired or invalid
|
// Token expired or invalid
|
||||||
authToken = null;
|
logout('Session expired. Please log in again.');
|
||||||
localStorage.removeItem('adminToken');
|
|
||||||
showLoginSection();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue