fix(auth): prevent unexpected session logout

This commit is contained in:
shuaiplus
2026-07-13 02:11:12 +08:00
parent e25ec159bb
commit b731a014f1
24 changed files with 489 additions and 191 deletions
+14 -5
View File
@@ -42,6 +42,7 @@ interface RefreshFailure {
ok: false;
transient: boolean;
error: string;
retryAfterMs?: number;
}
interface RefreshSuccess {
@@ -333,8 +334,8 @@ export async function loginWithAccountPasskeyAssertion(assertion: AccountPasskey
return json;
}
function isTransientRefreshStatus(status: number): boolean {
return status === 0 || status === 429 || status >= 500;
function isPermanentRefreshFailure(status: number, errorCode: string | undefined): boolean {
return status === 400 && (errorCode === 'invalid_grant' || errorCode === 'invalid_request');
}
export async function refreshAccessToken(session: SessionState): Promise<RefreshResult> {
@@ -346,6 +347,8 @@ export async function refreshAccessToken(session: SessionState): Promise<Refresh
try {
const resp = await fetch('/identity/connect/token', {
method: 'POST',
cache: 'no-store',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...(session.authMode === 'web-cookie' ? { [WEB_SESSION_HEADER]: '1' } : {}),
@@ -354,15 +357,19 @@ export async function refreshAccessToken(session: SessionState): Promise<Refresh
});
if (!resp.ok) {
const json = await parseJson<TokenError>(resp);
const retryAfterSeconds = Number(resp.headers.get('Retry-After') || 0);
return {
ok: false,
transient: isTransientRefreshStatus(resp.status),
error: translateServerError(json?.error_description || json?.error, t('txt_session_refresh_failed')),
transient: !isPermanentRefreshFailure(resp.status, json?.error),
error: translateServerError(json?.error_description || json?.error, t('txt_session_refresh_temporarily_unavailable')),
...(Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
? { retryAfterMs: retryAfterSeconds * 1000 }
: {}),
};
}
const json = await parseJson<TokenSuccess>(resp);
if (!json?.access_token) {
return { ok: false, transient: false, error: t('txt_session_refresh_failed') };
return { ok: false, transient: true, error: t('txt_session_refresh_temporarily_unavailable') };
}
return { ok: true, token: json };
} catch (error) {
@@ -400,6 +407,8 @@ export async function revokeCurrentSession(session: SessionState | null): Promis
}
await fetch('/identity/connect/revocation', {
method: 'POST',
cache: 'no-store',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...(session?.accessToken ? { Authorization: `Bearer ${session.accessToken}` } : {}),