diff --git a/source-code/mmria/mmria-server/Views/Shared/_LayoutBase.cshtml b/source-code/mmria/mmria-server/Views/Shared/_LayoutBase.cshtml index 7c6a02080..ba06923ce 100644 --- a/source-code/mmria/mmria-server/Views/Shared/_LayoutBase.cshtml +++ b/source-code/mmria/mmria-server/Views/Shared/_LayoutBase.cshtml @@ -79,6 +79,7 @@ if (top !== self) top.location.replace(self.location.href); @if(ViewBag.is_offline_mode_enabled == true){ + } diff --git a/source-code/mmria/mmria-server/wwwroot/scripts/offline/offline-session-validator.js b/source-code/mmria/mmria-server/wwwroot/scripts/offline/offline-session-validator.js index ce240b1d0..424e01c02 100644 --- a/source-code/mmria/mmria-server/wwwroot/scripts/offline/offline-session-validator.js +++ b/source-code/mmria/mmria-server/wwwroot/scripts/offline/offline-session-validator.js @@ -82,11 +82,154 @@ function is_offline_mode() { } // Expose the offline session validator API to the global scope +// Helper function to get session data for validation +async function getSessionDataForValidation() { + // Try service worker first + if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { + try { + const sessionData = await requestSessionDataFromServiceWorker(); + if (sessionData) { + return sessionData; + } + } catch (error) { + console.warn('Failed to get session data from service worker:', error); + } + } + + // Fallback to global variable + if (window.mmria_offline_session_data) { + return window.mmria_offline_session_data; + } + + // Last resort: localStorage + try { + const storedData = localStorage.getItem('mmria_offline_session'); + if (storedData) { + return JSON.parse(storedData); + } + } catch (error) { + console.warn('localStorage not available for session data:', error); + } + + return null; +} + +/** + * Validates the current offline session + * @returns {boolean} - Whether the offline session is valid + */ +function validateOfflineSession() { + try { + const sessionData = localStorage.getItem('mmria_offline_session'); + if (!sessionData) return false; + + const session = JSON.parse(sessionData); + return session && session.user_id; + } catch (error) { + console.error('Error validating offline session:', error); + return false; + } +} + +/** + * Clears offline session state but preserves session data for re-login + */ +function clearOfflineSessionData() { + localStorage.setItem('has_active_offline_session', 'false'); + + // Clear all case data from localStorage for security + try { + const keysToRemove = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith('case_')) { + keysToRemove.push(key); + } + } + + // Remove all case-related keys + keysToRemove.forEach(key => { + localStorage.removeItem(key); + }); + + // Clear the case index as well + localStorage.removeItem('case_index'); + + console.log(`Cleared ${keysToRemove.length} case data items from localStorage on logout`); + } catch (error) { + console.error('Error clearing case data on logout:', error); + } + + // Notify service worker of status change + if (window.ServiceWorkerManager) { + window.ServiceWorkerManager.notifyActiveOfflineSessionChange(); + } +} + +/** + * Logs offline events for audit purposes + * @param {string} action - The action being performed + * @param {string} message - Description of the action + */ +function logOfflineEvent(action, message) { + try { + const events = JSON.parse(localStorage.getItem('offline_audit_log') || '[]'); + const sessionData = JSON.parse(localStorage.getItem('mmria_offline_session') || '{}'); + + events.push({ + action, + message, + timestamp: new Date().toISOString(), + user: sessionData.user_id || 'unknown', + sessionId: localStorage.getItem('offline_session_id') || 'unknown' + }); + + // Keep only last 100 events to prevent localStorage overflow + if (events.length > 100) { + events.splice(0, events.length - 100); + } + + localStorage.setItem('offline_audit_log', JSON.stringify(events)); + } catch (error) { + console.error('Error logging offline event:', error); + } +} + +/** + * Checks if user has a valid offline session and redirects to login if not + * Should be called on page load for protected routes + */ +function checkOfflineSessionAndRedirect() { + const isOfflineMode = localStorage.getItem('is_offline') === 'true'; + const hasActiveSession = localStorage.getItem('has_active_offline_session') === 'true'; + + if (isOfflineMode && !hasActiveSession) { + console.log('Session validation failed: No active offline session, redirecting to offline login'); + + // Log the event for audit purposes + logOfflineEvent('session_invalid', 'User attempted to access protected route without valid session'); + + // Clear any potentially stale data + clearOfflineSessionData(); + + // Redirect to offline login + window.location.href = '/Account/OfflineLogin'; + return false; // Session invalid + } + + return true; // Session valid or not in offline mode +} + window.OfflineSessionValidator = { validateKey: validate_offline_key, getSessionData: get_offline_session_data, validateKeyAgainstSession: validate_offline_key_against_session, - isOfflineMode: is_offline_mode + isOfflineMode: is_offline_mode, + getSessionDataForValidation: getSessionDataForValidation, + validateOfflineSession: validateOfflineSession, + clearOfflineSessionData: clearOfflineSessionData, + logOfflineEvent: logOfflineEvent, + checkOfflineSessionAndRedirect: checkOfflineSessionAndRedirect }; console.log('Offline Session Validator module loaded'); diff --git a/source-code/mmria/mmria-server/wwwroot/scripts/shared/logout-handler.js b/source-code/mmria/mmria-server/wwwroot/scripts/shared/logout-handler.js index 9c80276be..0d168fa46 100644 --- a/source-code/mmria/mmria-server/wwwroot/scripts/shared/logout-handler.js +++ b/source-code/mmria/mmria-server/wwwroot/scripts/shared/logout-handler.js @@ -15,7 +15,7 @@ async function encryptCasesOnOfflineLogout(enteredKey) { return; } - const sessionData = await getSessionDataForValidation(); + const sessionData = await window.OfflineSessionValidator.getSessionDataForValidation(); if (!sessionData || !sessionData.keySalt) return; // Send password to service worker to derive and set key @@ -39,38 +39,6 @@ async function encryptCasesOnOfflineLogout(enteredKey) { } } -// Helper function to get session data for validation -async function getSessionDataForValidation() { - // Try service worker first - if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { - try { - const sessionData = await requestSessionDataFromServiceWorker(); - if (sessionData) { - return sessionData; - } - } catch (error) { - console.warn('Failed to get session data from service worker:', error); - } - } - - // Fallback to global variable - if (window.mmria_offline_session_data) { - return window.mmria_offline_session_data; - } - - // Last resort: localStorage - try { - const storedData = localStorage.getItem('mmria_offline_session'); - if (storedData) { - return JSON.parse(storedData); - } - } catch (error) { - console.warn('localStorage not available for session data:', error); - } - - return null; -} - async function handleLogout(event) { const isOffline = localStorage.getItem('is_offline') === 'true'; @@ -79,9 +47,9 @@ async function handleLogout(event) { event.preventDefault(); // Validate offline session before logout - if (validateOfflineSession()) { + if (window.OfflineSessionValidator.validateOfflineSession()) { // Log the logout event for audit purposes - logOfflineEvent('logout', 'User logged out in offline mode'); + window.OfflineSessionValidator.logOfflineEvent('logout', 'User logged out in offline mode'); // Show a brief message before redirecting //showLogoutMessage('Logging out of offline mode...'); @@ -91,7 +59,7 @@ async function handleLogout(event) { //await encryptCasesOnOfflineLogout("sssDDDkkk@@@2d"); // Clear all offline data securely - await clearOfflineSessionData(); + await window.OfflineSessionValidator.clearOfflineSessionData(); // Small delay to show message, then redirect setTimeout(() => { @@ -105,87 +73,6 @@ async function handleLogout(event) { return true; } -/** - * Validates the current offline session - * @returns {boolean} - Whether the offline session is valid - */ -function validateOfflineSession() { - try { - const sessionData = localStorage.getItem('mmria_offline_session'); - if (!sessionData) return false; - - const session = JSON.parse(sessionData); - return session && session.user_id; - } catch (error) { - console.error('Error validating offline session:', error); - return false; - } -} - -/** - * Clears offline session state but preserves session data for re-login - */ -function clearOfflineSessionData() { - localStorage.setItem('has_active_offline_session', 'false'); - - // Clear all case data from localStorage for security - try { - const keysToRemove = []; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key && key.startsWith('case_')) { - keysToRemove.push(key); - } - } - - // Remove all case-related keys - keysToRemove.forEach(key => { - localStorage.removeItem(key); - }); - - // Clear the case index as well - localStorage.removeItem('case_index'); - - console.log(`Cleared ${keysToRemove.length} case data items from localStorage on logout`); - } catch (error) { - console.error('Error clearing case data on logout:', error); - } - - // Notify service worker of status change - if (window.ServiceWorkerManager) { - window.ServiceWorkerManager.notifyActiveOfflineSessionChange(); - } -} - -/** - * Logs offline events for audit purposes - * @param {string} action - The action being performed - * @param {string} message - Description of the action - */ -function logOfflineEvent(action, message) { - try { - const events = JSON.parse(localStorage.getItem('offline_audit_log') || '[]'); - const sessionData = JSON.parse(localStorage.getItem('mmria_offline_session') || '{}'); - - events.push({ - action, - message, - timestamp: new Date().toISOString(), - user: sessionData.user_id || 'unknown', - sessionId: localStorage.getItem('offline_session_id') || 'unknown' - }); - - // Keep only last 100 events to prevent localStorage bloat - if (events.length > 100) { - events.splice(0, events.length - 100); - } - - localStorage.setItem('offline_audit_log', JSON.stringify(events)); - } catch (error) { - console.error('Error logging offline event:', error); - } -} - /** * Shows a logout message to the user * @param {string} message - Message to display @@ -215,31 +102,6 @@ function showLogoutMessage(message) { }, 3000); } -/** - * Checks if user has a valid offline session and redirects to login if not - * Should be called on page load for protected routes - */ -function checkOfflineSessionAndRedirect() { - const isOfflineMode = localStorage.getItem('is_offline') === 'true'; - const hasActiveSession = localStorage.getItem('has_active_offline_session') === 'true'; - - if (isOfflineMode && !hasActiveSession) { - console.log('Session validation failed: No active offline session, redirecting to offline login'); - - // Log the event for audit purposes - logOfflineEvent('session_invalid', 'User attempted to access protected route without valid session'); - - // Clear any potentially stale data - clearOfflineSessionData(); - - // Redirect to offline login - window.location.href = '/Account/OfflineLogin'; - return false; // Session invalid - } - - return true; // Session valid or not in offline mode -} - /** * Initialize logout handlers and session validation when DOM is ready * This provides an alternative to inline onsubmit handlers @@ -261,12 +123,12 @@ document.addEventListener('DOMContentLoaded', function() { const currentPath = window.location.pathname.toLowerCase(); if (currentPath.includes('/case') || currentPath.includes('/home')) { console.log('Protected route detected, validating offline session...'); - checkOfflineSessionAndRedirect(); + window.OfflineSessionValidator.checkOfflineSessionAndRedirect(); } }); // Make functions globally available window.handleLogout = handleLogout; -window.clearOfflineSessionData = clearOfflineSessionData; -window.validateOfflineSession = validateOfflineSession; -window.checkOfflineSessionAndRedirect = checkOfflineSessionAndRedirect; \ No newline at end of file +window.clearOfflineSessionData = window.OfflineSessionValidator.clearOfflineSessionData; +window.validateOfflineSession = window.OfflineSessionValidator.validateOfflineSession; +window.checkOfflineSessionAndRedirect = window.OfflineSessionValidator.checkOfflineSessionAndRedirect; \ No newline at end of file