From 66eb8ed394c282a642d46e67edc01c5557017d09 Mon Sep 17 00:00:00 2001 From: alexpaynex <55271826-alexpaynex@users.noreply.replit.com> Date: Thu, 19 Mar 2026 21:00:19 +0000 Subject: [PATCH] Improve login security and user experience on admin panel Add token validation on boot and auto-logout on 401 errors in the admin relay panel. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 1c574898-7c6a-475e-8f63-129c59af48e7 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/67YBlXt Replit-Helium-Checkpoint-Created: true --- .../src/routes/admin-relay-panel.ts | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) 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); })();