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

Function makeOAuthService

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

Source from the content-addressed store, hash-verified

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

Callers 1

createExecutorFunction · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected