Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-mcp/src/consent.ts

Snapshot at 34e3175

Containing directory

1,853 bytes · typescriptView raw
1import type { AuthRequest, OAuthHelpers } from "@cloudflare/workers-oauth-provider";
2import { PublicError } from "./core";
3
4/** D8 requires exact registration matching, including loopback ports, before consent/enrollment. */
5export async function requireRegisteredRedirect(oauth: OAuthHelpers, request: AuthRequest) {
6  const client = await oauth.lookupClient(request.clientId);
7  if (!client?.redirectUris.includes(request.redirectUri)) {
8    throw new PublicError(400, "Redirect URI must exactly match client registration.");
9  }
10}
11
12/** Emit a browser response, never fetch its Location. Preserve the registered callback and query. */
13export function consentRedirect(
14  request: AuthRequest,
15  redirectTo: string,
16  headers: Headers,
17  decision: "approve" | "deny",
18): Response {
19  const expected = new URL(request.redirectUri);
20  if (decision === "approve") {
21    const code = new URL(redirectTo).searchParams.get("code");
22    if (!code) throw new PublicError(502, "OAuth consent redirect refused.");
23    expected.searchParams.set("code", code);
24  } else {
25    for (const key of ["error", "error_description", "error_uri", "state", "iss"]) {
26      expected.searchParams.delete(key);
27    }
28    expected.searchParams.set("error", "access_denied");
29  }
30  if (request.state) expected.searchParams.set("state", request.state);
31  if (request.issuer) expected.searchParams.set("iss", request.issuer);
32  // Only the OAuth result fields may differ from the exact registered redirect_uri.
33  // Comparing the whole generated URL also rejects changed paths, query values, fragments,
34  // duplicate result fields, or extra parameters in a wrapped provider's response.
35  if (redirectTo !== expected.href) throw new PublicError(502, "OAuth consent redirect refused.");
36  headers.set("location", redirectTo);
37  return new Response(null, { status: 302, headers });
38}
39