/** * Spaxel Dashboard - Authentication Module * * Handles PIN setup, login, and session management for the dashboard. * Shows first-run setup page when PIN is not configured. */ (function() { 'use strict'; // ============================================ // Auth State // ============================================ const authState = { pinConfigured: null, isAuthenticated: false, isLoading: true, setupStep: 'enter', // 'enter' | 'confirm' enteredPin: '', loginError: '', setupError: '' }; // ============================================ // DOM Elements // ============================================ let authOverlay = null; let setupOverlay = null; let loginOverlay = null; // ============================================ // Auth API // ============================================ /** * Check if PIN is configured */ function checkAuthStatus() { authState.isLoading = true; renderOverlays(); return fetch('/api/auth/status') .then(function(res) { if (!res.ok) { throw new Error('Failed to check auth status: ' + res.status); } return res.json(); }) .then(function(data) { authState.pinConfigured = data.pin_configured; authState.isLoading = false; // If PIN is configured, check if we have a valid session if (authState.pinConfigured) { return checkSession(); } else { // Show first-run setup renderOverlays(); } }) .catch(function(err) { console.error('[Auth] Error checking auth status:', err); authState.isLoading = false; // On error, assume auth is required authState.pinConfigured = true; renderOverlays(); }); } /** * Check if current session is valid */ function checkSession() { return fetch('/api/settings', { method: 'HEAD' }) .then(function(res) { if (res.ok) { // Session is valid authState.isAuthenticated = true; renderOverlays(); } else { // Session invalid or expired authState.isAuthenticated = false; renderOverlays(); } }) .catch(function(err) { console.error('[Auth] Error checking session:', err); authState.isAuthenticated = false; renderOverlays(); }); } /** * Setup PIN on first run * @param {string} pin - The PIN to set */ function setupPIN(pin) { authState.setupError = ''; renderOverlays(); return fetch('/api/auth/setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pin: pin }) }) .then(function(res) { if (!res.ok) { return res.text().then(function(text) { throw new Error(text || 'Failed to setup PIN'); }); } return res.json(); }) .then(function(data) { // PIN setup successful, reload to start authenticated session window.location.reload(); }) .catch(function(err) { console.error('[Auth] Error setting up PIN:', err); authState.setupError = err.message || 'Failed to setup PIN'; renderOverlays(); throw err; }); } /** * Login with PIN * @param {string} pin - The PIN to authenticate with */ function login(pin) { authState.loginError = ''; renderOverlays(); return fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pin: pin }) }) .then(function(res) { if (!res.ok) { if (res.status === 401) { throw new Error('Invalid PIN'); } return res.text().then(function(text) { throw new Error(text || 'Login failed'); }); } return res.json(); }) .then(function(data) { // Login successful, reload to start authenticated session window.location.reload(); }) .catch(function(err) { console.error('[Auth] Error logging in:', err); authState.loginError = err.message || 'Login failed'; renderOverlays(); throw err; }); } /** * Logout and clear session */ function logout() { return fetch('/api/auth/logout', { method: 'POST' }) .then(function(res) { if (!res.ok) { throw new Error('Logout failed'); } return res.json(); }) .then(function(data) { // Logout successful, reload to show login page window.location.reload(); }) .catch(function(err) { console.error('[Auth] Error logging out:', err); // Even if error, reload to clear local state window.location.reload(); }); } // ============================================ // Overlay Rendering // ============================================ function renderOverlays() { // Remove existing overlays if (authOverlay) { authOverlay.remove(); authOverlay = null; } // If loading, show nothing if (authState.isLoading) { return; } // If PIN not configured, show first-run setup if (!authState.pinConfigured) { renderSetupOverlay(); return; } // If PIN configured but not authenticated, show login if (authState.pinConfigured && !authState.isAuthenticated) { renderLoginOverlay(); return; } } function renderSetupOverlay() { authOverlay = document.createElement('div'); authOverlay.id = 'auth-overlay'; authOverlay.innerHTML = `
Let's secure your dashboard with a PIN
Enter a 4-8 digit PIN to secure your dashboard:
Your PIN should be 4-8 digits
${authState.setupError ? `${authState.setupError}
` : ''} ` : `Confirm your PIN by entering it again:
${authState.setupError}
` : ''} `}Enter your PIN to continue
${authState.loginError}
` : ''}