/** * auth-guard.js * Included on every protected page. * * - If /api/v1/auth/me returns 401 → redirect to login.html immediately. * (When AUTH_ENABLED=false the endpoint returns a synthetic guest user, * so the redirect only fires in production auth-enabled mode.) * - On success, populate the sidebar user widget and wire up the logout button. * - Side effect: marks any sidebar link pointing at editor.html with an * "IN DEV" badge, so we don't have to touch every HTML file individually. */ // ── Cross-page IN-DEV markers ───────────────────────────────────────────── // Add (page-name → label) here to flag a sidebar item without editing all // 13 HTML files. The CSS + DOM patch runs once on every page. const IN_DEV_PAGES = { 'editor.html': 'IN DEV', }; (function tagInDevNavItems() { // 1. Inject the badge styles once. Kept inline so we don't add another // HTTP request per page — and so the rule can't get out of sync with // the auth-guard that toggles it. if (!document.getElementById('in-dev-style')) { const style = document.createElement('style'); style.id = 'in-dev-style'; style.textContent = ` .nav-item.is-in-dev { position: relative; } .nav-item.is-in-dev .nav-dev-badge { margin-left: auto; font-size: 9px; font-weight: 700; letter-spacing: 0.12em; padding: 2px 6px; border-radius: 4px; background: oklch(28% 0.14 80 / 0.45); color: oklch(85% 0.16 85); border: 1px solid oklch(50% 0.16 80 / 0.55); text-transform: uppercase; line-height: 1; flex-shrink: 0; } .nav-item.is-in-dev:hover .nav-dev-badge { background: oklch(32% 0.16 80 / 0.55); } `; document.head.appendChild(style); } // 2. Walk every sidebar nav-item link and tag the ones whose href matches // a known in-dev page. Href is matched case-insensitively against the // final path segment so '/editor.html', 'editor.html', and absolute // URLs all hit. const links = document.querySelectorAll('.nav-item'); links.forEach((a) => { const href = (a.getAttribute('href') || '').toLowerCase(); const last = href.split('/').pop().split('?')[0]; const label = IN_DEV_PAGES[last]; if (!label) return; if (a.querySelector('.nav-dev-badge')) return; // idempotent a.classList.add('is-in-dev'); const badge = document.createElement('span'); badge.className = 'nav-dev-badge'; badge.textContent = label; a.appendChild(badge); }); })(); // ── Auth + user widget ──────────────────────────────────────────────────── (async () => { try { const r = await fetch('/api/v1/auth/me', { credentials: 'include' }); if (r.status === 401) { location.replace('login.html'); return; } if (r.ok) { const u = await r.json(); const name = u.display_name || u.username || 'User'; const userNameEl = document.getElementById('userName'); const userAvatarEl = document.getElementById('userAvatar'); const userRoleEl = document.getElementById('userRole'); if (userNameEl) userNameEl.textContent = name; if (userAvatarEl) userAvatarEl.textContent = name[0].toUpperCase(); if (userRoleEl) userRoleEl.textContent = u.role || ''; } } catch (_) { // Network error — don't redirect; the user may be on a dev build without auth. } const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.onclick = async () => { try { await fetch('/api/v1/auth/logout', { method: 'POST', credentials: 'include' }); } catch (_) {} location.href = 'login.html'; }; } })();