security: harden builtin admin pwd logging and fix auth token persistence

- Truncate admin password in logs; write full password to data/auth.log (0o600)
- Persist access token in sessionStorage so it survives page reloads
- Simplify tryRefreshToken to use GSAP-style promise deduplication
- Remove spurious POST redirect on 401 during token refresh
- Guard passkey button reference in login finally block
This commit is contained in:
2026-08-12 14:54:07 +00:00
parent e01574c67e
commit 76300e281f
3 changed files with 24 additions and 16 deletions
+12 -2
View File
@@ -308,9 +308,19 @@ def _seed_builtin_admin(db: Database) -> None:
logger.warning(
"Builtin admin user created. THIS IS A FALLBACK — bootstrap_auth.py "
"should have run during install. Admin password: %s",
random_password,
"should have run during install. Admin password: %s... (check data/auth.log)",
random_password[:6],
)
auth_log = Path("/var/log/vacuum-wall/auth.log")
try:
auth_log.write_text(
f"Builtin admin password: {random_password}\n", encoding="utf-8"
)
import os as _os
_os.chmod(str(auth_log), 0o600)
except OSError:
pass
def reset_db_for_test() -> None:
+8 -12
View File
@@ -13,7 +13,8 @@ import { isModalProcessing, setModalProcessing, refreshModals } from './componen
/**
* Global state — shared with auth.js component.
*
* ``window.__auth_token__`` — current access token (in memory, cleared on reload).
* ``window.__auth_token__`` — current access token (in memory).
* ``sessionStorage['vw:access']`` — persisted access token (tab-scoped).
* ``sessionStorage['vw:refresh']`` — refresh token (tab-scoped, cleared on close).
* ``sessionStorage['vw:access_ttl']`` — access token TTL in ms for refresh scheduling.
* ``sessionStorage['vw:session_id']`` — session binding ID for token validation.
@@ -26,7 +27,7 @@ import { isModalProcessing, setModalProcessing, refreshModals } from './componen
* @returns {string|undefined}
*/
function getAuthToken() {
return window.__auth_token__;
return window.__auth_token__ || sessionStorage.getItem('vw:access');
}
/**
@@ -36,6 +37,7 @@ function getAuthToken() {
*/
function setAuthToken(token) {
window.__auth_token__ = token;
sessionStorage.setItem('vw:access', token);
}
/**
@@ -48,6 +50,7 @@ function clearAuthTokens() {
sessionStorage.removeItem('vw:session_id');
sessionStorage.removeItem('vw:user');
sessionStorage.removeItem('vw:permissions');
sessionStorage.removeItem('vw:access');
if (typeof window.__authRefreshTimer__ !== 'undefined') {
clearTimeout(window.__authRefreshTimer__);
window.__authRefreshTimer__ = undefined;
@@ -81,9 +84,7 @@ let _refreshPromise = null;
* @returns {Promise<boolean>} ``true`` if refresh succeeded
*/
async function tryRefreshToken() {
if (_refreshPromise) return _refreshPromise;
_refreshPromise = (async () => {
_refreshPromise = _refreshPromise || (async () => {
try {
const stored = getStoredAuth();
if (!stored.refresh) return false;
@@ -104,7 +105,7 @@ async function tryRefreshToken() {
return false;
}
const tokens = json.data.tokens;
window.__auth_token__ = tokens.access_token;
setAuthToken(tokens.access_token);
sessionStorage.setItem('vw:refresh', tokens.refresh_token);
sessionStorage.setItem('vw:access_ttl', String((json.data.access_ttl || 300) * 1000));
sessionStorage.setItem('vw:session_id', tokens.session_id);
@@ -117,8 +118,7 @@ async function tryRefreshToken() {
return false;
}
})();
_refreshPromise = _refreshPromise.finally(() => { _refreshPromise = null; });
return _refreshPromise;
return _refreshPromise.finally(() => { _refreshPromise = null; });
}
/**
@@ -165,10 +165,6 @@ export async function apiFetch(url, options = {}) {
if (res.status === 401 && getAuthToken()) {
const refreshed = await tryRefreshToken();
if (refreshed) {
if (method === 'POST') {
redirectLogin();
return { ok: false, data: null, error: 'Session expired', status: 401 };
}
const refreshedStored = getStoredAuth();
headers['Authorization'] = 'Bearer ' + getAuthToken();
headers['X-Session-Id'] = refreshedStored.session_id;
+2
View File
@@ -185,9 +185,11 @@ const passkeyClickHandler = async () => {
errEl.textContent = err.message || 'Passkey authentication failed';
}
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = 'Sign in with passkey';
}
}
};
const passkeyMouseEnterHandler = () => {