mirror of
https://github.com/woodchen-ink/Q58Connect.git
synced 2025-07-18 05:51:55 +08:00
refactor: Simplify OAuth authorization page and component
- Restructure AuthorizePage to handle user authentication and client validation - Modify Authorizing component to use new authorization action - Improve error handling with dedicated ErrorCard component - Streamline authorization flow and parameter handling - Remove redundant validation checks in favor of centralized authorization logic
This commit is contained in:
parent
493ad7136f
commit
a82643ada4
@ -1,13 +1,14 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getClientByClientId } from "@/lib/dto/client";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { Authorizing } from "@/components/auth/authorizing";
|
||||
|
||||
export interface AuthorizeParams extends Record<string, string> {
|
||||
export interface AuthorizeParams {
|
||||
oauth: string;
|
||||
clientId: string;
|
||||
scope: string;
|
||||
response_type: string;
|
||||
client_id: string;
|
||||
redirect_uri: string;
|
||||
redirectUri: string;
|
||||
}
|
||||
|
||||
export default async function AuthorizePage({
|
||||
@ -15,51 +16,23 @@ export default async function AuthorizePage({
|
||||
}: {
|
||||
searchParams: AuthorizeParams;
|
||||
}) {
|
||||
// 检查必要的参数
|
||||
if (
|
||||
!searchParams.response_type ||
|
||||
!searchParams.client_id ||
|
||||
!searchParams.redirect_uri
|
||||
) {
|
||||
const errorParams = new URLSearchParams({
|
||||
error: "invalid_request",
|
||||
error_description: "缺少必要的参数",
|
||||
});
|
||||
redirect(`${searchParams.redirect_uri}?${errorParams.toString()}`);
|
||||
const user = await getCurrentUser();
|
||||
if (!user?.id) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const client = await getClientByClientId(searchParams.client_id);
|
||||
|
||||
// 检查应用是否存在
|
||||
const client = await getClientByClientId(searchParams.clientId);
|
||||
if (!client) {
|
||||
const errorParams = new URLSearchParams({
|
||||
error: "invalid_client",
|
||||
error_description: "应用不存在",
|
||||
});
|
||||
redirect(`${searchParams.redirect_uri}?${errorParams.toString()}`);
|
||||
}
|
||||
|
||||
// 检查回调地址是否匹配
|
||||
if (client.redirectUri !== searchParams.redirect_uri) {
|
||||
const errorParams = new URLSearchParams({
|
||||
error: "invalid_request",
|
||||
error_description: "回调地址不匹配",
|
||||
});
|
||||
redirect(`${searchParams.redirect_uri}?${errorParams.toString()}`);
|
||||
}
|
||||
|
||||
// 检查应用是否被禁用
|
||||
if (!client.enabled) {
|
||||
const errorParams = new URLSearchParams({
|
||||
error: "access_denied",
|
||||
error_description: "此应用已被禁用",
|
||||
});
|
||||
redirect(`${searchParams.redirect_uri}?${errorParams.toString()}`);
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<div className="text-center text-red-600">应用不存在</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
|
||||
<Authorizing searchParams={searchParams} />
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<Authorizing {...searchParams} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,74 +2,61 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getDiscourseSSOUrl } from "@/actions/discourse-sso-url";
|
||||
import { handleAuthorizeAction } from "@/actions/authorizing";
|
||||
|
||||
import type { AuthorizeParams } from "@/app/(oauth)/oauth/authorize/page";
|
||||
import { ErrorCard } from "@/components/auth/error-card";
|
||||
|
||||
interface AuthorizingProps {
|
||||
searchParams: AuthorizeParams;
|
||||
oauth: string;
|
||||
clientId: string;
|
||||
scope: string;
|
||||
redirectUri: string;
|
||||
}
|
||||
|
||||
export function Authorizing({ searchParams }: AuthorizingProps) {
|
||||
const router = useRouter();
|
||||
const [error, setError] = useState<unknown | null>(null);
|
||||
export function Authorizing({
|
||||
oauth,
|
||||
clientId,
|
||||
scope,
|
||||
redirectUri,
|
||||
}: AuthorizingProps) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// 立即开始授权
|
||||
const doAuth = async () => {
|
||||
try {
|
||||
const url = await getDiscourseSSOUrl(
|
||||
new URLSearchParams(searchParams).toString(),
|
||||
);
|
||||
router.push(url);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
const authorize = async () => {
|
||||
const result = await handleAuthorizeAction(oauth, clientId, scope);
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
} else if (result.redirectUrl) {
|
||||
const url = await result.redirectUrl;
|
||||
window.location.href = url;
|
||||
}
|
||||
};
|
||||
|
||||
doAuth();
|
||||
}, [searchParams, router]);
|
||||
authorize().catch((err) => {
|
||||
console.error("授权过程出错:", err);
|
||||
setError("授权过程发生错误,请稍后重试");
|
||||
});
|
||||
}, [oauth, clientId, scope]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
|
||||
<div className="space-y-4 text-center">
|
||||
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-red-100">
|
||||
<svg
|
||||
className="h-8 w-8 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900">授权异常</h3>
|
||||
<p className="text-gray-500">登录失败,请稍后重试!</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-4 rounded-md bg-red-600 px-4 py-2 text-white transition-colors hover:bg-red-700"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<ErrorCard
|
||||
title="授权失败"
|
||||
description={error}
|
||||
redirectUri={redirectUri}
|
||||
error="access_denied"
|
||||
errorDescription={error}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
|
||||
<div className="space-y-4 text-center">
|
||||
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-blue-100">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent"></div>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900">正在授权</h3>
|
||||
<p className="text-gray-500">请稍候...</p>
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 text-2xl font-semibold">正在处理授权...</div>
|
||||
<div className="text-gray-500">请稍候,我们正在处理您的授权请求</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
68
src/components/auth/error-card.tsx
Normal file
68
src/components/auth/error-card.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
|
||||
interface ErrorCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
redirectUri?: string;
|
||||
error?: string;
|
||||
errorDescription?: string;
|
||||
}
|
||||
|
||||
export function ErrorCard({
|
||||
title,
|
||||
description,
|
||||
redirectUri,
|
||||
error,
|
||||
errorDescription,
|
||||
}: ErrorCardProps) {
|
||||
const router = useRouter();
|
||||
|
||||
const handleBack = () => {
|
||||
if (redirectUri) {
|
||||
const url = new URL(redirectUri);
|
||||
if (error) url.searchParams.set("error", error);
|
||||
if (errorDescription)
|
||||
url.searchParams.set("error_description", errorDescription);
|
||||
router.push(url.toString());
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="space-y-4 text-center">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-red-50">
|
||||
<AlertCircle className="h-10 w-10 text-red-600" />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl font-bold text-red-600">
|
||||
{title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-center text-gray-500">{description}</p>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="min-w-[120px] border-red-200 text-red-600 hover:bg-red-50 hover:text-red-700"
|
||||
onClick={handleBack}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user