From 65ec449a7c1173033fb63369175ec1874c927257 Mon Sep 17 00:00:00 2001 From: Guofang Tang Date: Fri, 6 Mar 2026 13:41:29 +0800 Subject: [PATCH] fix(admin): prevent logout on api key operations Add token caching and 401 retry logic to fix issue where users are logged out when adding/editing API keys. Cache api_key in ensureApiKey() and retry once with forceRefresh on 401 errors before logout. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- app/static/common/admin-auth.js | 5 ++++- app/static/keys/keys.js | 30 +++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/static/common/admin-auth.js b/app/static/common/admin-auth.js index db6015cb..8f3a6dbd 100644 --- a/app/static/common/admin-auth.js +++ b/app/static/common/admin-auth.js @@ -171,7 +171,10 @@ async function requestApiKey(creds) { return cachedApiKey; } -async function ensureApiKey() { +async function ensureApiKey(forceRefresh = false) { + if (cachedApiKey && !forceRefresh) { + return cachedApiKey; + } const creds = await getStoredAppKey(); if (!creds || !creds.password) { window.location.href = '/login'; diff --git a/app/static/keys/keys.js b/app/static/keys/keys.js index 6e3c8654..f6181362 100644 --- a/app/static/keys/keys.js +++ b/app/static/keys/keys.js @@ -383,14 +383,20 @@ function applyKeyLimitPreset(mode) { q('limit-video').value = recommended.video; } -async function loadKeys() { +async function loadKeys(retryOn401 = true) { const body = q('keys-table-body'); if (body) body.innerHTML = ''; setLoading(true); setEmptyState(false); try { const res = await fetch('/api/v1/admin/keys', { headers: buildAuthHeaders(apiKey) }); - if (res.status === 401) return logout(); + if (res.status === 401) { + if (retryOn401) { + apiKey = await ensureApiKey(true); + if (apiKey) return loadKeys(false); + } + return logout(); + } const payload = await parseJsonSafely(res); if (!res.ok || payload?.success !== true) { throw new Error(extractErrorMessage(payload, '加载失败')); @@ -428,7 +434,12 @@ async function submitKeyModal() { is_active: isActive, }), }); - if (res.status === 401) return logout(); + if (res.status === 401) { + apiKey = await ensureApiKey(true); + if (!apiKey) return logout(); + setSubmitState(false); + return submitKeyModal(); + } const payload = await parseJsonSafely(res); if (!res.ok || payload?.success !== true) { throw new Error(extractErrorMessage(payload, '创建失败')); @@ -457,7 +468,12 @@ async function submitKeyModal() { limits, }), }); - if (res.status === 401) return logout(); + if (res.status === 401) { + apiKey = await ensureApiKey(true); + if (!apiKey) return logout(); + setSubmitState(false); + return submitKeyModal(); + } const payload = await parseJsonSafely(res); if (!res.ok || payload?.success !== true) { throw new Error(extractErrorMessage(payload, '更新失败')); @@ -484,7 +500,11 @@ async function deleteKey(row) { headers: { ...buildAuthHeaders(apiKey), 'Content-Type': 'application/json' }, body: JSON.stringify({ key }), }); - if (res.status === 401) return logout(); + if (res.status === 401) { + apiKey = await ensureApiKey(true); + if (!apiKey) return logout(); + return deleteKey(row); + } const payload = await parseJsonSafely(res); if (!res.ok || payload?.success !== true) { throw new Error(extractErrorMessage(payload, '删除失败'));