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

Function makeOAuthService

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

Source from the content-addressed store, hash-verified

491 });
492
493export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => {
494 const httpClientLayer = deps.httpClientLayer ?? FetchHttpClient.layer;
495 const fetch = deps.fetch;
496 // EXPLICIT — no localhost default. `null` means this executor has no OAuth
497 // callback; redirect-requiring flows fail loudly via `requireRedirectUri`.
498 const redirectUri = deps.redirectUri;
499 const discoveryOptions = { endpointUrlPolicy: deps.endpointUrlPolicy };
500
501 const filterAuthorizationCodeScopes = (
502 client: LoadedOAuthClient,
503 requestedScopes: readonly string[],
504 ): Effect.Effect<readonly string[], never> =>
505 Effect.gen(function* () {
506 if (requestedScopes.length === 0) return requestedScopes;
507 const resource = client.resource
508 ? yield* discoverProtectedResourceMetadata(client.resource, discoveryOptions).pipe(
509 Effect.catch(() => Effect.succeed(null)),
510 Effect.provide(httpClientLayer),
511 )
512 : null;
513 const issuer =
514 resource?.metadata.authorization_servers?.[0] ?? new URL(client.authorizationUrl).origin;
515 const as = yield* discoverAuthorizationServerMetadata(issuer, discoveryOptions).pipe(
516 Effect.catch(() => Effect.succeed(null)),
517 Effect.provide(httpClientLayer),
518 );
519 if (!as || !oauthMetadataMatchesClient(client, as.metadata)) return requestedScopes;
520 return intersectScopes(requestedScopes, as.metadata.scopes_supported);
521 }).pipe(Effect.catch(() => Effect.succeed(requestedScopes)));
522
523 // Caps on server-controlled discovery input — a hostile or buggy server must
524 // not be able to hang `oauth.start` or overflow the authorize URL.
525 const MAX_DISCOVERY_AUTH_SERVERS = 3; // AS-failover lists are tiny in practice
526 const MAX_DISCOVERED_SCOPES = 100; // far beyond any realistic authorization template
527 const capScopes = (scopes: readonly string[]): readonly string[] =>
528 dedupeScopes(scopes).slice(0, MAX_DISCOVERED_SCOPES);
529
530 // Discover the scopes to request when the integration declares none — only
531 // reached for integrations that opt in (MCP-style). The resource's own RFC
532 // 9728 `scopes_supported` is authoritative when present, even when empty (§2
533 // defines the field; §7.2 cautions against requesting more than it lists).
534 // Only when the resource is SILENT do we read the scopes advertised by the
535 // authorization servers it NAMES (RFC 8414) — we never probe arbitrary URLs.
536 const discoverScopesForResource = (
537 resource: string | null,
538 ): Effect.Effect<readonly string[], OAuthDiscoveryError> =>
539 Effect.gen(function* () {
540 if (resource == null) {
541 return yield* new OAuthDiscoveryError({
542 message: "Cannot discover OAuth scopes: the client has no resource configured",
543 });
544 }
545 // `httpClientLayer` flows through `options` so discovery uses the host's
546 // configured client (discovery self-provides from `options.httpClientLayer`).
547 const discoveryOptions = { endpointUrlPolicy: deps.endpointUrlPolicy, httpClientLayer };
548
549 const protectedResource = yield* discoverProtectedResourceMetadata(
550 resource,

Callers 1

createExecutorFunction · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected