// 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");
});