From bd6b2b747d7a21adcf96463737e7637dddc8a6b7 Mon Sep 17 00:00:00 2001 From: wood chen Date: Thu, 20 Feb 2025 02:13:06 +0800 Subject: [PATCH] feat: Add user allowlist and improve client creation validation - Implement user allowlist feature for client applications - Add input validation for client creation form - Handle parsing of allowed users list - Improve error handling and user feedback during client creation - Update authorization process to check client enabled status and user permissions --- src/actions/add-client.ts | 25 +++++- src/actions/authorizing.ts | 93 ++++++++++++---------- src/components/auth/authorization-card.tsx | 42 +++++++++- src/components/clients/add-client.tsx | 25 ++++++ 4 files changed, 137 insertions(+), 48 deletions(-) diff --git a/src/actions/add-client.ts b/src/actions/add-client.ts index 591c0e3..1bbd552 100644 --- a/src/actions/add-client.ts +++ b/src/actions/add-client.ts @@ -10,8 +10,28 @@ export async function AddClientAction(formData: FormData) { const logo = formData.get("logo") as string; const redirectUri = formData.get("redirectUri") as string; const description = formData.get("description") as string; + const allowedUsersStr = formData.get("allowedUsers") as string; const user = await getCurrentUser(); + if (!user?.id) { + return { success: false, error: "未登录" }; + } + + // 验证必填字段 + if (!name || !home || !redirectUri) { + return { success: false, error: "请填写所有必填字段" }; + } + + // 解析允许的用户列表 + let allowedUsers: string[] = []; + if (allowedUsersStr) { + try { + allowedUsers = JSON.parse(allowedUsersStr); + } catch (error) { + console.error("Error parsing allowedUsers:", error); + return { success: false, error: "允许用户列表格式错误" }; + } + } // Generate a unique client ID and secret let clientId = generateRandomKey(); @@ -29,13 +49,14 @@ export async function AddClientAction(formData: FormData) { description, clientId, clientSecret, - userId: user?.id || "", + userId: user.id, + allowedUsers, }); console.log("New client created:", newClient); return { success: true, client: newClient }; } catch (error) { console.error("Error creating client:", error); - return { success: false, error: "Failed to create client" }; + return { success: false, error: "创建应用失败" }; } } diff --git a/src/actions/authorizing.ts b/src/actions/authorizing.ts index 2d280db..02d66e7 100644 --- a/src/actions/authorizing.ts +++ b/src/actions/authorizing.ts @@ -12,53 +12,60 @@ export async function handleAuthorizeAction( clientId: string, scope: string, ) { - // 检查客户端是否限制了允许的用户 - const client = await prisma.client.findUnique({ - where: { id: clientId }, - select: { allowedUsers: true, name: true }, - }); - - if (!client) { - throw new Error("应用不存在"); - } - - // 如果设置了允许的用户列表,检查当前用户是否在列表中 - if (client.allowedUsers.length > 0) { - const user = await prisma.user.findUnique({ - where: { id: userId }, - select: { username: true }, + try { + // 检查客户端是否限制了允许的用户 + const client = await prisma.client.findUnique({ + where: { id: clientId }, + select: { allowedUsers: true, name: true, enabled: true }, }); - if (!user) { - console.error(`用户不存在: ${userId}`); - throw new Error("用户不存在"); + if (!client) { + return { error: "应用不存在" }; } - if (!client.allowedUsers.includes(user.username)) { - console.error( - `用户 ${user.username} 不在应用 ${client.name} 的允许列表中。允许列表: ${client.allowedUsers.join(", ")}`, - ); - throw new Error("您没有权限使用此应用"); + if (!client.enabled) { + return { error: "该应用已被禁用" }; } + + // 如果设置了允许的用户列表,检查当前用户是否在列表中 + if (client.allowedUsers.length > 0) { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { username: true }, + }); + + if (!user) { + return { error: "用户不存在" }; + } + + if (!client.allowedUsers.includes(user.username)) { + return { + error: "您没有权限使用此应用", + }; + } + } + + const oauthParams = new URLSearchParams(atob(oauth)); + const redirectUrl = getAuthorizeUrl(oauthParams); + + // 保存授权 + await createAuthorization({ + userId, + clientId, + scope, + enabled: true, // 新授权默认启用 + lastUsedAt: new Date(), // 首次授权时间作为最后使用时间 + }); + + // 刷新相关页面 + revalidatePath("/dashboard"); + revalidatePath(`/dashboard/clients/${clientId}`); + revalidatePath("/admin/clients"); + revalidatePath(`/admin/clients/${clientId}`); + + return { redirectUrl }; + } catch (error) { + console.error("授权处理失败:", error); + return { error: "授权处理失败,请稍后重试" }; } - - const oauthParams = new URLSearchParams(atob(oauth)); - const redirectUrl = getAuthorizeUrl(oauthParams); - - // 保存授权 - await createAuthorization({ - userId, - clientId, - scope, - enabled: true, // 新授权默认启用 - lastUsedAt: new Date(), // 首次授权时间作为最后使用时间 - }); - - // 刷新相关页面 - revalidatePath("/dashboard"); - revalidatePath(`/dashboard/clients/${clientId}`); - revalidatePath("/admin/clients"); - revalidatePath(`/admin/clients/${clientId}`); - - return redirectUrl; } diff --git a/src/components/auth/authorization-card.tsx b/src/components/auth/authorization-card.tsx index d1f6585..1b2e84a 100644 --- a/src/components/auth/authorization-card.tsx +++ b/src/components/auth/authorization-card.tsx @@ -47,6 +47,7 @@ export function AuthorizationCard({ null, ); const [isAuthorizing, setIsAuthorizing] = useState(false); + const [error, setError] = useState(null); const router = useRouter(); const togglePermission = (id: string) => { @@ -56,16 +57,43 @@ export function AuthorizationCard({ const authorizingHandler = async () => { try { setIsAuthorizing(true); - const url = await handleAuthorizeAction( + setError(null); + const result = await handleAuthorizeAction( oauthParams, client.userId, client.id, permissions[0].id, ); - router.push(url); + + 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("/"); } }; @@ -102,6 +130,13 @@ export function AuthorizationCard({ + {error && ( +
+

+ 错误: {error} +

+
+ )}

请求的权限 @@ -143,6 +178,7 @@ export function AuthorizationCard({

+
+ + +

+ 留空表示允许所有 Q58 + 论坛用户登录。如需限制,请输入用户名列表,用逗号分隔。 +

+