From 02d8000202d5d12d191fd2a26494bf5bcc0dd4ea Mon Sep 17 00:00:00 2001 From: Xiaoxia AI Date: Mon, 22 Jun 2026 07:12:53 +0800 Subject: [PATCH] fix(web): align auth contract with api --- apps/web/src/api/auth.ts | 41 +++++++++++++++++++++++++++++---- apps/web/src/api/client.ts | 36 ++++------------------------- apps/web/src/hooks/useAuth.ts | 10 ++++++-- apps/web/src/store/authStore.ts | 13 ++++++++--- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/apps/web/src/api/auth.ts b/apps/web/src/api/auth.ts index 777692f96..be2e931f7 100644 --- a/apps/web/src/api/auth.ts +++ b/apps/web/src/api/auth.ts @@ -11,9 +11,13 @@ export interface LoginRequest { export interface LoginResponse { access_token: string; - refresh_token: string; + refresh_token?: string | null; token_type: string; expires_in: number; + user_id: string; + email: string; + username: string; + display_name: string; } export interface RegisterRequest { @@ -25,13 +29,42 @@ export interface RegisterRequest { export interface User { id: string; + user_id: string; email: string; username: string; display_name: string; is_email_verified: boolean; - created_at: string; + email_verified: boolean; + created_at?: string; } +interface UserResponse { + id?: string; + user_id?: string; + email: string; + username: string; + display_name: string; + is_email_verified?: boolean; + email_verified?: boolean; + created_at?: string; +} + +const normalizeUser = (data: UserResponse): User => { + const userId = data.id ?? data.user_id ?? ''; + const emailVerified = data.is_email_verified ?? data.email_verified ?? false; + + return { + id: userId, + user_id: userId, + email: data.email, + username: data.username, + display_name: data.display_name, + is_email_verified: emailVerified, + email_verified: emailVerified, + created_at: data.created_at, + }; +}; + // 登录 export const login = async (data: LoginRequest): Promise => { const response = await apiClient.post('/auth/login', data); @@ -51,8 +84,8 @@ export const logout = async (): Promise => { // 获取当前用户 export const getCurrentUser = async (): Promise => { - const response = await apiClient.get('/auth/me'); - return response.data; + const response = await apiClient.get('/auth/me'); + return normalizeUser(response.data); }; // 请求密码重置 diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts index c32eebd24..f2a517ecb 100644 --- a/apps/web/src/api/client.ts +++ b/apps/web/src/api/client.ts @@ -27,41 +27,13 @@ apiClient.interceptors.request.use( } ); -// 响应拦截器:处理错误和 Token 刷新 +// 响应拦截器:处理未授权状态 apiClient.interceptors.response.use( (response) => response, async (error: AxiosError) => { - const originalRequest = error.config; - - // Token 过期,尝试刷新 - if (error.response?.status === 401 && originalRequest) { - const refreshToken = localStorage.getItem('refresh_token'); - - if (refreshToken) { - try { - const { data } = await axios.post('/api/v1/auth/refresh', { - refresh_token: refreshToken, - }); - - // 保存新 Token - localStorage.setItem('access_token', data.access_token); - - // 重试原请求 - if (originalRequest.headers) { - originalRequest.headers.Authorization = `Bearer ${data.access_token}`; - } - return apiClient(originalRequest); - } catch (refreshError) { - // 刷新失败,清除 Token 并跳转登录 - localStorage.removeItem('access_token'); - localStorage.removeItem('refresh_token'); - window.location.href = '/login'; - return Promise.reject(refreshError); - } - } else { - // 没有 refresh token,直接跳转登录 - window.location.href = '/login'; - } + if (error.response?.status === 401) { + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); } return Promise.reject(error); diff --git a/apps/web/src/hooks/useAuth.ts b/apps/web/src/hooks/useAuth.ts index 7718f3d47..933cb93a1 100644 --- a/apps/web/src/hooks/useAuth.ts +++ b/apps/web/src/hooks/useAuth.ts @@ -18,13 +18,19 @@ export const useLogin = () => { // 手动处理成功后的逻辑 const login = async (credentials: Parameters[0]) => { const data = await mutation.mutateAsync(credentials); + const refreshToken = data.refresh_token ?? null; + // 先保存 token localStorage.setItem('access_token', data.access_token); - localStorage.setItem('refresh_token', data.refresh_token); + if (refreshToken) { + localStorage.setItem('refresh_token', refreshToken); + } else { + localStorage.removeItem('refresh_token'); + } // 获取用户信息 const user = await authApi.getCurrentUser(); - setAuth(user, data.access_token, data.refresh_token); + setAuth(user, data.access_token, refreshToken); navigate('/workspaces'); return data; }; diff --git a/apps/web/src/store/authStore.ts b/apps/web/src/store/authStore.ts index 5d7355050..6e7b2b44b 100644 --- a/apps/web/src/store/authStore.ts +++ b/apps/web/src/store/authStore.ts @@ -7,10 +7,12 @@ import { persist } from 'zustand/middleware'; interface User { id: string; + user_id: string; email: string; username: string; display_name: string; is_email_verified: boolean; + email_verified: boolean; } interface AuthState { @@ -20,7 +22,8 @@ interface AuthState { isAuthenticated: boolean; // Actions - setAuth: (user: User, accessToken: string, refreshToken: string) => void; + setAuth: (user: User, accessToken: string, refreshToken?: string | null) => void; + clearAuth: () => void; setUser: (user: User) => void; } @@ -33,9 +36,13 @@ export const useAuthStore = create()( refreshToken: null, isAuthenticated: false, - setAuth: (user, accessToken, refreshToken) => { + setAuth: (user, accessToken, refreshToken = null) => { localStorage.setItem('access_token', accessToken); - localStorage.setItem('refresh_token', refreshToken); + if (refreshToken) { + localStorage.setItem('refresh_token', refreshToken); + } else { + localStorage.removeItem('refresh_token'); + } set({ user, accessToken,