MCPcopy Create free account
hub / github.com/UsefulSoftwareCo/executor / makeOAuthService

Function makeOAuthService

packages/core/sdk/src/oauth-service.ts:424–1376  ·  view source on GitHub ↗
(deps: OAuthServiceDeps)

Source from the content-addressed store, hash-verified

422 });
423
424export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => {
425 const httpClientLayer = deps.httpClientLayer ?? FetchHttpClient.layer;
426 const fetch = deps.fetch;
427 // EXPLICIT — no localhost default. `null` means this executor has no OAuth
428 // callback; redirect-requiring flows fail loudly via `requireRedirectUri`.
429 const redirectUri = deps.redirectUri;
430 const discoveryOptions = { endpointUrlPolicy: deps.endpointUrlPolicy };
431
432 const filterAuthorizationCodeScopes = (
433 client: LoadedOAuthClient,
434 requestedScopes: readonly string[],
435 ): Effect.Effect<readonly string[], never> =>
436 Effect.gen(function* () {
437 if (requestedScopes.length === 0) return requestedScopes;
438 const resource = client.resource
439 ? yield* discoverProtectedResourceMetadata(client.resource, discoveryOptions).pipe(
440 Effect.catch(() => Effect.succeed(null)),
441 Effect.provide(httpClientLayer),
442 )
443 : null;
444 const issuer =
445 resource?.metadata.authorization_servers?.[0] ?? new URL(client.authorizationUrl).origin;
446 const as = yield* discoverAuthorizationServerMetadata(issuer, discoveryOptions).pipe(
447 Effect.catch(() => Effect.succeed(null)),
448 Effect.provide(httpClientLayer),
449 );
450 return intersectScopes(requestedScopes, as?.metadata.scopes_supported);
451 }).pipe(Effect.catch(() => Effect.succeed(requestedScopes)));
452
453 // Caps on server-controlled discovery input — a hostile or buggy server must
454 // not be able to hang `oauth.start` or overflow the authorize URL.
455 const MAX_DISCOVERY_AUTH_SERVERS = 3; // AS-failover lists are tiny in practice
456 const MAX_DISCOVERED_SCOPES = 100; // far beyond any realistic authorization template
457 const capScopes = (scopes: readonly string[]): readonly string[] =>
458 dedupeScopes(scopes).slice(0, MAX_DISCOVERED_SCOPES);
459
460 // Discover the scopes to request when the integration declares none — only
461 // reached for integrations that opt in (MCP-style). The resource's own RFC
462 // 9728 `scopes_supported` is authoritative when present, even when empty (§2
463 // defines the field; §7.2 cautions against requesting more than it lists).
464 // Only when the resource is SILENT do we read the scopes advertised by the
465 // authorization servers it NAMES (RFC 8414) — we never probe arbitrary URLs.
466 const discoverScopesForResource = (
467 resource: string | null,
468 ): Effect.Effect<readonly string[], OAuthDiscoveryError> =>
469 Effect.gen(function* () {
470 if (resource == null) {
471 return yield* new OAuthDiscoveryError({
472 message: "Cannot discover OAuth scopes: the client has no resource configured",
473 });
474 }
475 // `httpClientLayer` flows through `options` so discovery uses the host's
476 // configured client (discovery self-provides from `options.httpClientLayer`).
477 const discoveryOptions = { endpointUrlPolicy: deps.endpointUrlPolicy, httpClientLayer };
478
479 const protectedResource = yield* discoverProtectedResourceMetadata(
480 resource,
481 discoveryOptions,

Callers 1

createExecutorFunction · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected