dragonflight/services/web-ui/public/js/auth-guard.js
Zac Gaetano 16a1fe604f web-ui: tag IN DEV pages in sidebar from auth-guard
Adds a tiny CSS rule + DOM patch that walks .nav-item links on every
page and appends an 'IN DEV' badge to those matching a known in-dev
page (currently just editor.html). Avoids touching all 13 HTML files
for the same single-line nav change.
2026-05-21 09:59:29 -04:00

97 lines
3.8 KiB
JavaScript

/**
* 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';
};
}
})();