CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | import { describe, it, expect } from "bun:test";
import app from "../app";
// RFC 7591 Dynamic Client Registration — POST /oauth/register.
// The validation paths (redirect_uris, auth method) run BEFORE any DB access,
// so they're deterministic without a test DB. The happy path needs a DB, so it
// returns 201 with a DB or 503 without one — we assert both shapes.
async function register(body: unknown) {
return app.request("/oauth/register", {
method: "POST",
headers: { "content-type": "application/json" },
body: typeof body === "string" ? body : JSON.stringify(body),
});
}
describe("POST /oauth/register — validation (RFC 7591)", () => {
it("rejects a non-JSON body", async () => {
const res = await register("{not json");
expect(res.status).toBe(400);
expect((await res.json()).error).toBe("invalid_client_metadata");
});
it("requires a non-empty redirect_uris array", async () => {
const res = await register({ client_name: "X" });
expect(res.status).toBe(400);
expect((await res.json()).error).toBe("invalid_redirect_uri");
});
it("rejects a wildcard redirect URI", async () => {
const res = await register({
redirect_uris: ["https://evil.example.com/*"],
});
expect(res.status).toBe(400);
expect((await res.json()).error).toBe("invalid_redirect_uri");
});
it("rejects a non-https remote redirect URI", async () => {
const res = await register({
redirect_uris: ["http://evil.example.com/cb"],
});
expect(res.status).toBe(400);
expect((await res.json()).error).toBe("invalid_redirect_uri");
});
it("rejects too many redirect URIs", async () => {
const res = await register({
redirect_uris: Array.from({ length: 9 }, (_, i) => `https://a${i}.example.com/cb`),
});
expect(res.status).toBe(400);
expect((await res.json()).error).toBe("invalid_redirect_uri");
});
it("rejects an unsupported token_endpoint_auth_method", async () => {
const res = await register({
redirect_uris: ["https://claude.ai/cb"],
token_endpoint_auth_method: "private_key_jwt",
});
expect(res.status).toBe(400);
expect((await res.json()).error).toBe("invalid_client_metadata");
});
it("accepts a valid public-client registration (201) or degrades to 503", async () => {
const res = await register({
client_name: "Claude (test)",
redirect_uris: ["https://claude.ai/api/mcp/callback"],
token_endpoint_auth_method: "none",
scope: "read:repo write:pr bogus:scope",
});
expect([201, 503]).toContain(res.status);
if (res.status === 201) {
const doc = await res.json();
expect(doc.client_id).toMatch(/^glc_app_/);
// Public client (auth method "none") gets NO client_secret.
expect(doc.client_secret).toBeUndefined();
expect(doc.token_endpoint_auth_method).toBe("none");
expect(doc.grant_types).toContain("authorization_code");
expect(doc.client_secret_expires_at).toBe(0);
expect(doc.registration_access_token).toBeTruthy();
// Unknown scope dropped; known scopes preserved.
expect(doc.scope).toContain("read:repo");
expect(doc.scope).not.toContain("bogus:scope");
}
});
it("accepts a confidential-client registration and returns a secret (201) or 503", async () => {
const res = await register({
client_name: "Confidential (test)",
redirect_uris: ["https://app.example.com/callback"],
token_endpoint_auth_method: "client_secret_basic",
});
expect([201, 503]).toContain(res.status);
if (res.status === 201) {
const doc = await res.json();
expect(doc.client_secret).toMatch(/^glcs_/);
}
});
});
|