// Authentication Module for GopherGate Dashboard class AuthManager { constructor() { this.isAuthenticated = false; this.token = null; this.user = null; this.init(); } init() { // Check for existing session const savedToken = localStorage.getItem('dashboard_token'); const savedUser = localStorage.getItem('dashboard_user'); if (savedToken && savedUser) { this.token = savedToken; this.user = JSON.parse(savedUser); this.isAuthenticated = true; this.showDashboard(); } else { this.showLogin(); } // Setup login form this.setupLoginForm(); this.setupLogout(); } setupLoginForm() { const loginForm = document.getElementById('login-form'); if (!loginForm) return; loginForm.addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; await this.login(username, password); }); } setupLogout() { const logoutBtn = document.getElementById('logout-btn'); if (!logoutBtn) return; logoutBtn.addEventListener('click', () => { this.logout(); }); } setToken(newToken) { if (!newToken) return; this.token = newToken; localStorage.setItem('dashboard_token', this.token); } async login(username, password) { const errorElement = document.getElementById('login-error'); const loginBtn = document.querySelector('.login-btn'); try { loginBtn.innerHTML = ' Authenticating...'; loginBtn.disabled = true; const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const result = await response.json(); if (result.success) { this.token = result.data.token; this.user = result.data.user; localStorage.setItem('dashboard_token', this.token); localStorage.setItem('dashboard_user', JSON.stringify(this.user)); this.isAuthenticated = true; this.showDashboard(); this.showToast('Successfully logged in!', 'success'); } else { throw new Error(result.error || 'Invalid credentials'); } } catch (error) { errorElement.style.display = 'flex'; errorElement.querySelector('span').textContent = error.message; loginBtn.innerHTML = ' Sign In'; loginBtn.disabled = false; } } async logout() { // Revoke server-side session first try { if (this.token) { await fetch('/api/auth/logout', { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}` } }); } } catch (e) { // Best-effort — still clear local state even if server call fails console.warn('Server logout failed:', e); } // Clear localStorage localStorage.removeItem('dashboard_token'); localStorage.removeItem('dashboard_user'); // Reset state this.isAuthenticated = false; this.token = null; this.user = null; // Show login screen this.showLogin(); // Show logout message this.showToast('Successfully logged out', 'info'); } showLogin() { const loginScreen = document.getElementById('login-screen'); const dashboard = document.getElementById('dashboard'); if (loginScreen) loginScreen.style.display = 'flex'; if (dashboard) dashboard.style.display = 'none'; // Clear form const loginForm = document.getElementById('login-form'); if (loginForm) loginForm.reset(); // Hide error const errorElement = document.getElementById('login-error'); if (errorElement) errorElement.style.display = 'none'; // Reset button const loginBtn = document.querySelector('.login-btn'); if (loginBtn) { loginBtn.innerHTML = ' Sign In'; loginBtn.disabled = false; } } showDashboard() { const loginScreen = document.getElementById('login-screen'); const dashboard = document.getElementById('dashboard'); if (loginScreen) loginScreen.style.display = 'none'; if (dashboard) dashboard.style.display = 'flex'; // Update user info in sidebar this.updateUserInfo(); // Initialize dashboard components if (typeof window.initDashboard === 'function') { window.initDashboard(); } } updateUserInfo() { const userNameElement = document.querySelector('.user-name'); const userRoleElement = document.querySelector('.user-role'); if (userNameElement && this.user) { userNameElement.textContent = this.user.name || this.user.username || 'User'; } if (userRoleElement && this.user) { const roleLabels = { admin: 'Administrator', viewer: 'Viewer' }; userRoleElement.textContent = roleLabels[this.user.role] || this.user.role || 'User'; } } getAuthHeaders() { if (!this.token) return {}; return { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }; } async fetchWithAuth(url, options = {}) { const headers = this.getAuthHeaders(); const response = await fetch(url, { ...options, headers: { ...headers, ...options.headers } }); if (response.status === 401) { // Token expired or invalid this.logout(); throw new Error('Authentication required'); } return response; } showToast(message, type = 'info') { // Create toast container if it doesn't exist let container = document.querySelector('.toast-container'); if (!container) { container = document.createElement('div'); container.className = 'toast-container'; document.body.appendChild(container); } // Create toast const toast = document.createElement('div'); toast.className = `toast ${type}`; // Set icon based on type let icon = 'info-circle'; switch (type) { case 'success': icon = 'check-circle'; break; case 'error': icon = 'exclamation-circle'; break; case 'warning': icon = 'exclamation-triangle'; break; } toast.innerHTML = `