Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f2b2f77b6 | |||
| fb73a69ecd |
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -84,7 +84,7 @@ describe("useAuth hooks", () => {
|
||||
expect(typeof result.current.mutateAsync).toBe("function")
|
||||
})
|
||||
|
||||
it("登录成功时保存 token 并调用 setAuth", async () => {
|
||||
it("登录成功时调用 setAuth 并跳转到登录前页面或首页", async () => {
|
||||
mockMutateAsync.mockResolvedValue({
|
||||
access_token: "access-123",
|
||||
refresh_token: "refresh-456",
|
||||
@@ -96,15 +96,15 @@ describe("useAuth hooks", () => {
|
||||
await result.current.mutateAsync({ username: "test", password: "123" })
|
||||
})
|
||||
|
||||
expect(localStorage.getItem("access_token")).toBe("access-123")
|
||||
expect(localStorage.getItem("refresh_token")).toBe("refresh-456")
|
||||
expect(mockSetAuth).toHaveBeenCalled()
|
||||
expect(mockNavigate).toHaveBeenCalledWith("/")
|
||||
expect(mockNavigate).toHaveBeenCalledWith("/", { replace: true })
|
||||
})
|
||||
|
||||
it("没有 refresh_token 时从 localStorage 移除", async () => {
|
||||
it("登录成功后跳转到 login_redirect 指定的页面", async () => {
|
||||
localStorage.setItem("login_redirect", "/app/templates")
|
||||
mockMutateAsync.mockResolvedValue({
|
||||
access_token: "access-123",
|
||||
refresh_token: "refresh-456",
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useLogin(), { wrapper })
|
||||
@@ -113,8 +113,8 @@ describe("useAuth hooks", () => {
|
||||
await result.current.mutateAsync({ username: "test", password: "123" })
|
||||
})
|
||||
|
||||
expect(localStorage.getItem("access_token")).toBe("access-123")
|
||||
expect(localStorage.getItem("refresh_token")).toBeNull()
|
||||
expect(mockNavigate).toHaveBeenCalledWith("/app/templates", { replace: true })
|
||||
expect(localStorage.getItem("login_redirect")).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user