diff --git a/artifacts/api-server/src/routes/admin-relay-panel.ts b/artifacts/api-server/src/routes/admin-relay-panel.ts index 55d626e..d110524 100644 --- a/artifacts/api-server/src/routes/admin-relay-panel.ts +++ b/artifacts/api-server/src/routes/admin-relay-panel.ts @@ -490,13 +490,19 @@ let refreshTimer = null; // ── Auth ───────────────────────────────────────────────────────────────────── +async function verifyToken(token) { + const r = await fetch(BASE + '/api/admin/relay/stats', { + headers: { Authorization: 'Bearer ' + token } + }).catch(() => null); + if (!r || r.status === 401) return null; + return r; +} + async function submitToken() { const val = document.getElementById('token-input').value.trim(); if (!val) return; - const r = await fetch(BASE + '/api/admin/relay/stats', { - headers: { Authorization: 'Bearer ' + val } - }); - if (r.status === 401) { + const r = await verifyToken(val); + if (!r) { document.getElementById('auth-error').style.display = 'block'; return; } @@ -530,10 +536,23 @@ async function showMain(initialStats) { // ── API helpers ────────────────────────────────────────────────────────────── +// api() wraps fetch with Bearer auth. On 401, clears stored token and forces +// back to auth gate so the user is never stuck in a degraded "logged-in" state +// with a stale/expired token. async function api(path, opts) { opts = opts || {}; const headers = Object.assign({ Authorization: 'Bearer ' + adminToken, 'Content-Type': 'application/json' }, opts.headers || {}); - return fetch(BASE + '/api' + path, Object.assign({}, opts, { headers })); + const r = await fetch(BASE + '/api' + path, Object.assign({}, opts, { headers })); + if (r.status === 401) { + localStorage.removeItem(LS_KEY); + adminToken = ''; + clearInterval(refreshTimer); + document.getElementById('main').style.display = 'none'; + document.getElementById('auth-gate').style.display = 'block'; + document.getElementById('auth-error').style.display = 'block'; + document.getElementById('auth-error').textContent = 'Session expired or token changed — please re-authenticate.'; + } + return r; } // ── Stats ──────────────────────────────────────────────────────────────────── @@ -725,13 +744,21 @@ function toast(msg, type) { } // ── Boot ───────────────────────────────────────────────────────────────────── +// Validate any saved token before showing the main panel to avoid a degraded +// "logged-in" state if the token was rotated or never valid. -(function boot() { +(async function boot() { var saved = localStorage.getItem(LS_KEY); - if (saved) { - adminToken = saved; - showMain(null); + if (!saved) return; + var r = await verifyToken(saved); + if (!r) { + localStorage.removeItem(LS_KEY); + return; } + adminToken = saved; + var stats = null; + try { stats = await r.json(); } catch(e) {} + showMain(stats); })();