移除前端自动游客凭证残留
This commit is contained in:
@@ -99,11 +99,7 @@ beforeEach(() => {
|
||||
});
|
||||
authMocks.startWechatLogin.mockResolvedValue(undefined);
|
||||
authMocks.ensureAutoAuthUser.mockResolvedValue({
|
||||
user: mockUser,
|
||||
credentials: {
|
||||
username: 'guest_tester',
|
||||
password: 'auto_password',
|
||||
},
|
||||
...mockUser,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
setStatus('recovering');
|
||||
|
||||
try {
|
||||
const { user: nextUser } = await ensureAutoAuthUser();
|
||||
const nextUser = await ensureAutoAuthUser();
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
} from '../../packages/shared/src/http';
|
||||
|
||||
const ACCESS_TOKEN_KEY = 'genarrative.auth.access-token.v1';
|
||||
const AUTO_AUTH_USERNAME_KEY = 'genarrative.auth.auto-username.v1';
|
||||
const AUTO_AUTH_PASSWORD_KEY = 'genarrative.auth.auto-password.v1';
|
||||
export const AUTH_STATE_EVENT = 'genarrative-auth-state-changed';
|
||||
const REQUEST_ID_HEADER = 'x-request-id';
|
||||
const API_VERSION_HEADER = 'x-api-version';
|
||||
@@ -376,46 +374,6 @@ export function clearStoredAccessToken(
|
||||
}
|
||||
}
|
||||
|
||||
export function getStoredAutoAuthCredentials() {
|
||||
if (!canUseLocalStorage()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = window.localStorage.getItem(AUTO_AUTH_USERNAME_KEY)?.trim() || '';
|
||||
const password = window.localStorage.getItem(AUTO_AUTH_PASSWORD_KEY)?.trim() || '';
|
||||
|
||||
if (!username || !password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
username,
|
||||
password,
|
||||
};
|
||||
}
|
||||
|
||||
export function setStoredAutoAuthCredentials(credentials: {
|
||||
username: string;
|
||||
password: string;
|
||||
}) {
|
||||
if (!canUseLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.setItem(AUTO_AUTH_USERNAME_KEY, credentials.username.trim());
|
||||
window.localStorage.setItem(AUTO_AUTH_PASSWORD_KEY, credentials.password.trim());
|
||||
}
|
||||
|
||||
export function clearStoredAutoAuthCredentials() {
|
||||
if (!canUseLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.removeItem(AUTO_AUTH_USERNAME_KEY);
|
||||
window.localStorage.removeItem(AUTO_AUTH_PASSWORD_KEY);
|
||||
emitAuthStateChange();
|
||||
}
|
||||
|
||||
function withAuthorizationHeaders(
|
||||
headers?: HeadersInit,
|
||||
options: Pick<ApiRequestOptions, 'omitEnvelopeHeader' | 'skipAuth'> = {},
|
||||
|
||||
@@ -3,11 +3,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
ApiClientError,
|
||||
clearStoredAccessToken,
|
||||
clearStoredAutoAuthCredentials,
|
||||
setStoredAccessToken,
|
||||
} from './apiClient';
|
||||
import {
|
||||
createAutoAuthCredentials,
|
||||
getAuthRiskBlocks,
|
||||
getAuthSessions,
|
||||
getCaptchaChallengeFromError,
|
||||
@@ -185,14 +183,6 @@ describe('authService with SpacetimeDB', () => {
|
||||
spacetimeMocks.ensureSpacetimeConnection.mockReset();
|
||||
spacetimeMocks.disconnectSpacetimeConnection.mockReset();
|
||||
clearStoredAccessToken();
|
||||
clearStoredAutoAuthCredentials();
|
||||
});
|
||||
|
||||
it('creates credentials that match current guest username/password constraints', () => {
|
||||
const credentials = createAutoAuthCredentials();
|
||||
|
||||
expect(credentials.username).toMatch(/^guest_[a-z0-9]{12}$/u);
|
||||
expect(credentials.password).toMatch(/^auto_[a-z0-9]{24}_[a-z0-9]{8}$/u);
|
||||
});
|
||||
|
||||
it('extracts captcha challenge details from api errors', () => {
|
||||
|
||||
@@ -34,18 +34,12 @@ import {
|
||||
import {
|
||||
ApiClientError,
|
||||
clearStoredAccessToken,
|
||||
clearStoredAutoAuthCredentials,
|
||||
getStoredAccessToken,
|
||||
} from './apiClient';
|
||||
|
||||
export type { AuthUser } from '../../packages/shared/src/contracts/auth';
|
||||
export type { AuthLoginMethod } from '../../packages/shared/src/contracts/auth';
|
||||
|
||||
export type AutoAuthCredentials = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type AuthSessionSnapshot = {
|
||||
user: import('../../packages/shared/src/contracts/auth').AuthUser | null;
|
||||
availableLoginMethods: AuthLoginMethod[];
|
||||
@@ -65,30 +59,10 @@ export type ConsumedAuthCallback = {
|
||||
error: string | null;
|
||||
};
|
||||
|
||||
let pendingAutoAuthUser: Promise<{
|
||||
user: AuthUser;
|
||||
credentials: AutoAuthCredentials;
|
||||
}> | null = null;
|
||||
let pendingAutoAuthUser: Promise<AuthUser> | null = null;
|
||||
|
||||
const TOKEN_RECOVERY_TIMEOUT_MS = 3500;
|
||||
|
||||
function buildRandomSegment(length: number) {
|
||||
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const cryptoApi = globalThis.crypto;
|
||||
|
||||
if (!cryptoApi?.getRandomValues) {
|
||||
return Array.from(
|
||||
{ length },
|
||||
() => alphabet[Math.floor(Math.random() * alphabet.length)],
|
||||
).join('');
|
||||
}
|
||||
|
||||
const bytes = cryptoApi.getRandomValues(new Uint8Array(length));
|
||||
return Array.from(bytes, (value) => alphabet[value % alphabet.length]).join(
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise<void>((resolve) => {
|
||||
window.setTimeout(resolve, ms);
|
||||
@@ -255,17 +229,9 @@ export function getCaptchaChallengeFromError(
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createAutoAuthCredentials(): AutoAuthCredentials {
|
||||
return {
|
||||
username: `guest_${buildRandomSegment(12)}`,
|
||||
password: `auto_${buildRandomSegment(24)}_${buildRandomSegment(8)}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearAuthSession() {
|
||||
disconnectSpacetimeConnection();
|
||||
clearStoredAccessToken();
|
||||
clearStoredAutoAuthCredentials();
|
||||
}
|
||||
|
||||
export async function sendPhoneLoginCode(
|
||||
@@ -346,20 +312,9 @@ export async function authEntry(_username: string, _password: string) {
|
||||
return session.user;
|
||||
}
|
||||
|
||||
export async function authEntryWithStoredCredentials(
|
||||
credentials: AutoAuthCredentials,
|
||||
) {
|
||||
const user = await authEntry(credentials.username, credentials.password);
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function ensureAutoAuthUser() {
|
||||
pendingAutoAuthUser ??= (async () => {
|
||||
const user = await authEntry('guest', 'guest');
|
||||
return {
|
||||
user,
|
||||
credentials: createAutoAuthCredentials(),
|
||||
};
|
||||
return authEntry('guest', 'guest');
|
||||
})();
|
||||
|
||||
try {
|
||||
@@ -460,7 +415,6 @@ export async function liftAuthRiskBlock(_scopeType: 'phone' | 'ip') {
|
||||
|
||||
export async function logoutAuthUser() {
|
||||
disconnectSpacetimeConnection({ clearToken: true });
|
||||
clearStoredAutoAuthCredentials();
|
||||
return {
|
||||
ok: true,
|
||||
} satisfies LogoutResponse;
|
||||
@@ -475,7 +429,6 @@ export async function logoutAllAuthSessions() {
|
||||
throw new Error(result.message || '退出全部设备失败');
|
||||
}
|
||||
disconnectSpacetimeConnection({ clearToken: true });
|
||||
clearStoredAutoAuthCredentials();
|
||||
return {
|
||||
ok: true,
|
||||
} satisfies AuthLogoutAllResponse;
|
||||
|
||||
Reference in New Issue
Block a user