This commit is contained in:
2026-04-22 22:01:07 +08:00
parent d8716d70b0
commit b317c2a8ea
37 changed files with 1821 additions and 515 deletions

View File

@@ -1,3 +1,4 @@
import type { AuthRefreshResponse } from '../../packages/shared/src/contracts/auth';
import {
API_RESPONSE_ENVELOPE_HEADER,
API_RESPONSE_ENVELOPE_VERSION,
@@ -7,7 +8,6 @@ import {
parseApiErrorMessage,
unwrapApiResponse,
} from '../../packages/shared/src/http';
import type { AuthRefreshResponse } from '../../packages/shared/src/contracts/auth';
const ACCESS_TOKEN_KEY = 'genarrative.auth.access-token.v1';
const AUTO_AUTH_USERNAME_KEY = 'genarrative.auth.auto-username.v1';
@@ -487,6 +487,16 @@ async function refreshAccessToken() {
}
}
export async function ensureStoredAccessToken() {
const currentToken = getStoredAccessToken();
if (currentToken) {
return currentToken;
}
// AuthGate 恢复会话时可能只有 HttpOnly refresh cookie本地尚无 access token。
return refreshAccessToken();
}
export async function fetchWithApiAuth(
input: string,
init: RequestInit = {},

View File

@@ -118,7 +118,7 @@ describe('authService', () => {
'登录失败',
);
expect(getStoredAccessToken()).toBe('jwt-entry-token');
expect(window.dispatchEvent).toHaveBeenCalledTimes(1);
expect(window.dispatchEvent).not.toHaveBeenCalled();
});
it('creates a fresh guest credential pair for auto auth when a session is missing', async () => {
@@ -251,7 +251,7 @@ describe('authService', () => {
'登录失败',
);
expect(getStoredAccessToken()).toBe('jwt-phone-token');
expect(window.dispatchEvent).toHaveBeenCalledTimes(1);
expect(window.dispatchEvent).not.toHaveBeenCalled();
});
it('stores renewed access token after wechat bind activation', async () => {
@@ -272,7 +272,7 @@ describe('authService', () => {
expect(user.wechatBound).toBe(true);
expect(getStoredAccessToken()).toBe('jwt-wechat-bind-token');
expect(window.dispatchEvent).toHaveBeenCalledTimes(1);
expect(window.dispatchEvent).not.toHaveBeenCalled();
});
it('changes phone number without emitting a global auth state refresh', async () => {
@@ -367,7 +367,7 @@ describe('authService', () => {
error: null,
});
expect(getStoredAccessToken()).toBe('jwt-callback-token');
expect(window.dispatchEvent).toHaveBeenCalledTimes(1);
expect(window.dispatchEvent).not.toHaveBeenCalled();
expect(replaceStateMock).toHaveBeenCalledWith(null, '', '/');
});

View File

@@ -3,9 +3,9 @@ import type {
AuthAuditLogsResponse,
AuthCaptchaChallenge,
AuthEntryResponse,
AuthLiftRiskBlockResponse,
AuthLoginMethod,
AuthLoginOptionsResponse,
AuthLiftRiskBlockResponse,
AuthLogoutAllResponse,
AuthMeResponse,
AuthPhoneChangeResponse,
@@ -166,7 +166,7 @@ export async function loginWithPhoneCode(phone: string, code: string) {
'登录失败',
);
setStoredAccessToken(response.token);
setStoredAccessToken(response.token, { emit: false });
return response.user;
}
@@ -184,7 +184,7 @@ export async function bindWechatPhone(phone: string, code: string) {
'绑定手机号失败',
);
setStoredAccessToken(response.token);
setStoredAccessToken(response.token, { emit: false });
return response.user;
}
@@ -239,7 +239,7 @@ export async function authEntry(username: string, password: string) {
'登录失败',
);
setStoredAccessToken(response.token);
setStoredAccessToken(response.token, { emit: false });
return response.user;
}
@@ -297,7 +297,7 @@ export function consumeAuthCallbackResult(): ConsumedAuthCallback | null {
}
if (authToken) {
setStoredAccessToken(authToken);
setStoredAccessToken(authToken, { emit: false });
}
if (typeof window.history?.replaceState === 'function') {