/** * Nexus One AI — Shared Auth Guard * Include in every portal page. Checks session, injects user info into nav. * * Usage: (place just before
tag. */ // ── Session timeout constants ──────────────────────────────────────────────── const SESSION_HOURS = 8; // must match JWT_EXPIRE_HRS in main.py const WARN_BEFORE_MIN = 10; // show warning this many minutes before expiry const EXTEND_INTERVAL = 60000; // poll session state every 60 seconds (async function () { // Don't guard the login page itself if (window.location.pathname.endsWith('login.html')) return; let user = null; try { const res = await fetch('/api/auth/me', { credentials: 'include' }); if (!res.ok) throw new Error('unauthenticated'); user = await res.json(); } catch { window.location.href = '/login.html?next=' + encodeURIComponent(window.location.pathname); return; } // Force password change if (user.must_change_password && !window.location.pathname.endsWith('change-password.html')) { window.location.href = '/change-password.html'; return; } // Admin-only pages if (document.body.dataset.role === 'admin' && user.role !== 'admin') { window.location.href = '/index.html'; return; } // ── Inject user chip + logout into nav ───────────────────────────────────── const topnav = document.querySelector('.topnav'); if (topnav) { document.querySelectorAll('[data-admin-only]').forEach(el => { if (user.role !== 'admin') el.style.display = 'none'; }); const chip = document.createElement('div'); chip.className = 'nav-user-chip'; chip.innerHTML = ` `; topnav.appendChild(chip); document.getElementById('cezen-logout').addEventListener('click', async () => { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }); window.location.href = '/login.html'; }); } // Expose user globally for page scripts window.cezenUser = user; document.dispatchEvent(new CustomEvent('cezenAuthReady', { detail: user })); // ── Online users pill in navbar (admin only) ──────────────────────────────── if (user.role === 'admin' && topnav) { const pill = document.createElement('a'); pill.id = 'cezen-online-pill'; pill.href = '/dashboard.html#sessions'; pill.title = 'View active sessions'; pill.style.cssText = [ 'display:inline-flex;align-items:center;gap:5px;', 'background:rgba(21,128,61,.08);border:1px solid rgba(21,128,61,.2);', 'color:#15803D;border-radius:20px;padding:3px 10px 3px 8px;', 'font-size:11.5px;font-weight:600;text-decoration:none;', 'margin-right:2px;cursor:pointer;transition:.15s;flex-shrink:0;', ].join(''); pill.innerHTML = '… online'; // Insert before user chip (last child) topnav.insertBefore(pill, topnav.lastElementChild); async function refreshOnlineCount() { try { const d = await fetch('/api/users/sessions', { credentials: 'include' }).then(r => r.json()); const n = (d.sessions || []).length; document.getElementById('cezen-online-count').textContent = n; pill.title = (d.sessions || []).map(s => s.username).join(', ') || 'No active sessions'; } catch { /* silently ignore */ } } refreshOnlineCount(); setInterval(refreshOnlineCount, 30000); } // ── Session timeout banner ────────────────────────────────────────────────── // Record login time in sessionStorage so we can track elapsed time const loginKey = 'cezen_login_ts'; if (!sessionStorage.getItem(loginKey)) { sessionStorage.setItem(loginKey, Date.now().toString()); } // Inject banner CSS + element const style = document.createElement('style'); style.textContent = ` #cezen-timeout-banner { display: none; position: fixed; bottom: 20px; right: 20px; background: #1E3A5F; color: white; border-radius: 12px; padding: 14px 18px; font-size: 13px; font-family: inherit; z-index: 9999; box-shadow: 0 8px 32px rgba(0,0,0,.35); max-width: 320px; border-left: 4px solid #F59E0B; animation: cezenFadeIn .3s ease; } @keyframes cezenFadeIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } } #cezen-timeout-banner.expired { border-color: #EF4444; } #cezen-timeout-banner strong { display:block; margin-bottom:4px; font-size:14px; } #cezen-timeout-banner .tb-actions { display:flex; gap:8px; margin-top:10px; } #cezen-timeout-banner .tb-btn { flex:1; padding:6px 0; border:none; border-radius:7px; font-size:12px; font-weight:700; cursor:pointer; font-family:inherit; } #cezen-timeout-banner .tb-extend { background:#0D9488; color:white; } #cezen-timeout-banner .tb-logout { background:rgba(255,255,255,.15); color:white; } `; document.head.appendChild(style); const banner = document.createElement('div'); banner.id = 'cezen-timeout-banner'; banner.innerHTML = ` ⏱ Session expiring soon Your session expires in