Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

oauth-dcr.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

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