import { redirect } from "next/navigation" import { auth } from "@/lib/auth" import { prisma } from "@/lib/prisma" import { Beer, ShieldCheck } from "lucide-react" import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card" import { ConsentForm } from "@/components/oauth/consent-form" import { buildRedirect, validateAuthorizeRequest, } from "@/lib/mcp/authorize-request" import { AUTH_CODE_TTL_MS, SCOPE_DESCRIPTIONS, coversScopes, generateAuthCode, } from "@/lib/mcp/oauth" /** * The consent screen. * * Not in PUBLIC_ROUTES, so the middleware bounces an unauthenticated visitor to * /login for us - and now that login honours ?callbackUrl they come straight * back here afterwards. The explicit session check below is for the callbackUrl * construction, not for access control. * * Deliberately outside the (app) route group: this is a credential prompt, and * it should not be wrapped in the sidebar and bottom nav of the signed-in app. */ export const dynamic = "force-dynamic" type SearchParams = Record function toParams(searchParams: SearchParams): URLSearchParams { const params = new URLSearchParams() for (const [key, value] of Object.entries(searchParams)) { if (typeof value === "string") params.set(key, value) else if (Array.isArray(value) && value[0]) params.set(key, value[0]) } return params } export default async function AuthorizePage({ searchParams, }: { searchParams: SearchParams }) { const search = toParams(searchParams) const validation = await validateAuthorizeRequest(search) if (validation.kind === "fatal") { return } if (validation.kind === "redirectable") { redirect( buildRedirect(validation.redirectUri, { error: validation.error, error_description: validation.description, state: validation.state, }) ) } const { params, client } = validation const session = await auth() if (!session?.user?.id) { redirect(`/login?callbackUrl=${encodeURIComponent(`/oauth/authorize?${search}`)}`) } // A session already implies an ACTIVE user - the jwt callback in lib/auth.ts // returns null for anyone suspended or deleted, which clears the cookie. const userId = session.user.id // Already approved for these scopes? Skip the screen. This is what makes a // re-connection after an expired refresh token silent rather than another // round of clicking Allow. const existing = await prisma.oAuthGrant.findUnique({ where: { clientId_userId: { clientId: client.id, userId } }, select: { id: true, scopes: true, revokedAt: true }, }) if (existing && !existing.revokedAt && coversScopes(existing.scopes, params.scopes)) { const code = await createAuthCode(client.id, userId, params) redirect(buildRedirect(params.redirectUri, { code, state: params.state })) } return (
Connect {client.clientName} to DrinkTracker? Signed in as {session.user.email ?? session.user.name}

It will be able to:

    {params.scopes.map((scope) => (
  • {SCOPE_DESCRIPTIONS[scope] ?? scope}
  • ))}
{client.clientUri && (

{client.clientUri}

)}

It will be sent back to {params.redirectUri}. You can disconnect it any time from Settings.

) } async function createAuthCode( clientRowId: string, userId: string, params: { redirectUri: string scopes: string[] codeChallenge: string resource: string | null } ): Promise { const { code, hash } = generateAuthCode() await prisma.oAuthAuthCode.create({ data: { codeHash: hash, clientId: clientRowId, userId, redirectUri: params.redirectUri, resource: params.resource, scopes: params.scopes, codeChallenge: params.codeChallenge, codeChallengeMethod: "S256", expiresAt: new Date(Date.now() + AUTH_CODE_TTL_MS), }, }) return code } function ErrorCard({ title, detail }: { title: string; detail: string }) { return (
{title} {detail}

Nothing was connected. Try starting the connection again from the app you were using.

) }