diff --git a/webui/static/app.js b/webui/static/app.js index afeb58a..688bffa 100644 --- a/webui/static/app.js +++ b/webui/static/app.js @@ -1,4 +1,4 @@ -import { h, render, Link, hComp, ToastContainer, connect, apiFetch, modelRegister, modelFetch, reactive, initAuth, getAuthToken, checkSession } from '/static/hoover/index.js'; +import { h, render, Link, hComp, ToastContainer, connect, disconnect, apiFetch, modelRegister, modelFetch, reactive, createAuthModel, isAuthenticated, getAuthData } from '/static/hoover/index.js'; import DashboardPage from '/static/pages/dashboard.js'; import InterfacesPage from '/static/pages/interfaces.js'; @@ -32,14 +32,17 @@ const _NavBase = [ ]; function getNav() { + const perms = getAuthData()?.permissions; const nav = [..._NavBase]; - const perms = JSON.parse(sessionStorage.getItem('vw:permissions') || 'null'); if (perms && perms.auth === 'rw') { nav.push({ path: '/users', label: 'Users' }); } return nav; } +/* ── Auth model (silent topic — the daemon never broadcasts 'auth') ── */ +modelRegister('auth', createAuthModel()); + modelRegister('firewall', { subsystem: 'firewall', fetch: async () => { @@ -266,14 +269,29 @@ export async function initApp() { // Listen for login events to update router state after auth window.addEventListener('auth:login', () => { router.isAuthenticated = true; - if (!router.state.path.startsWith('/login')) { - fetchInitialData(); - } + // Defer to a macrotask: at dispatch time (microtask) the login form's + // hash change has not run yet — router.state.path is still '/login'. + // The deferred check runs after the hashchange task, so a fresh login + // fetches all models. connect() is idempotent (_wsConnect no-ops with + // a live connection) and gives the post-login session its WS — today + // WS only connects on an authenticated page load (pre-existing gap). + setTimeout(() => { + connect(); + if (!router.state.path.startsWith('/login')) { + fetchInitialData(); + } + }, 0); + }); + + // Terminal auth transition — close the WS socket so a same-tab relogin + // establishes a fresh connection with the new user's token. + window.addEventListener('auth:logout', () => { + disconnect(); }); // Check auth state before connecting WS - const ok = await initAuth(); - if (ok) { + await modelFetch('auth', { action: 'check' }); + if (isAuthenticated()) { router.isAuthenticated = true; fetchInitialData(); setTimeout(connect, 0); diff --git a/webui/static/hoover/index.js b/webui/static/hoover/index.js index df4a685..71fbb17 100644 --- a/webui/static/hoover/index.js +++ b/webui/static/hoover/index.js @@ -23,13 +23,19 @@ export { definePage, hComp } from './component.js'; export { createRouter, Link } from './router.js'; /* ── WebSocket ───────────────────────────────────────────────── */ -export { connect, onMessage } from './websocket.js'; +export { connect, onMessage, disconnect } from './websocket.js'; /* ── API & Toast ─────────────────────────────────────────────── */ -export { apiFetch, toast, dismissToast, apiSubmit, checkAbort, poll, refactorLoad, formAction, setAuthToken, clearAuthTokens, getAuthToken } from './api.js'; +export { apiFetch, toast, dismissToast, apiSubmit, checkAbort, poll, refactorLoad, formAction } + from './api.js'; /* ── UI Components: Auth ──────────────────────────────────────── */ -export { scheduleTokenRefresh, cancelTokenRefresh, checkSession, logout, initAuth, handleLoginSuccess, webauthnSupported, startRegistration, startAuthentication } from './components/auth.js'; +export { logout, doLogin, webauthnSupported, checkWebAuthnCapable, + startRegistration, startAuthentication } from './components/auth.js'; + +/* ── Auth model ───────────────────────────────────────────────── */ +export { createAuthModel, getAuthToken, isAuthenticated, refreshAuth, getAuthData } + from './auth_model.js'; /* ── Model ───────────────────────────────────────────────────── */ export { modelRegister, getModel, modelFetch, collectLoadingModels } from './model.js'; diff --git a/webui/static/pages/login.js b/webui/static/pages/login.js index 6b67661..de7dd45 100644 --- a/webui/static/pages/login.js +++ b/webui/static/pages/login.js @@ -11,9 +11,7 @@ import { html, apiFetch, toast, - setAuthToken, - getAuthToken, - handleLoginSuccess, + doLogin, webauthnSupported, startAuthentication, esc, @@ -89,7 +87,7 @@ async function doPasswordLogin() { }); if (res.ok) { - handleLoginSuccess(res.data); + doLogin(res.data); toast('Welcome, ' + esc(username), 'success'); } else { errEl.textContent = res.error || 'Login failed'; @@ -173,7 +171,7 @@ const passkeyClickHandler = async () => { }); if (finishRes.ok) { - handleLoginSuccess(finishRes.data); + doLogin(finishRes.data); toast('Welcome, ' + esc(username), 'success'); } else { errEl.textContent = finishRes.error || 'Passkey authentication failed';