import { describe, expect, it } from "vitest"; import { createOAuthConfiguration, createOAuthHandler } from "../src/oauth"; import type { ConsentDescription, OAuthHelpers } from "@cloudflare/workers-oauth-provider"; import type { OAuthEnv } from "../src/oauth"; import { FixtureCore, fixtureContext, fixtureKv } from "./fixtures"; const origin = "https://mcp.idiot.tax"; const resource = `${origin}/mcp`; const verifier = "fixture-pkce-verifier-abcdefghijklmnopqrstuvwxyz-0123456789"; const redirect = "https://client.idiot.tax/callback"; async function setup() { const core = new FixtureCore(); const env = { OAUTH_KV: fixtureKv() } as OAuthEnv; const handler = createOAuthHandler({ core: core.client, publicOrigin: origin, project: "demo", authorizeOwner: async (request) => request.headers.get("x-fixture-owner") === "verified" ? { subject: "fixture-owner" } : null, }); const fetch = (request: Request) => handler(request, env, fixtureContext()); const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)); const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))) .replaceAll("+", "-") .replaceAll("/", "_") .replace(/=+$/, ""); const registered = await fetch( new Request(`${origin}/oauth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ client_name: "", redirect_uris: [redirect], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: "none", }), }), ); expect(registered.status).toBe(201); const registration = (await registered.json()) as { client_id: string }; const authorization = new URL(`${origin}/authorize`); authorization.search = new URLSearchParams({ client_id: registration.client_id, redirect_uri: redirect, response_type: "code", scope: "mcp:agent", resource, state: "fixture-state", code_challenge: challenge, code_challenge_method: "S256", }).toString(); return { core, fetch, authorization, registration }; } async function consent() { const setupResult = await setup(); const page = await setupResult.fetch( new Request(setupResult.authorization, { headers: { "x-fixture-owner": "verified" } }), ); expect(page.status).toBe(200); const html = await page.text(); const handle = /name="handle" value="([^"]+)"/.exec(html)?.[1]; expect(handle).toBeTruthy(); const cookie = page.headers .getSetCookie() .map((value) => value.split(";")[0]) .join("; "); return { ...setupResult, html, handle: handle!, cookie, page }; } function decision(handle: string, cookie: string, value = "approve", verified = true) { return new Request(`${origin}/authorize`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded", origin, cookie, ...(verified ? { "x-fixture-owner": "verified" } : {}), }, body: new URLSearchParams({ handle, decision: value }), }); } function tokenRequest(client: string, code: string, codeVerifier: string) { return new Request(`${origin}/oauth/token`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", client_id: client, code, redirect_uri: redirect, code_verifier: codeVerifier, resource, }), }); } describe("real OAuth provider with labeled in-memory fixtures", () => { it("serves RFC 9728 and authorization-server discovery with configured URLs and PKCE", async () => { const { fetch } = await setup(); const metadata = await fetch(new Request(`${origin}/.well-known/oauth-protected-resource/mcp`)); expect(metadata.status).toBe(200); expect(await metadata.json()).toMatchObject({ resource, authorization_servers: [origin], bearer_methods_supported: ["header"], }); const as = await fetch(new Request(`${origin}/.well-known/oauth-authorization-server`)); expect(as.status).toBe(200); expect(await as.json()).toMatchObject({ issuer: origin, registration_endpoint: `${origin}/oauth/register`, token_endpoint: `${origin}/oauth/token`, authorization_endpoint: `${origin}/authorize`, code_challenge_methods_supported: ["S256"], }); const unauthorized = await fetch(new Request(resource)); expect(unauthorized.status).toBe(401); expect(unauthorized.headers.get("www-authenticate")).toContain("oauth-protected-resource"); }); it("requires verified owner access before consent or enrollment, and escapes client metadata", async () => { const { fetch, authorization, core } = await setup(); expect((await fetch(new Request(authorization))).status).toBe(403); expect(core.enrollments).toBe(0); const { html, handle, cookie, page } = await consent(); expect(html).not.toContain("