import { mkdtempSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path"; import { afterAll, expect, test } from "@effect/vitest"; import { mintInviteCode } from "../testing/mint-invite"; process.env.EXECUTOR_DATA_DIR = mkdtempSync(join(tmpdir(), "eh-env-"));process.env.BETTER_AUTH_SECRET = "env-test-secret-0123456789-abcdefghij-klmnop";process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL = "admin@env.test";process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD = "admin-pass-123456"; const { makeSelfHostApiHandler } = await import("../app");const { handler, dispose } = await makeSelfHostApiHandler();afterAll(() => dispose()); const BASE = "http://localhost:4788"; test("serves OAuth Authorization Server metadata at the origin root", async () => { const res = await handler(new Request(`${BASE}/.well-known/oauth-authorization-server`)); expect(res.status).toBe(200); const body = (await res.json()) as Record<string, unknown>; expect(body.issuer).toBeDefined(); // mcp() advertises its DCR + authorize + token endpoints under /api/auth/mcp. expect(String(body.authorization_endpoint)).toContain("/api/auth/mcp/authorize"); expect(String(body.token_endpoint)).toContain("/api/auth/mcp/token"); expect(String(body.registration_endpoint)).toContain("/api/auth/mcp/register");}); test("serves OAuth Protected Resource metadata at the origin root", async () => { const res = await handler(new Request(`${BASE}/.well-known/oauth-protected-resource`)); expect(res.status).toBe(200); const body = (await res.json()) as Record<string, unknown>; expect(body.resource).toBeDefined(); expect(Array.isArray(body.authorization_servers)).toBe(true);}); test("serves OAuth Protected Resource metadata for a toolkit MCP resource", async () => { const res = await handler( new Request(`${BASE}/.well-known/oauth-protected-resource/mcp/toolkits/deploy`), ); expect(res.status).toBe(200); const body = (await res.json()) as Record<string, unknown>; expect(body.resource).toBe(`${BASE}/mcp/toolkits/deploy`); expect(Array.isArray(body.authorization_servers)).toBe(true);}); test("an unauthenticated /mcp request returns 401 with a WWW-Authenticate challenge", async () => { const res = await handler( new Request(`${BASE}/mcp`, { method: "POST", headers: { "content-type": "application/json", accept: "application/json, text/event-stream", }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }), }), ); expect(res.status).toBe(401); const challenge = res.headers.get("www-authenticate") ?? ""; expect(challenge).toContain("Bearer"); expect(challenge).toContain("resource_metadata=");}); test("an unauthenticated toolkit MCP request challenges with toolkit metadata", async () => { const res = await handler( new Request(`${BASE}/mcp/toolkits/deploy`, { method: "POST", headers: { "content-type": "application/json", accept: "application/json, text/event-stream", }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }), }), ); expect(res.status).toBe(401); const challenge = res.headers.get("www-authenticate") ?? ""; expect(challenge).toContain("Bearer"); expect(challenge).toContain( `resource_metadata="${BASE}/.well-known/oauth-protected-resource/mcp/toolkits/deploy"`, );}); // --- End-to-end MCP OAuth: DCR -> authorize -> token -> /mcp with bearer ---const json = async (res: Response) => (await res.json()) as Record<string, unknown>; const signUp = async (email: string): Promise<string> => { const inviteCode = await mintInviteCode(handler); const res = await handler( new Request(`${BASE}/api/auth/sign-up/email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password: "password-12345678", name: email, inviteCode }), }), ); expect(res.status).toBe(200); // The session cookie lets /mcp/authorize skip the interactive login. return res.headers.get("set-cookie") ?? "";}; const b64url = (buf: Uint8Array): string => btoa(String.fromCharCode(...buf)) .replaceAll("+", "-") .replaceAll("/", "_") .replaceAll("=", ""); test("MCP OAuth opaque-bearer flow authenticates /mcp end-to-end", async () => { const cookie = await signUp("oauth@env.test"); // 1. Dynamic client registration (public/PKCE client). const reg = await handler( new Request(`${BASE}/api/auth/mcp/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ client_name: "test-client", redirect_uris: ["http://localhost:9999/callback"], token_endpoint_auth_method: "none", grant_types: ["authorization_code"], response_types: ["code"], }), }), ); expect([200, 201]).toContain(reg.status); const clientId = String((await json(reg)).client_id); // 2. PKCE authorize with the signed-in session cookie. Self-host forces an // approval screen (prompt=consent injected by the serving layer), so authorize // does NOT redirect straight to the client callback — it redirects to the // relative /mcp-consent page carrying a consent_code. A real client's browser // lands there and the user clicks Allow; we drive that POST directly. const verifier = b64url(crypto.getRandomValues(new Uint8Array(32))); const challengeBytes = new Uint8Array( await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)), ); const codeChallenge = b64url(challengeBytes); const authorizeUrl = new URL(`${BASE}/api/auth/mcp/authorize`); authorizeUrl.search = new URLSearchParams({ response_type: "code", client_id: clientId, redirect_uri: "http://localhost:9999/callback", code_challenge: codeChallenge, code_challenge_method: "S256", scope: "openid", }).toString(); const authorize = await handler( new Request(authorizeUrl, { headers: { cookie }, redirect: "manual" }), ); expect(authorize.status).toBe(302); // Resolve against BASE — the consent redirect is a relative app path. const consentRedirect = new URL(authorize.headers.get("location") ?? "", BASE); expect(consentRedirect.pathname).toBe("/mcp-consent"); const consentCode = consentRedirect.searchParams.get("consent_code") ?? ""; expect(consentCode).not.toBe(""); // 2b. Approve on the consent screen — the same POST the /mcp-consent page makes. // It returns { redirectURI } back to the client's callback carrying the code. const consent = await handler( new Request(`${BASE}/api/auth/oauth2/consent`, { method: "POST", headers: { "content-type": "application/json", cookie }, body: JSON.stringify({ accept: true, consent_code: consentCode }), }), ); expect(consent.status).toBe(200); const redirectURI = String((await json(consent)).redirectURI ?? ""); const code = new URL(redirectURI).searchParams.get("code") ?? ""; expect(code).not.toBe(""); // 3. Token exchange. const token = await handler( new Request(`${BASE}/api/auth/mcp/token`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: "http://localhost:9999/callback", client_id: clientId, code_verifier: verifier, }).toString(), }), ); expect(token.status).toBe(200); const accessToken = String((await json(token)).access_token); expect(accessToken).not.toBe(""); // 4. The opaque access token authenticates /mcp (initialize succeeds). const init = await handler( new Request(`${BASE}/mcp`, { method: "POST", headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json", accept: "application/json, text/event-stream", }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "t", version: "1" }, }, }), }), ); expect(init.status).toBe(200); expect(init.headers.get("mcp-session-id")).not.toBe(null);});
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, expect, test } from "@effect/vitest";
import { mintInviteCode } from "../testing/mint-invite";
process.env.EXECUTOR_DATA_DIR = mkdtempSync(join(tmpdir(), "eh-env-"));
process.env.BETTER_AUTH_SECRET = "env-test-secret-0123456789-abcdefghij-klmnop";
process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL = "admin@env.test";
process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD = "admin-pass-123456";
const { makeSelfHostApiHandler } = await import("../app");
const { handler, dispose } = await makeSelfHostApiHandler();
afterAll(() => dispose());
const BASE = "http://localhost:4788";
test("serves OAuth Authorization Server metadata at the origin root", async () => {
const res = await handler(new Request(`${BASE}/.well-known/oauth-authorization-server`));
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, unknown>;
expect(body.issuer).toBeDefined();
// mcp() advertises its DCR + authorize + token endpoints under /api/auth/mcp.
expect(String(body.authorization_endpoint)).toContain("/api/auth/mcp/authorize");
expect(String(body.token_endpoint)).toContain("/api/auth/mcp/token");
expect(String(body.registration_endpoint)).toContain("/api/auth/mcp/register");
});
test("serves OAuth Protected Resource metadata at the origin root", async () => {
const res = await handler(new Request(`${BASE}/.well-known/oauth-protected-resource`));
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, unknown>;
expect(body.resource).toBeDefined();
expect(Array.isArray(body.authorization_servers)).toBe(true);
});
test("serves OAuth Protected Resource metadata for a toolkit MCP resource", async () => {
const res = await handler(
new Request(`${BASE}/.well-known/oauth-protected-resource/mcp/toolkits/deploy`),
);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, unknown>;
expect(body.resource).toBe(`${BASE}/mcp/toolkits/deploy`);
expect(Array.isArray(body.authorization_servers)).toBe(true);
});
test("an unauthenticated /mcp request returns 401 with a WWW-Authenticate challenge", async () => {
const res = await handler(
new Request(`${BASE}/mcp`, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json, text/event-stream",
},
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }),
}),
);
expect(res.status).toBe(401);
const challenge = res.headers.get("www-authenticate") ?? "";
expect(challenge).toContain("Bearer");
expect(challenge).toContain("resource_metadata=");
});
test("an unauthenticated toolkit MCP request challenges with toolkit metadata", async () => {
const res = await handler(
new Request(`${BASE}/mcp/toolkits/deploy`, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json, text/event-stream",
},
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }),
}),
);
expect(res.status).toBe(401);
const challenge = res.headers.get("www-authenticate") ?? "";
expect(challenge).toContain("Bearer");
expect(challenge).toContain(
`resource_metadata="${BASE}/.well-known/oauth-protected-resource/mcp/toolkits/deploy"`,
);
});
// --- End-to-end MCP OAuth: DCR -> authorize -> token -> /mcp with bearer ---
const json = async (res: Response) => (await res.json()) as Record<string, unknown>;
const signUp = async (email: string): Promise<string> => {
const inviteCode = await mintInviteCode(handler);
const res = await handler(
new Request(`${BASE}/api/auth/sign-up/email`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ email, password: "password-12345678", name: email, inviteCode }),
}),
);
expect(res.status).toBe(200);
// The session cookie lets /mcp/authorize skip the interactive login.
return res.headers.get("set-cookie") ?? "";
};
const b64url = (buf: Uint8Array): string =>
btoa(String.fromCharCode(...buf))
.replaceAll("+", "-")
.replaceAll("/", "_")
.replaceAll("=", "");
test("MCP OAuth opaque-bearer flow authenticates /mcp end-to-end", async () => {
const cookie = await signUp("oauth@env.test");
// 1. Dynamic client registration (public/PKCE client).
const reg = await handler(
new Request(`${BASE}/api/auth/mcp/register`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
client_name: "test-client",
redirect_uris: ["http://localhost:9999/callback"],
token_endpoint_auth_method: "none",
grant_types: ["authorization_code"],
response_types: ["code"],
}),
}),
);
expect([200, 201]).toContain(reg.status);
const clientId = String((await json(reg)).client_id);
// 2. PKCE authorize with the signed-in session cookie. Self-host forces an
// approval screen (prompt=consent injected by the serving layer), so authorize
// does NOT redirect straight to the client callback — it redirects to the
// relative /mcp-consent page carrying a consent_code. A real client's browser
// lands there and the user clicks Allow; we drive that POST directly.
const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));
const challengeBytes = new Uint8Array(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)),
);
const codeChallenge = b64url(challengeBytes);
const authorizeUrl = new URL(`${BASE}/api/auth/mcp/authorize`);
authorizeUrl.search = new URLSearchParams({
response_type: "code",
client_id: clientId,
redirect_uri: "http://localhost:9999/callback",
code_challenge: codeChallenge,
code_challenge_method: "S256",
scope: "openid",
}).toString();
const authorize = await handler(
new Request(authorizeUrl, { headers: { cookie }, redirect: "manual" }),
);
expect(authorize.status).toBe(302);
// Resolve against BASE — the consent redirect is a relative app path.
const consentRedirect = new URL(authorize.headers.get("location") ?? "", BASE);
expect(consentRedirect.pathname).toBe("/mcp-consent");
const consentCode = consentRedirect.searchParams.get("consent_code") ?? "";
expect(consentCode).not.toBe("");
// 2b. Approve on the consent screen — the same POST the /mcp-consent page makes.
// It returns { redirectURI } back to the client's callback carrying the code.
const consent = await handler(
new Request(`${BASE}/api/auth/oauth2/consent`, {
method: "POST",
headers: { "content-type": "application/json", cookie },
body: JSON.stringify({ accept: true, consent_code: consentCode }),
}),
);
expect(consent.status).toBe(200);
const redirectURI = String((await json(consent)).redirectURI ?? "");
const code = new URL(redirectURI).searchParams.get("code") ?? "";
expect(code).not.toBe("");
// 3. Token exchange.
const token = await handler(
new Request(`${BASE}/api/auth/mcp/token`, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: "http://localhost:9999/callback",
client_id: clientId,
code_verifier: verifier,
}).toString(),
}),
);
expect(token.status).toBe(200);
const accessToken = String((await json(token)).access_token);
expect(accessToken).not.toBe("");
// 4. The opaque access token authenticates /mcp (initialize succeeds).
const init = await handler(
new Request(`${BASE}/mcp`, {
method: "POST",
headers: {
authorization: `Bearer ${accessToken}`,
"content-type": "application/json",
accept: "application/json, text/event-stream",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: "2025-03-26",
capabilities: {},
clientInfo: { name: "t", version: "1" },
},
}),
}),
);
expect(init.status).toBe(200);
expect(init.headers.get("mcp-session-id")).not.toBe(null);
});