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:
wood chen 2025-02-20 03:09:46 +08:00
parent 493ad7136f
commit a82643ada4
3 changed files with 121 additions and 93 deletions

View File

@ -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>
);
}

View File

@ -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>
);

View 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>
);
}