From 2d8d58a9ef33f9836fa9e103a910558d59b4a397 Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Thu, 28 May 2026 16:09:24 +0200 Subject: [PATCH] auth: gate SPA boot on /api/auth/me, add user badge and logout --- containers/clearview/site/app.js | 50 +++++++++++++++++++++++++++- containers/clearview/site/index.html | 2 ++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/containers/clearview/site/app.js b/containers/clearview/site/app.js index 8920b97..f145edc 100644 --- a/containers/clearview/site/app.js +++ b/containers/clearview/site/app.js @@ -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 () { const state = { selectedJobId: null, @@ -113,7 +157,11 @@ // ------------------------------------------------------------------------- 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) { let detail = response.statusText; try { diff --git a/containers/clearview/site/index.html b/containers/clearview/site/index.html index d9e9187..257229e 100644 --- a/containers/clearview/site/index.html +++ b/containers/clearview/site/index.html @@ -32,6 +32,7 @@ New Entra Scan + Users Tenants Settings @@ -46,6 +47,7 @@
Dashboard
+