Files
GopherGate/static/js/auth.js
hobokenchicken 6f0a159245
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled
fix: resolve login visibility issues and improve sidebar layout
Corrected element ID mismatches between index.html and auth.js. Improved sidebar CSS to handle collapsed state and logo visibility correctly.
2026-03-19 13:45:55 -04:00

272 lines
8.4 KiB
JavaScript

// 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.getElementById('login-btn');
try {
loginBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 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 = '<i class="fas fa-sign-in-alt"></i> 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('auth-page');
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.getElementById('login-btn');
if (loginBtn) {
loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i> Sign In';
loginBtn.disabled = false;
}
}
showDashboard() {
const loginScreen = document.getElementById('auth-page');
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 = `
<i class="fas fa-${icon} toast-icon"></i>
<div class="toast-content">
<div class="toast-title">${type.charAt(0).toUpperCase() + type.slice(1)}</div>
<div class="toast-message">${message}</div>
</div>
<button class="toast-close">
<i class="fas fa-times"></i>
</button>
`;
// Add close functionality
const closeBtn = toast.querySelector('.toast-close');
closeBtn.addEventListener('click', () => {
toast.remove();
});
// Add to container
container.appendChild(toast);
// Auto-remove after 5 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 5000);
}
}
// Initialize auth manager when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.authManager = new AuthManager();
});
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = AuthManager;
}