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

Function makeOAuthService

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

Source from the content-addressed store, hash-verified

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

Callers 1

createExecutorFunction · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected