feat: Improve SSO authentication with enhanced error handling and dynamic return URL

This commit is contained in:
wood chen 2025-02-21 20:51:44 +08:00
parent 1edfc035e2
commit 7188c46cd7
3 changed files with 71 additions and 35 deletions

View File

@ -113,6 +113,14 @@ headers: {
"groups": ["group1", "group2"] "groups": ["group1", "group2"]
} }
## 添加新功能的准则
1. 尽量简单, 尽量少修改代码
2. 不能影响已有的功能
3. 尽量不新增文件
4. 尽量使用已有的组件和函数
5. 前端页面要尽量使用shadcn的组件
## 许可证 ## 许可证
本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。 本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。

View File

@ -9,14 +9,36 @@ const hostUrl = process.env.NEXT_PUBLIC_HOST_URL as string;
const discourseHost = process.env.DISCOURSE_HOST as string; const discourseHost = process.env.DISCOURSE_HOST as string;
const clientSecret = process.env.DISCOURSE_SECRET as string; const clientSecret = process.env.DISCOURSE_SECRET as string;
export async function POST(_req: Request) { export async function POST(req: Request) {
const nonce = WordArray.random(16).toString(); try {
const return_url = `${hostUrl}/authorize`; const nonce = WordArray.random(16).toString();
const sso = btoa(`nonce=${nonce}&return_sso_url=${return_url}`); let return_url = `${hostUrl}/authorize`;
const sig = hmacSHA256(sso, clientSecret).toString(Hex);
cookies().set(AUTH_NONCE, nonce, { maxAge: 60 * 10 }); // 尝试从请求中获取 return_url
return Response.json({ try {
sso_url: `${discourseHost}/session/sso_provider?sso=${sso}&sig=${sig}`, const body = await req.json();
}); if (body.return_url) {
return_url = body.return_url;
}
} catch (error) {
console.error("Failed to parse request body:", error);
}
const sso = btoa(`nonce=${nonce}&return_sso_url=${return_url}`);
const sig = hmacSHA256(sso, clientSecret).toString(Hex);
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("SSO 处理错误:", error);
return Response.json({ error: "处理登录请求时发生错误" }, { status: 500 });
}
} }

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import { useRouter } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { Loader2, MessageCircleCode } from "lucide-react"; import { Loader2, MessageCircleCode } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -19,33 +19,39 @@ export function UserAuthForm({
const [isLoading, setIsLoading] = React.useState<boolean>(false); const [isLoading, setIsLoading] = React.useState<boolean>(false);
const router = useRouter(); const router = useRouter();
const { toast } = useToast(); const { toast } = useToast();
const searchParams = useSearchParams();
const signIn = () => { async function signIn() {
React.startTransition(async () => { try {
try { const body: Record<string, any> = {};
const response = await fetch("/api/auth/q58", { const callbackUrl = searchParams?.get("callbackUrl");
method: "POST", if (callbackUrl) {
headers: { body.return_url = callbackUrl;
"Content-Type": "application/json",
},
});
if (!response.ok || response.status !== 200) {
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 : "登录请求失败",
});
} }
});
}; const response = await fetch("/api/auth/q58", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error("登录请求失败");
}
const data = await response.json();
window.location.href = data.sso_url;
} catch (error) {
console.error("登录错误:", error);
toast({
title: "错误",
description: "登录过程中发生错误,请稍后重试",
variant: "destructive",
});
}
}
return ( return (
<div className={cn("grid gap-3", className)} {...props}> <div className={cn("grid gap-3", className)} {...props}>