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
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:
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user