// Run with `bun test`. import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { test } from "node:test"; import { isAllowedRedirectUri, isCodeChallenge, redirectUriMatches, requestedPermission, verifyPkce, withParams } from "./oauth-rules"; test("redirect URIs: https anywhere, http only on loopback, app schemes, never script or fragments", () => { for (const ok of ["https://claude.ai/api/mcp/auth_callback", "http://localhost:3118/callback", "http://127.0.0.1/cb", "http://[::1]:9/cb", "cursor://anysphere.cursor-retrieval/oauth/callback"]) { assert.equal(isAllowedRedirectUri(ok), true, ok); } for (const bad of ["http://evil.example/cb", "javascript:alert(1)", "data:text/html,x", "file:///etc/passwd", "https://a.example/cb#frag", "https://u:p@a.example/", "not a url"]) { assert.equal(isAllowedRedirectUri(bad), false, bad); } }); test("redirect URIs match exactly, except for the port of loopback ones", () => { const registered = ["http://localhost:3118/callback", "https://app.example/cb"]; assert.equal(redirectUriMatches(registered, "https://app.example/cb"), true); assert.equal(redirectUriMatches(registered, "http://localhost:50123/callback"), true); assert.equal(redirectUriMatches(registered, "https://app.example/cb2"), false); assert.equal(redirectUriMatches(registered, "https://app.example:8443/cb"), false); assert.equal(redirectUriMatches(registered, "http://localhost:50123/other"), false); assert.equal(redirectUriMatches(registered, "http://127.0.0.1:3118/callback"), false); }); test("PKCE S256", () => { const verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; const challenge = createHash("sha256").update(verifier).digest("base64url"); assert.equal(challenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"); // RFC 7636 appendix B assert.equal(isCodeChallenge(challenge), true); assert.equal(verifyPkce(verifier, challenge), true); assert.equal(verifyPkce(`${verifier}x`, challenge), false); assert.equal(verifyPkce("short", createHash("sha256").update("short").digest("base64url")), false); }); test("scopes: read alone is read-only, anything else asks for write", () => { assert.equal(requestedPermission("read"), "read"); assert.equal(requestedPermission("read write"), "write"); assert.equal(requestedPermission(undefined), "write"); assert.equal(requestedPermission("openid profile"), "write"); }); test("response parameters keep the client's own query", () => { assert.equal(withParams("https://a.example/cb?x=1", { code: "c", state: null }), "https://a.example/cb?x=1&code=c"); });