fix(auth): Extend JWT expiry to 24h + add friendly session expiration UX

Backend:
- Extend Access Token expiry from 2h to 24h (long operations like
  review/deep-research need sufficient time)
- Refresh Token remains 7 days

Frontend:
- Add sessionGuard.ts: centralized session expiration handler with
  auto token refresh and friendly modal prompt
- ASL fetch client: intercept 401, try refresh, retry on success,
  show friendly modal on failure (was: raw "Unauthorized" red error)
- Axios apiClient: replace alert() + bare redirect with friendly
  session expired modal (covers RVW, IIT, SSA, Admin, DC, PKB)

Tested: Token expiration flow verified, friendly modal displays correctly
Made-with: Cursor
This commit is contained in:
2026-03-08 22:24:33 +08:00
parent a666649fd4
commit b4c293788d
5 changed files with 139 additions and 14 deletions

View File

@@ -15,6 +15,7 @@ import type {
ProjectStatistics
} from '../types';
import { getAccessToken } from '../../../framework/auth/api';
import { handleFetchUnauthorized } from '../../../framework/auth/sessionGuard';
// API基础URL
const API_BASE_URL = '/api/v1/asl';
@@ -33,10 +34,11 @@ function getAuthHeaders(): HeadersInit {
return headers;
}
// 通用请求函数
// 通用请求函数(含 401 自动刷新 + 友好提示)
async function request<T = any>(
url: string,
options?: RequestInit
options?: RequestInit,
_retried = false,
): Promise<T> {
const response = await fetch(`${API_BASE_URL}${url}`, {
...options,
@@ -46,14 +48,26 @@ async function request<T = any>(
},
});
if (response.status === 401 && !_retried) {
let serverMsg = '';
try {
const body = await response.clone().json();
serverMsg = body?.message || '';
} catch { /* ignore */ }
const newToken = await handleFetchUnauthorized(serverMsg);
if (newToken) {
return request<T>(url, options, true);
}
throw new Error('登录已过期,请重新登录');
}
if (!response.ok) {
// 尝试解析错误响应
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
try {
const errorData = await response.json();
errorMessage = errorData.error || errorData.message || errorMessage;
} catch (e) {
// 如果响应体不是JSON使用状态文本
const text = await response.text().catch(() => '');
if (text) {
errorMessage = text;