diff --git a/apps/web/src/hooks/useAuth.ts b/apps/web/src/hooks/useAuth.ts index 1d3f0fec5..ae1a958cf 100644 --- a/apps/web/src/hooks/useAuth.ts +++ b/apps/web/src/hooks/useAuth.ts @@ -20,24 +20,56 @@ export const useLogin = () => { const data = await mutation.mutateAsync(credentials) const refreshToken = data.refresh_token ?? null - // 先保存 token - localStorage.setItem("access_token", data.access_token) - if (refreshToken) { - localStorage.setItem("refresh_token", refreshToken) - } else { - localStorage.removeItem("refresh_token") - } - // 获取用户信息 const user = await authApi.getCurrentUser() setAuth(user, data.access_token, refreshToken) - navigate("/") + + // 跳转到登录前页面或首页 + const redirect = localStorage.getItem("login_redirect") || "/" + localStorage.removeItem("login_redirect") + navigate(redirect, { replace: true }) return data } return { ...mutation, mutateAsync: login } } +// 微信登录 Hook(用于回调后处理登录状态) +export const useWechatCallback = () => { + const navigate = useNavigate() + const setAuth = useAuthStore((state) => state.setAuth) + const setUser = useAuthStore((state) => state.setUser) + + const mutation = useMutation({ + mutationFn: ({ code, state }: { code: string; state: string }) => + authApi.wechatCallback(code, state), + }) + + const handleCallback = async (code: string, state: string) => { + // 校验 state + const savedState = localStorage.getItem("wechat_state") + if (!savedState || savedState !== state) { + throw new Error("安全校验失败") + } + localStorage.removeItem("wechat_state") + + const result = await mutation.mutateAsync({ code, state }) + const user = await authApi.getCurrentUser() + setAuth(user, result.access_token, result.refresh_token) + + return { ...result, user } + } + + // 绑定成功后跳转 + const finishLogin = () => { + const redirect = localStorage.getItem("login_redirect") || "/" + localStorage.removeItem("login_redirect") + navigate(redirect, { replace: true }) + } + + return { ...mutation, handleCallback, finishLogin, setUser } +} + // 注册 Hook export const useRegister = () => { const navigate = useNavigate() diff --git a/apps/web/src/pages/auth/Login.tsx b/apps/web/src/pages/auth/Login.tsx index 6a4e22abb..1b92d9eca 100644 --- a/apps/web/src/pages/auth/Login.tsx +++ b/apps/web/src/pages/auth/Login.tsx @@ -41,6 +41,13 @@ const Login: React.FC = () => { const result = await getWechatAuthUrl() // 保存 state 到 localStorage 用于回调时验证 localStorage.setItem("wechat_state", result.state) + // 记录登录前的来源页,登录成功后跳回 + const from = window.location.pathname + window.location.search + if (from !== "/login" && from !== "/register") { + localStorage.setItem("login_redirect", from) + } else { + localStorage.removeItem("login_redirect") + } // 跳转到微信授权页 window.location.href = result.auth_url } catch (error) { diff --git a/apps/web/src/pages/auth/WechatCallback.tsx b/apps/web/src/pages/auth/WechatCallback.tsx index 41d46886e..783198149 100644 --- a/apps/web/src/pages/auth/WechatCallback.tsx +++ b/apps/web/src/pages/auth/WechatCallback.tsx @@ -28,15 +28,16 @@ const WechatCallback: React.FC = () => { const handleCallback = async () => { try { - const result = await wechatCallback(code, state) - - // 保存 token - localStorage.setItem("access_token", result.access_token) - if (result.refresh_token) { - localStorage.setItem("refresh_token", result.refresh_token) - } else { - localStorage.removeItem("refresh_token") + // 校验 state,防止 CSRF + const savedState = localStorage.getItem("wechat_state") + if (!savedState || savedState !== state) { + setError("安全校验失败,请重新登录") + setLoading(false) + return } + localStorage.removeItem("wechat_state") + + const result = await wechatCallback(code, state) // 获取用户信息 const userData = await getCurrentUser() @@ -44,9 +45,11 @@ const WechatCallback: React.FC = () => { setAuth(user, result.access_token, result.refresh_token) if (result.binding_complete) { - // 已绑定,直接跳转到首页 + // 已绑定,跳转到登录前页面或首页 message.success("登录成功") - navigate("/app/dashboard") + const redirect = localStorage.getItem("login_redirect") || "/" + localStorage.removeItem("login_redirect") + navigate(redirect, { replace: true }) } else { // 未绑定,显示绑定弹窗 setLoading(false) @@ -61,10 +64,14 @@ const WechatCallback: React.FC = () => { handleCallback() }, [searchParams, navigate, setAuth]) - const handleBindSuccess = (_user: User) => { + const handleBindSuccess = (user: User) => { + const setUser = useAuthStore.getState().setUser + setUser(user) setShowBindModal(false) message.success("绑定成功") - navigate("/app/dashboard") + const redirect = localStorage.getItem("login_redirect") || "/" + localStorage.removeItem("login_redirect") + navigate(redirect, { replace: true }) } const handleBindCancel = () => {