Q58Connect/src/components/auth/authorization-card.tsx
wood chen 493ad7136f refactor: Enhance authorization flow with improved permission checks and user context
- Separate platform and application-level authorization checks
- Use current user session for authorization instead of passing user ID
- Add detailed permission validation with specific error messages
- Simplify authorization record creation and update logic
- Remove redundant client and user checks
2025-02-20 03:03:49 +08:00

213 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { handleAuthorizeAction } from "@/actions/authorizing";
import { Client } from "@prisma/client";
import {
ChevronsDownUp,
ChevronsUpDown,
GithubIcon,
Users,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
interface Permission {
id: string;
name: string;
description: string;
}
const permissions: Permission[] = [
{
id: "read_profile",
name: "个人用户数据",
description: "电子邮件地址(只读), 个人资料信息(只读)",
},
];
export function AuthorizationCard({
client,
oauthParams,
}: {
client: Client;
oauthParams: string;
}) {
const [expandedPermission, setExpandedPermission] = useState<string | null>(
null,
);
const [isAuthorizing, setIsAuthorizing] = useState(false);
const [error, setError] = useState<string | null>(null);
const router = useRouter();
const togglePermission = (id: string) => {
setExpandedPermission(expandedPermission === id ? null : id);
};
const authorizingHandler = async () => {
try {
setIsAuthorizing(true);
setError(null);
const result = await handleAuthorizeAction(
oauthParams,
client.id,
permissions[0].id,
);
if (result.error) {
setError(result.error);
setIsAuthorizing(false);
return;
}
if (result.redirectUrl) {
const url = await Promise.resolve(result.redirectUrl);
router.push(url);
}
} catch (error) {
setError("授权处理失败,请稍后重试");
setIsAuthorizing(false);
}
};
const handleCancel = () => {
// 从 URL 中获取 redirect_uri 参数
const params = new URLSearchParams(atob(oauthParams));
const redirectUri = params.get("redirect_uri");
if (redirectUri) {
// 添加错误参数返回
const redirectUrl = new URL(redirectUri);
redirectUrl.searchParams.set("error", "access_denied");
redirectUrl.searchParams.set("error_description", "用户取消了授权请求");
router.push(redirectUrl.toString());
} else {
// 如果没有 redirect_uri返回首页
router.push("/");
}
};
return (
<Card className="w-full max-w-2xl transform transition-all duration-300 hover:shadow-lg">
<CardHeader className="space-y-4 text-center">
<div className="flex items-center justify-center space-x-6">
<div className="group relative">
<div className="absolute -inset-0.5 rounded-full bg-gradient-to-r from-pink-600 to-purple-600 opacity-50 blur transition duration-300 group-hover:opacity-75"></div>
<div className="relative flex h-20 w-20 items-center justify-center rounded-full bg-white">
{client.logo ? (
<Image
src={client.logo}
alt={client.name}
width={64}
height={64}
unoptimized
className="rounded-full object-cover"
/>
) : (
<span className="bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-4xl font-bold text-transparent">
{client.name[0].toUpperCase()}
</span>
)}
</div>
</div>
</div>
<CardTitle className="bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-3xl font-bold text-transparent">
{client.name}
</CardTitle>
<p className="text-sm text-gray-500">
访Q58论坛账号
</p>
</CardHeader>
<CardContent className="space-y-4">
{error && (
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 p-4 text-red-600">
<p className="text-sm">
<strong></strong> {error}
</p>
</div>
)}
<div className="space-y-3 rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
</h3>
{permissions.map((permission) => (
<div
key={permission.id}
className="rounded-md border border-gray-200 bg-white p-4 shadow-sm transition-all duration-200 hover:border-purple-200 dark:border-gray-700 dark:bg-gray-900"
onClick={() => togglePermission(permission.id)}
>
<div className="flex cursor-pointer items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox id={permission.id} checked disabled />
<label
htmlFor={permission.id}
className="font-medium text-gray-900 dark:text-gray-100"
>
{permission.name}
</label>
</div>
{expandedPermission === permission.id ? (
<ChevronsDownUp className="h-4 w-4 text-gray-500" />
) : (
<ChevronsUpDown className="h-4 w-4 text-gray-500" />
)}
</div>
{expandedPermission === permission.id && (
<div className="mt-2 pl-6 text-sm text-gray-600 dark:text-gray-400">
{permission.description}
</div>
)}
</div>
))}
</div>
</CardContent>
<CardFooter className="flex flex-col items-center space-y-4">
<div className="flex space-x-4">
<Button
variant="outline"
className="min-w-[100px] transition-all duration-200 hover:bg-gray-100"
onClick={handleCancel}
disabled={isAuthorizing}
>
</Button>
<Button
className="min-w-[100px] bg-gradient-to-r from-pink-600 to-purple-600 text-white transition-all duration-200 hover:from-pink-700 hover:to-purple-700"
onClick={authorizingHandler}
disabled={isAuthorizing}
>
{isAuthorizing ? (
<div className="flex items-center">
<div className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent"></div>
...
</div>
) : (
`授权 ${client.name}`
)}
</Button>
</div>
<p className="text-sm text-gray-500">
{client.redirectUri}
</p>
<div className="flex justify-center space-x-8 text-sm text-gray-500">
<div className="flex items-center">
<Users className="mr-1 h-4 w-4" />
<span>Q58论坛运营</span>
</div>
</div>
</CardFooter>
</Card>
);
}