diff --git a/src/oauth/client-metadata.ts b/src/oauth/client-metadata.ts index d11a831..82dbb94 100644 --- a/src/oauth/client-metadata.ts +++ b/src/oauth/client-metadata.ts @@ -29,6 +29,19 @@ export interface ClientMetadata { [key: string]: unknown; } +export const ROOK_CLI_CLIENT_METADATA: ClientMetadata = { + client_id: "https://rook.host/client-metadata.json", + client_name: "rook cli", + application_type: "native", + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + redirect_uris: ["http://127.0.0.1/callback"], + scope: "atproto transition:generic repo:sh.tangled.repo repo:sh.tangled.repo.pull blob:*/* rpc:sh.tangled.repo.create?aud=did:web:knot.rook.host rpc:sh.tangled.git.receivePack?aud=did:web:knot.rook.host", + token_endpoint_auth_method: "none", + dpop_bound_access_tokens: true, + client_uri: "https://rook.host", +}; + export interface FetchClientMetadataOptions { fetch?: typeof fetch; timeoutMs?: number; @@ -185,6 +198,10 @@ export async function fetchClientMetadata( ): Promise { validateClientId(clientId, env); + if (clientId === ROOK_CLI_CLIENT_METADATA.client_id) { + return validateClientMetadata(clientId, ROOK_CLI_CLIENT_METADATA); + } + const cached = clientMetadataCache.get(clientId); if (cached && Date.now() - cached.fetchedAt < CLIENT_METADATA_CACHE_TTL_MS) { return cached.metadata; diff --git a/src/worker.ts b/src/worker.ts index a1651de..9b309b8 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -35,6 +35,7 @@ import { ClientMetadataError, fetchClientMetadata, matchRedirectUri, + ROOK_CLI_CLIENT_METADATA, verifyClientAuth, } from "./oauth/client-metadata"; import { UseDpopNonceError, validateOauthDpopProof } from "./oauth/dpop"; @@ -258,19 +259,6 @@ this is the rulebook for rook.host, the commons sol pbc operates — atproto hos 11. **free, as-is.** the commons is free. sol pbc works to keep it up and honest but doesn't guarantee it stays available, and provides it as-is — no warranties, express or implied, including merchantability and fitness for a particular purpose. to the fullest extent the law allows, sol pbc isn't liable for indirect, incidental, or consequential harm arising from the commons; nothing here waives what the law won't let us waive. governed by colorado law. your own rookery is always your fallback. `; -const ROOK_CLI_CLIENT_METADATA = { - client_id: "https://rook.host/client-metadata.json", - client_name: "rook cli", - application_type: "native", - grant_types: ["authorization_code", "refresh_token"], - response_types: ["code"], - redirect_uris: ["http://127.0.0.1/callback"], - scope: "atproto transition:generic repo:sh.tangled.repo repo:sh.tangled.repo.pull blob:*/* rpc:sh.tangled.repo.create?aud=did:web:knot.rook.host rpc:sh.tangled.git.receivePack?aud=did:web:knot.rook.host", - token_endpoint_auth_method: "none", - dpop_bound_access_tokens: true, - client_uri: "https://rook.host", -} as const; - function getTosText(env: Env): string { return env.ROOKERY_VARIANT === "commons" ? TOS_TEXT_COMMONS : TOS_TEXT; } diff --git a/test/oauth-client-metadata.test.ts b/test/oauth-client-metadata.test.ts index 3c0c594..9104fb5 100644 --- a/test/oauth-client-metadata.test.ts +++ b/test/oauth-client-metadata.test.ts @@ -112,13 +112,17 @@ describe("client metadata", () => { ).rejects.toThrow("rookery origin"); }); - it("allows only the built-in CLI metadata path on the rookery origin", async () => { + it("resolves only the built-in CLI metadata path locally on the rookery origin", async () => { const clientId = "https://rookery.test/client-metadata.json"; - const fetchImpl = (async () => jsonResponse(validMetadata(clientId))) as typeof fetch; + const fetchImpl = (async () => { + throw new Error("the built-in metadata must not self-fetch"); + }) as typeof fetch; - await expect(fetchClientMetadata(clientId, env, { fetch: fetchImpl })).resolves.toMatchObject({ - client_id: clientId, - }); + await expect( + fetchClientMetadata("https://rook.host/client-metadata.json", { + ROOKERY_HOSTNAME: "rook.host", + }, { fetch: fetchImpl }), + ).resolves.toMatchObject({ client_id: "https://rook.host/client-metadata.json" }); __resetClientMetadataCache(); await expect(