From 99e833c84ef1693450295c86cc6f3485bed91676 Mon Sep 17 00:00:00 2001 From: wood chen Date: Fri, 21 Feb 2025 18:05:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20SSO=20?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=B5=81=E7=A8=8B=EF=BC=8C=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E5=92=8C=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/auth/q58/route.ts | 56 +++++++++------- src/components/auth/user-auth-form.tsx | 93 ++++++++++++-------------- 2 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/app/api/auth/q58/route.ts b/src/app/api/auth/q58/route.ts index 93b6303..18a4de0 100644 --- a/src/app/api/auth/q58/route.ts +++ b/src/app/api/auth/q58/route.ts @@ -10,11 +10,11 @@ const discourseHost = process.env.DISCOURSE_HOST as string; const clientSecret = process.env.DISCOURSE_SECRET as string; export async function POST(req: Request) { - const nonce = WordArray.random(16).toString(); - - // 尝试从请求体中获取 OAuth 参数 - let oauthParams = ""; try { + const nonce = WordArray.random(16).toString(); + + // 尝试从请求体中获取 OAuth 参数 + let oauthParams = ""; const body = await req.json(); if (body.oauth_params) { oauthParams = body.oauth_params; @@ -22,27 +22,37 @@ export async function POST(req: Request) { cookies().set("oauth_params", oauthParams, { maxAge: 60 * 10, // 10分钟过期 path: "/", + httpOnly: true, + secure: process.env.NODE_ENV === "production", }); } + + // 设置回调地址 + const return_url = oauthParams + ? `${hostUrl}/authorize?${oauthParams}` // OAuth流程:回到授权页面 + : `${hostUrl}/dashboard`; // 普通登录:直接到仪表板 + + // 构建 SSO 参数 + const ssoParams = new URLSearchParams(); + ssoParams.set("nonce", nonce); + ssoParams.set("return_sso_url", return_url); + + const sso = btoa(ssoParams.toString()); + const sig = hmacSHA256(sso, clientSecret).toString(Hex); + + // 保存 nonce 到 cookie + cookies().set(AUTH_NONCE, nonce, { + maxAge: 60 * 10, + path: "/", + httpOnly: true, + secure: process.env.NODE_ENV === "production", + }); + + return Response.json({ + sso_url: `${discourseHost}/session/sso_provider?sso=${sso}&sig=${sig}`, + }); } catch (error) { - console.error("Failed to parse request body:", error); + console.error("SSO 处理错误:", error); + return Response.json({ error: "处理登录请求时发生错误" }, { status: 500 }); } - - // 设置回调地址 - const return_url = oauthParams - ? `${hostUrl}/authorize?${oauthParams}` // OAuth流程:回到授权页面 - : `${hostUrl}/dashboard`; // 普通登录:直接到仪表板 - - // 构建 SSO 参数 - const ssoParams = new URLSearchParams(); - ssoParams.set("nonce", nonce); - ssoParams.set("return_sso_url", return_url); - - const sso = btoa(ssoParams.toString()); - const sig = hmacSHA256(sso, clientSecret).toString(Hex); - - cookies().set(AUTH_NONCE, nonce, { maxAge: 60 * 10 }); - return Response.json({ - sso_url: `${discourseHost}/session/sso_provider?sso=${sso}&sig=${sig}`, - }); } diff --git a/src/components/auth/user-auth-form.tsx b/src/components/auth/user-auth-form.tsx index 20b0342..61c19c1 100644 --- a/src/components/auth/user-auth-form.tsx +++ b/src/components/auth/user-auth-form.tsx @@ -21,55 +21,51 @@ export function UserAuthForm({ const { toast } = useToast(); const searchParams = useSearchParams(); - // 在组件挂载时保存 OAuth 参数 - React.useEffect(() => { - if (searchParams?.toString()) { - localStorage.setItem("oauth_params", searchParams.toString()); - } - }, [searchParams]); + const signIn = async () => { + if (isLoading) return; - const signIn = () => { - React.startTransition(async () => { - try { - setIsLoading(true); - // 构建请求体,包含 OAuth 参数 - const body: Record = {}; - // 优先使用 URL 中的参数 - if (searchParams?.toString()) { - body.oauth_params = searchParams.toString(); - } else { - // 如果 URL 中没有参数,尝试从 localStorage 获取 - const savedParams = localStorage.getItem("oauth_params"); - if (savedParams) { - body.oauth_params = savedParams; - // 使用后清除存储的参数 - localStorage.removeItem("oauth_params"); - } - } + setIsLoading(true); + try { + // 构建请求体,包含 OAuth 参数 + const body: Record = {}; + const currentParams = searchParams?.toString(); - const response = await fetch("/api/auth/q58", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - throw new Error(response.statusText); - } - - const data: DiscourseData = await response.json(); - router.push(data.sso_url); - } catch (error) { - setIsLoading(false); - toast({ - variant: "destructive", - title: "内部服务异常", - description: error instanceof Error ? error.message : "未知错误", - }); + if (currentParams) { + body.oauth_params = currentParams; } - }); + + const response = await fetch("/api/auth/q58", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(errorText || response.statusText); + } + + const data: DiscourseData = await response.json(); + + // 在跳转之前确保保存当前的 OAuth 参数 + if (currentParams) { + localStorage.setItem("oauth_params", currentParams); + } + + // 跳转到 SSO 登录页面 + window.location.href = data.sso_url; + } catch (error) { + console.error("登录失败:", error); + setIsLoading(false); + toast({ + variant: "destructive", + title: "登录失败", + description: + error instanceof Error ? error.message : "请求失败,请稍后重试", + }); + } }; return ( @@ -77,10 +73,7 @@ export function UserAuthForm({