auth: gate SPA boot on /api/auth/me, add user badge and logout

This commit is contained in:
Ivo Oskamp 2026-05-28 16:09:24 +02:00
parent 939bf38b66
commit 2d8d58a9ef
2 changed files with 51 additions and 1 deletions

View File

@ -1,3 +1,47 @@
// -------------------------------------------------------------------------
// Auth gate: runs before the main app IIFE bootstraps. If the user is not
// authenticated, we redirect to /login.html (or /setup.html when the backend
// indicates the initial setup is still required) and abort further init.
// -------------------------------------------------------------------------
(async function authGate() {
try {
const r = await fetch('/api/auth/me', { credentials: 'same-origin' });
if (r.status === 401) {
const setup = await fetch('/api/auth/setup-required').then(function (x) { return x.json(); }).catch(function () { return { setup_required: false }; });
window.location.replace(setup.setup_required ? '/setup.html' : '/login.html');
return;
}
if (!r.ok) return;
const me = await r.json();
window.__clearviewUser = me;
renderUserBadge(me);
if (me.role !== 'admin') {
const usersLink = document.querySelector('[data-route="users"]');
if (usersLink) usersLink.style.display = 'none';
}
} catch (e) {
window.location.replace('/login.html');
}
})();
function renderUserBadge(me) {
const slot = document.getElementById('userBadge');
if (!slot) return;
slot.innerHTML = '';
const wrap = document.createElement('span');
wrap.className = 'user-badge';
wrap.append(document.createTextNode(me.username + ' (' + me.role + ')'));
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = 'Sign out';
btn.addEventListener('click', async function () {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' });
window.location.replace('/login.html');
});
wrap.append(btn);
slot.append(wrap);
}
(function () { (function () {
const state = { const state = {
selectedJobId: null, selectedJobId: null,
@ -113,7 +157,11 @@
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
async function requestJson(url, options) { async function requestJson(url, options) {
const response = await fetch(url, options); const response = await fetch(url, Object.assign({ credentials: 'same-origin' }, options || {}));
if (response.status === 401) {
window.location.replace('/login.html');
throw new Error('unauthenticated');
}
if (!response.ok) { if (!response.ok) {
let detail = response.statusText; let detail = response.statusText;
try { try {

View File

@ -32,6 +32,7 @@
<div class="nav-section">Entra</div> <div class="nav-section">Entra</div>
<a href="#/scan/entra" class="nav-link" data-route="scan-entra">New Entra Scan</a> <a href="#/scan/entra" class="nav-link" data-route="scan-entra">New Entra Scan</a>
<a href="#/users" class="nav-link" data-route="users">Users</a>
<div class="nav-spacer"></div> <div class="nav-spacer"></div>
<a href="#/tenants" class="nav-link" data-route="tenants">Tenants</a> <a href="#/tenants" class="nav-link" data-route="tenants">Tenants</a>
<a href="#/settings" class="nav-link" data-route="settings">Settings</a> <a href="#/settings" class="nav-link" data-route="settings">Settings</a>
@ -46,6 +47,7 @@
<div class="content-title" id="contentTitle">Dashboard</div> <div class="content-title" id="contentTitle">Dashboard</div>
<div class="content-actions"> <div class="content-actions">
<button id="refreshJobsBtn" class="btn btn-outline" type="button">Refresh</button> <button id="refreshJobsBtn" class="btn btn-outline" type="button">Refresh</button>
<div class="header-user" id="userBadge"></div>
</div> </div>
</header> </header>