Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

79 lines · 5.1 KBRaw History
1/**
2 * OAuth for MCP clients (auth/oauth.ts): which redirect URIs an app may register and use, the access
3 * preselected on the consent page, resource indicators, metadata document client IDs, and the
4 * discovery documents. The whole flow, in a browser, is test/e2e/oauth.test.mjs.
5 */
6import { describe, expect, it } from "vitest";
7import { adminAllowed, authorizationServerMetadata, checkResource, formActionSource, isMetadataClientId, mcpChallenge, protectedResourceMetadata, redirectLabel, redirectMatches, requestedScope, validRedirectUri } from "../../src/auth/oauth.js";
8import { bucketsFor } from "../../src/lib/rate-limit.js";
9
10const env = { PUBLIC_URL: "https://diffinery.com" };
11
12describe("redirect URIs", () => {
13 it("allows https anywhere, http only on this computer, and apps' own schemes", () => {
14 for (const ok of ["https://claude.ai/api/mcp/auth_callback", "http://localhost/callback", "http://127.0.0.1:33418/cb", "http://[::1]:9/x", "cursor://anysphere.cursor-retrieval/oauth/callback", "vscode://vscode.github-authentication/did-authenticate"]) expect(validRedirectUri(ok), ok).toBe(true);
15 for (const bad of ["http://evil.example/cb", "http://localhost.evil.example/cb", "javascript:alert(1)", "data:text/html,x", "file:///etc/passwd", "https://a.example/cb#x", "https://user:pw@a.example/cb", "chrome-extension://abc/cb", "not a url", "", 42]) expect(validRedirectUri(bad), String(bad)).toBe(false);
16 });
17
18 it("matches exactly, except a loopback port (RFC 8252)", () => {
19 const reg = ["http://127.0.0.1/callback", "https://app.example/cb"];
20 expect(redirectMatches(reg, "https://app.example/cb")).toBe(true);
21 expect(redirectMatches(reg, "http://127.0.0.1:51234/callback")).toBe(true);
22 expect(redirectMatches(reg, "http://127.0.0.1:51234/other")).toBe(false);
23 expect(redirectMatches(reg, "http://localhost:51234/callback")).toBe(false);
24 expect(redirectMatches(reg, "https://app.example:8443/cb")).toBe(false);
25 expect(redirectMatches(reg, "https://app.example/cb?x=1")).toBe(false);
26 });
27
28 it("names where the person goes back to, and lets the form lead there", () => {
29 expect(redirectLabel("http://127.0.0.1:5/callback")).toBe("an app on this computer");
30 expect(redirectLabel("https://claude.ai/api/mcp/auth_callback")).toBe("claude.ai");
31 expect(redirectLabel("cursor://anysphere.cursor-retrieval/oauth")).toBe("cursor://anysphere.cursor-retrieval");
32 expect(formActionSource("https://claude.ai/api/mcp/auth_callback")).toBe("https://claude.ai");
33 expect(formActionSource("cursor://anysphere.cursor-retrieval/oauth")).toBe("cursor:");
34 });
35});
36
37describe("scopes and resources", () => {
38 it("preselects write for apps that ask for everything, never admin", () => {
39 expect(requestedScope("read write admin")).toBe("write");
40 expect(requestedScope("admin")).toBe("write");
41 expect(requestedScope("read")).toBe("read");
42 expect(requestedScope("")).toBe("write");
43 expect(requestedScope("openid profile")).toBe("write");
44 });
45
46 it("never lets an app act as a site administrator with admin access", () => {
47 expect(adminAllowed({ site_admin: 1 } as never)).toBe(false);
48 expect(adminAllowed({ site_admin: 0 } as never)).toBe(true);
49 });
50
51 it("accepts only this server or its /mcp as the resource", () => {
52 expect(() => checkResource(env, "https://diffinery.com/mcp")).not.toThrow();
53 expect(() => checkResource(env, "https://diffinery.com/")).not.toThrow();
54 expect(() => checkResource(env, undefined)).not.toThrow();
55 expect(() => checkResource(env, "https://evil.example/mcp")).toThrow(/resource must be/);
56 });
57
58 it("takes https URLs with a path as metadata document client IDs (plain http only locally)", () => {
59 expect(isMetadataClientId("https://claude.ai/oauth/claude-code-client-metadata")).toBe(true);
60 expect(isMetadataClientId("https://claude.ai/")).toBe(false);
61 expect(isMetadataClientId("http://127.0.0.1:9/client.json")).toBe(false);
62 expect(isMetadataClientId("http://127.0.0.1:9/client.json", true)).toBe(true);
63 expect(isMetadataClientId("dfc_abc")).toBe(false);
64 });
65});
66
67describe("discovery", () => {
68 it("points /mcp's 401 at the resource metadata, and that at this server", () => {
69 expect(mcpChallenge(env)).toBe('Bearer realm="Diffinery", resource_metadata="https://diffinery.com/.well-known/oauth-protected-resource/mcp"');
70 expect(mcpChallenge(env, "invalid_token")).toMatch(/error="invalid_token"$/);
71 expect(protectedResourceMetadata(env)).toMatchObject({ resource: "https://diffinery.com/mcp", authorization_servers: ["https://diffinery.com"] });
72 expect(authorizationServerMetadata(env)).toMatchObject({ issuer: "https://diffinery.com", token_endpoint: "https://diffinery.com/oauth/token", code_challenge_methods_supported: ["S256"], client_id_metadata_document_supported: true });
73 });
74
75 it("counts registrations against the sign-in rate limit", () => {
76 expect(bucketsFor("POST", "/oauth/register", { type: "anonymous" }, "1.2.3.4")).toEqual([["RL_AUTH", "ip:1.2.3.4"]]);
77 expect(bucketsFor("POST", "/oauth/token", { type: "anonymous" }, "1.2.3.4")).toEqual([["RL_ANON", "ip:1.2.3.4"]]);
78 });
79});