mirror of
https://github.com/woodchen-ink/Q58Connect.git
synced 2025-07-18 14:01: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 { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { getClientByClientId } from "@/lib/dto/client";
|
import { getClientByClientId } from "@/lib/dto/client";
|
||||||
|
import { getCurrentUser } from "@/lib/session";
|
||||||
import { Authorizing } from "@/components/auth/authorizing";
|
import { Authorizing } from "@/components/auth/authorizing";
|
||||||
|
|
||||||
export interface AuthorizeParams extends Record<string, string> {
|
export interface AuthorizeParams {
|
||||||
|
oauth: string;
|
||||||
|
clientId: string;
|
||||||
scope: string;
|
scope: string;
|
||||||
response_type: string;
|
redirectUri: string;
|
||||||
client_id: string;
|
|
||||||
redirect_uri: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function AuthorizePage({
|
export default async function AuthorizePage({
|
||||||
@ -15,51 +16,23 @@ export default async function AuthorizePage({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: AuthorizeParams;
|
searchParams: AuthorizeParams;
|
||||||
}) {
|
}) {
|
||||||
// 检查必要的参数
|
const user = await getCurrentUser();
|
||||||
if (
|
if (!user?.id) {
|
||||||
!searchParams.response_type ||
|
redirect("/login");
|
||||||
!searchParams.client_id ||
|
|
||||||
!searchParams.redirect_uri
|
|
||||||
) {
|
|
||||||
const errorParams = new URLSearchParams({
|
|
||||||
error: "invalid_request",
|
|
||||||
error_description: "缺少必要的参数",
|
|
||||||
});
|
|
||||||
redirect(`${searchParams.redirect_uri}?${errorParams.toString()}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await getClientByClientId(searchParams.client_id);
|
const client = await getClientByClientId(searchParams.clientId);
|
||||||
|
|
||||||
// 检查应用是否存在
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
const errorParams = new URLSearchParams({
|
return (
|
||||||
error: "invalid_client",
|
<div className="flex min-h-screen items-center justify-center p-4">
|
||||||
error_description: "应用不存在",
|
<div className="text-center text-red-600">应用不存在</div>
|
||||||
});
|
</div>
|
||||||
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 (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
|
<div className="flex min-h-screen items-center justify-center p-4">
|
||||||
<Authorizing searchParams={searchParams} />
|
<Authorizing {...searchParams} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,74 +2,61 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
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 {
|
interface AuthorizingProps {
|
||||||
searchParams: AuthorizeParams;
|
oauth: string;
|
||||||
|
clientId: string;
|
||||||
|
scope: string;
|
||||||
|
redirectUri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Authorizing({ searchParams }: AuthorizingProps) {
|
export function Authorizing({
|
||||||
const router = useRouter();
|
oauth,
|
||||||
const [error, setError] = useState<unknown | null>(null);
|
clientId,
|
||||||
|
scope,
|
||||||
|
redirectUri,
|
||||||
|
}: AuthorizingProps) {
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 立即开始授权
|
const authorize = async () => {
|
||||||
const doAuth = async () => {
|
const result = await handleAuthorizeAction(oauth, clientId, scope);
|
||||||
try {
|
if (result.error) {
|
||||||
const url = await getDiscourseSSOUrl(
|
setError(result.error);
|
||||||
new URLSearchParams(searchParams).toString(),
|
} else if (result.redirectUrl) {
|
||||||
);
|
const url = await result.redirectUrl;
|
||||||
router.push(url);
|
window.location.href = url;
|
||||||
} catch (error) {
|
|
||||||
setError(error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
doAuth();
|
authorize().catch((err) => {
|
||||||
}, [searchParams, router]);
|
console.error("授权过程出错:", err);
|
||||||
|
setError("授权过程发生错误,请稍后重试");
|
||||||
|
});
|
||||||
|
}, [oauth, clientId, scope]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
|
<div className="flex min-h-screen items-center justify-center p-4">
|
||||||
<div className="space-y-4 text-center">
|
<ErrorCard
|
||||||
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-red-100">
|
title="授权失败"
|
||||||
<svg
|
description={error}
|
||||||
className="h-8 w-8 text-red-600"
|
redirectUri={redirectUri}
|
||||||
fill="none"
|
error="access_denied"
|
||||||
stroke="currentColor"
|
errorDescription={error}
|
||||||
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
|
<div className="flex min-h-screen items-center justify-center">
|
||||||
<div className="space-y-4 text-center">
|
<div className="text-center">
|
||||||
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-blue-100">
|
<div className="mb-4 text-2xl font-semibold">正在处理授权...</div>
|
||||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent"></div>
|
<div className="text-gray-500">请稍候,我们正在处理您的授权请求</div>
|
||||||
</div>
|
|
||||||
<h3 className="text-xl font-semibold text-gray-900">正在授权</h3>
|
|
||||||
<p className="text-gray-500">请稍候...</p>
|
|
||||||
</div>
|
</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