fix(auth): 5 BUG 修复 — refresh token 持久化 + 密码验证 + 浏览器兼容
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

BUG-1 (P1): LoginPage 注册密码验证从 6 位改为 8 位,与后端一致
BUG-2 (P0): refresh token 持久化到 OS keyring + restoreSession 三级恢复
  (access token → refresh token → cookie auth) + saveSaaSSession 改为 await
BUG-3 (P0): Tauri 聊天路由降级问题,根因同 BUG-2(会话恢复失败)
BUG-4 (P1): App.tsx 跳过 Onboarding 改用 agentStore(兼容所有 client),
  Workspace.tsx Tauri invoke 改为动态 import 避免浏览器崩溃
BUG-5: tauri.conf.json createUpdaterArtifacts 改为 boolean true
This commit is contained in:
iven
2026-04-11 09:43:17 +08:00
parent 1171218276
commit d871685e25
6 changed files with 113 additions and 54 deletions

View File

@@ -201,15 +201,18 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
saasClient.setBaseUrl(normalizedUrl);
const loginData: SaaSLoginResponse = await saasClient.login(trimmedUsername, password);
// Persist session: token → secure storage (OS keyring), metadata → localStorage
// Persist session: token + refresh token → secure storage, metadata → localStorage
const sessionData = {
token: loginData.token, // Will be stored in OS keyring by saveSaaSSession
token: loginData.token,
refreshToken: loginData.refresh_token,
account: loginData.account,
saasUrl: normalizedUrl,
};
saveSaaSSession(sessionData).catch((e) =>
log.warn('Failed to persist SaaS session after login', { error: e })
);
try {
await saveSaaSSession(sessionData);
} catch (e) {
log.warn('Failed to persist SaaS session after login', { error: e });
}
saveConnectionMode('saas');
set({
@@ -306,12 +309,15 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
const sessionData = {
token: loginData.token,
refreshToken: loginData.refresh_token,
account: loginData.account,
saasUrl: normalizedUrl,
};
saveSaaSSession(sessionData).catch((e) =>
log.warn('Failed to persist SaaS session after TOTP login', { error: e })
);
try {
await saveSaaSSession(sessionData);
} catch (e) {
log.warn('Failed to persist SaaS session after TOTP login', { error: e });
}
saveConnectionMode('saas');
set({
@@ -373,12 +379,15 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
const sessionData = {
token: registerData.token,
refreshToken: registerData.refresh_token,
account: registerData.account,
saasUrl: normalizedUrl,
};
saveSaaSSession(sessionData).catch((e) =>
log.warn('Failed to persist SaaS session after register', { error: e })
);
try {
await saveSaaSSession(sessionData);
} catch (e) {
log.warn('Failed to persist SaaS session after register', { error: e });
}
saveConnectionMode('saas');
set({
@@ -744,8 +753,9 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
saasClient.setBaseUrl(restored.saasUrl);
// Strategy: try secure storage token first, then cookie auth
// Strategy: access token → refresh token cookie auth → fail
let account: SaaSAccountInfo | null = null;
let newToken: string | null = restored.token;
if (restored.token) {
// Token from secure storage — use as Bearer
@@ -753,8 +763,37 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
try {
account = await saasClient.me();
} catch {
// Token expired — try cookie auth
// Access token expired — clear and try refresh
saasClient.setToken(null);
newToken = null;
}
}
if (!account && restored.refreshToken) {
// Try refresh token from secure storage
saasClient.setRefreshToken(restored.refreshToken);
try {
const refreshed = await saasClient.refreshMutex();
newToken = refreshed;
saasClient.setToken(refreshed);
account = await saasClient.me();
// Persist the new tokens back to secure storage
try {
const { saveSaaSSession: save } = await import('../lib/saas-session');
await save({
token: refreshed,
refreshToken: saasClient.getRefreshToken(),
account,
saasUrl: restored.saasUrl,
});
} catch (e) {
log.warn('Failed to persist refreshed session', { error: e });
}
} catch {
// Refresh token also expired or invalid
saasClient.setRefreshToken(null);
saasClient.setToken(null);
newToken = null;
}
}
@@ -764,7 +803,7 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
}
if (!account) {
// Neither token nor cookie works — user needs to re-login
// All methods failed — user needs to re-login
set({
isLoggedIn: false,
account: null,
@@ -778,7 +817,7 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
isLoggedIn: true,
account,
saasUrl: restored.saasUrl,
authToken: restored.token, // In-memory from secure storage (null if cookie-only)
authToken: newToken,
connectionMode: loadConnectionMode() === 'saas' ? 'saas' : 'tauri',
});
get().fetchAvailableModels().catch(() => {});
@@ -812,7 +851,7 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
await saasClient.verifyTotp(code);
const account = await saasClient.me();
const { saasUrl } = get();
saveSaaSSession({ token: null, account, saasUrl }).catch((e) =>
saveSaaSSession({ token: null, refreshToken: null, account, saasUrl }).catch((e) =>
log.warn('Failed to persist SaaS session after verifyTotp', { error: e })
); // Token in saasClient memory only
set({ totpSetupData: null, isLoading: false, account });
@@ -830,7 +869,7 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
await saasClient.disableTotp(password);
const account = await saasClient.me();
const { saasUrl } = get();
saveSaaSSession({ token: null, account, saasUrl }).catch((e) =>
saveSaaSSession({ token: null, refreshToken: null, account, saasUrl }).catch((e) =>
log.warn('Failed to persist SaaS session after disableTotp', { error: e })
);
set({ isLoading: false, account });