fix: auth reconnection loop, duplicate login listeners, modal double-disable
- websocket: clear tokens on refresh failure to prevent infinite 401 loop - api: write vw:user to sessionStorage on refresh for consistency with WS - api: remove vw:user from sessionStorage in clearAuthTokens - login: guard listener setup with flags to prevent duplicate attachment - modal: skip inline button disable when handler uses processing state - users: remove unused requestUpdate import
This commit is contained in:
@@ -46,6 +46,7 @@ function clearAuthTokens() {
|
||||
sessionStorage.removeItem('vw:refresh');
|
||||
sessionStorage.removeItem('vw:access_ttl');
|
||||
sessionStorage.removeItem('vw:session_id');
|
||||
sessionStorage.removeItem('vw:user');
|
||||
localStorage.removeItem('vw:user');
|
||||
if (typeof window.__authRefreshTimer__ !== 'undefined') {
|
||||
clearTimeout(window.__authRefreshTimer__);
|
||||
@@ -102,6 +103,7 @@ async function tryRefreshToken() {
|
||||
sessionStorage.setItem('vw:session_id', tokens.session_id);
|
||||
}
|
||||
if (json.data.user) {
|
||||
sessionStorage.setItem('vw:user', JSON.stringify(json.data.user));
|
||||
localStorage.setItem('vw:user', JSON.stringify(json.data.user));
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -191,8 +191,15 @@ export function formModal(inner, title, fields, actions) {
|
||||
if (a.handler) {
|
||||
const origHandler = a.handler;
|
||||
btn.addEventListener('click', () => {
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="btn-spinner"></span>';
|
||||
// Only inline-disable if the handler doesn't use processing
|
||||
// state. apiSubmit/formAction already call
|
||||
// setModalProcessing + refreshModals which re-creates the
|
||||
// button in processing state. Without the guard the old
|
||||
// button is discarded before the handler even starts.
|
||||
if (!a.processing) {
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="btn-spinner"></span>';
|
||||
}
|
||||
origHandler();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { refreshByTopic } from './model.js?v=9';
|
||||
import { clearAuthTokens } from './api.js?v=12';
|
||||
|
||||
let _wsConn = null;
|
||||
let _wsReconnectMs = 0;
|
||||
@@ -16,8 +17,8 @@ let _wsFailCount = 0;
|
||||
const _directHandlers = [];
|
||||
|
||||
/**
|
||||
* Refresh the access token. Does NOT redirect on failure — the caller
|
||||
* decides what to do when refresh fails.
|
||||
* Refresh the access token. On failure, clears all tokens to prevent
|
||||
* an infinite reconnection loop with a stale token.
|
||||
*
|
||||
* @returns {Promise<boolean>} true if token was refreshed
|
||||
*/
|
||||
@@ -31,9 +32,15 @@ async function _tryRefreshToken() {
|
||||
body: JSON.stringify({ refresh_token: refresh }),
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
if (res.status !== 200) return false;
|
||||
if (res.status !== 200) {
|
||||
clearAuthTokens();
|
||||
return false;
|
||||
}
|
||||
const json = await res.json();
|
||||
if (!json.ok || !json.data?.tokens) return false;
|
||||
if (!json.ok || !json.data?.tokens) {
|
||||
clearAuthTokens();
|
||||
return false;
|
||||
}
|
||||
const tokens = json.data.tokens;
|
||||
window.__auth_token__ = tokens.access_token;
|
||||
sessionStorage.setItem('vw:refresh', tokens.refresh_token);
|
||||
@@ -47,6 +54,7 @@ async function _tryRefreshToken() {
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
clearAuthTokens();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,13 @@ function LoginPage() {
|
||||
`;
|
||||
}
|
||||
|
||||
let _loginFormBound = false;
|
||||
let _passkeyBound = false;
|
||||
|
||||
function handleLogin() {
|
||||
if (_loginFormBound) return;
|
||||
_loginFormBound = true;
|
||||
|
||||
const form = document.getElementById('loginForm');
|
||||
if (!form) return;
|
||||
|
||||
@@ -90,6 +96,9 @@ async function doPasswordLogin() {
|
||||
}
|
||||
|
||||
function setupPasskeyButton() {
|
||||
if (_passkeyBound) return;
|
||||
_passkeyBound = true;
|
||||
|
||||
const passkeyBtn = document.getElementById('passkeyBtn');
|
||||
if (!passkeyBtn) return;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Requires auth: rw permission.
|
||||
*/
|
||||
|
||||
import { h, definePage, reactive, requestUpdate } from '/static/hoover/index.js?v=11';
|
||||
import { h, definePage, reactive } from '/static/hoover/index.js?v=11';
|
||||
import { html, PageHeader, Table, Badge, ConfirmDelete, Empty, Card, openModal, closeModal, formModal, apiFetch, toast, esc } from '/static/hoover/index.js?v=11';
|
||||
|
||||
const SUBSYSTEMS = [
|
||||
|
||||
Reference in New Issue
Block a user