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("