diff --git a/src/app/(oauth)/oauth/authorize/page.tsx b/src/app/(oauth)/oauth/authorize/page.tsx index 700ef60..c10b717 100644 --- a/src/app/(oauth)/oauth/authorize/page.tsx +++ b/src/app/(oauth)/oauth/authorize/page.tsx @@ -1,3 +1,5 @@ +import { redirect } from "next/navigation"; + import { getClientByClientId } from "@/lib/dto/client"; import { Authorizing } from "@/components/auth/authorizing"; @@ -8,30 +10,56 @@ export interface AuthorizeParams extends Record { redirect_uri: string; } -export default async function OAuthAuthorization({ +export default async function AuthorizePage({ searchParams, }: { searchParams: AuthorizeParams; }) { - // params invalid + // 检查必要的参数 if ( !searchParams.response_type || !searchParams.client_id || !searchParams.redirect_uri ) { - throw new Error("Params invalid"); + const errorParams = new URLSearchParams({ + error: "invalid_request", + error_description: "缺少必要的参数", + }); + redirect(`${searchParams.redirect_uri}?${errorParams.toString()}`); } - // client invalid const client = await getClientByClientId(searchParams.client_id); - if (!client || client.redirectUri !== searchParams.redirect_uri) { - throw new Error("Client not found"); + + // 检查应用是否存在 + 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()}`); } - // Authorizing ... return (
- +
); } diff --git a/src/app/(oauth)/q58/callback/page.tsx b/src/app/(oauth)/q58/callback/page.tsx index 39de7aa..5865fdb 100644 --- a/src/app/(oauth)/q58/callback/page.tsx +++ b/src/app/(oauth)/q58/callback/page.tsx @@ -27,6 +27,15 @@ export default async function Q58CallbackPage({ throw new Error("Client Id invalid (code: -1004)."); } + // 检查应用是否被禁用 + if (!client.enabled) { + const redirectUri = client.redirectUri; + const redirectUrl = new URL(redirectUri); + redirectUrl.searchParams.set("error", "access_denied"); + redirectUrl.searchParams.set("error_description", "该应用已被禁用"); + return redirect(redirectUrl.toString()); + } + // verify q58 callback const user = await q58CallbackVerify(searchParams.sso, searchParams.sig); @@ -34,6 +43,14 @@ export default async function Q58CallbackPage({ const authorization = await findAuthorization(user.id, client.id); if (authorization) { + // 如果授权被禁用,也返回错误 + if (!authorization.enabled) { + const redirectUrl = new URL(client.redirectUri); + redirectUrl.searchParams.set("error", "access_denied"); + redirectUrl.searchParams.set("error_description", "您的授权已被禁用"); + return redirect(redirectUrl.toString()); + } + const redirectUrl = await getAuthorizeUrl(oauthParams); return redirect(redirectUrl); } diff --git a/src/components/auth/authorizing.tsx b/src/components/auth/authorizing.tsx index a37f6b9..4312ee9 100644 --- a/src/components/auth/authorizing.tsx +++ b/src/components/auth/authorizing.tsx @@ -1,38 +1,44 @@ "use client"; import { useCallback, useEffect, useState } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { getDiscourseSSOUrl } from "@/actions/discourse-sso-url"; -export function Authorizing() { +import type { AuthorizeParams } from "@/app/(oauth)/oauth/authorize/page"; + +interface AuthorizingProps { + searchParams: AuthorizeParams; +} + +export function Authorizing({ searchParams }: AuthorizingProps) { const router = useRouter(); - const searchParams = useSearchParams(); const [error, setError] = useState(null); const [progress, setProgress] = useState(0); const signInCallback = useCallback(async () => { try { - const url = await getDiscourseSSOUrl(searchParams.toString()); + const url = await getDiscourseSSOUrl( + new URLSearchParams(searchParams).toString(), + ); router.push(url); } catch (error) { setError(error); } - }, []); + }, [searchParams, router]); useEffect(() => { - // 模拟进度条 - const progressInterval = setInterval(() => { - setProgress((prev) => (prev >= 90 ? 90 : prev + 10)); - }, 300); + // 启动进度条动画 + const progressTimer = setInterval(() => { + setProgress((prev) => Math.min(prev + 10, 90)); + }, 500); - // Delay 3s get sso url go to ... - const timer = setTimeout(signInCallback, 3000); + // 立即开始授权 + signInCallback(); return () => { - clearTimeout(timer); - clearInterval(progressInterval); + clearInterval(progressTimer); }; - }, []); + }, [signInCallback]); return (
diff --git a/src/lib/dto/client.ts b/src/lib/dto/client.ts index 1900a2b..6cc4b20 100644 --- a/src/lib/dto/client.ts +++ b/src/lib/dto/client.ts @@ -3,7 +3,24 @@ import { Prisma as PrismaType } from "@prisma/client"; import { prisma } from "@/lib/prisma"; export async function getClientByClientId(clientId: string) { - return prisma.client.findUnique({ where: { clientId } }); + return prisma.client.findUnique({ + where: { clientId }, + select: { + id: true, + name: true, + clientId: true, + clientSecret: true, + redirectUri: true, + home: true, + logo: true, + description: true, + enabled: true, + allowedUsers: true, + userId: true, + createdAt: true, + updatedAt: true, + }, + }); } export async function createClient( @@ -31,5 +48,20 @@ export async function getClientById(id: string) { where: { id, }, + select: { + id: true, + name: true, + clientId: true, + clientSecret: true, + redirectUri: true, + home: true, + logo: true, + description: true, + enabled: true, + allowedUsers: true, + userId: true, + createdAt: true, + updatedAt: true, + }, }); }