@executor-js/sdk 1.5.16 → 1.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EIHWBY6T.js → chunk-4XPVLX62.js} +36 -1
- package/dist/chunk-4XPVLX62.js.map +1 -0
- package/dist/{chunk-VICUTMT6.js → chunk-5Q35SELN.js} +68 -14
- package/dist/chunk-5Q35SELN.js.map +1 -0
- package/dist/{chunk-SK3PCJHJ.js → chunk-EJKXSREX.js} +10 -1
- package/dist/chunk-EJKXSREX.js.map +1 -0
- package/dist/{chunk-6EED5LAL.js → chunk-PCSRC6WP.js} +1 -1
- package/dist/{chunk-6EED5LAL.js.map → chunk-PCSRC6WP.js.map} +1 -1
- package/dist/core-schema.d.ts +2 -0
- package/dist/core-schema.d.ts.map +1 -1
- package/dist/core-tools.d.ts.map +1 -1
- package/dist/core.js +4 -4
- package/dist/executor.d.ts.map +1 -1
- package/dist/host-internal.js +1 -1
- package/dist/index.js +3 -3
- package/dist/oauth-client.d.ts +5 -0
- package/dist/oauth-client.d.ts.map +1 -1
- package/dist/oauth-helpers.d.ts +1 -0
- package/dist/oauth-helpers.d.ts.map +1 -1
- package/dist/oauth-service.d.ts +4 -0
- package/dist/oauth-service.d.ts.map +1 -1
- package/dist/server-connection.d.ts +7 -0
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/shared.js +2 -2
- package/dist/testing.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-EIHWBY6T.js.map +0 -1
- package/dist/chunk-SK3PCJHJ.js.map +0 -1
- package/dist/chunk-VICUTMT6.js.map +0 -1
|
@@ -62,6 +62,40 @@ var providerAuthorizeExtras = (authorizationUrl) => {
|
|
|
62
62
|
}
|
|
63
63
|
return {};
|
|
64
64
|
};
|
|
65
|
+
var hostnameFromCallbackDomain = (callbackDomain) => {
|
|
66
|
+
const trimmed = callbackDomain.trim();
|
|
67
|
+
if (trimmed.length === 0) return void 0;
|
|
68
|
+
const candidate = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
|
|
69
|
+
if (!URL.canParse(candidate)) return void 0;
|
|
70
|
+
const url = new URL(candidate);
|
|
71
|
+
if (url.port !== "" || url.username !== "" || url.password !== "") return void 0;
|
|
72
|
+
if (url.pathname !== "/" && url.pathname !== "") return void 0;
|
|
73
|
+
return url.hostname.toLowerCase();
|
|
74
|
+
};
|
|
75
|
+
var siblingParentDomainOf = (hostname) => {
|
|
76
|
+
const labels = hostname.split(".");
|
|
77
|
+
if (labels.length < 3) return void 0;
|
|
78
|
+
const parent = labels.slice(1).join(".");
|
|
79
|
+
return parent.includes(".") ? parent : void 0;
|
|
80
|
+
};
|
|
81
|
+
var rebindTokenEndpointHostToCallbackDomain = (configuredTokenUrl, callbackDomain) => {
|
|
82
|
+
if (!callbackDomain) return configuredTokenUrl;
|
|
83
|
+
if (!URL.canParse(configuredTokenUrl)) return configuredTokenUrl;
|
|
84
|
+
const configured = new URL(configuredTokenUrl);
|
|
85
|
+
if (configured.protocol !== "https:") return configuredTokenUrl;
|
|
86
|
+
const targetHost = hostnameFromCallbackDomain(callbackDomain);
|
|
87
|
+
if (!targetHost) return configuredTokenUrl;
|
|
88
|
+
const configuredHost = configured.hostname.toLowerCase();
|
|
89
|
+
if (targetHost === configuredHost) return configuredTokenUrl;
|
|
90
|
+
const configuredParent = siblingParentDomainOf(configuredHost);
|
|
91
|
+
const targetParent = siblingParentDomainOf(targetHost);
|
|
92
|
+
if (!configuredParent || !targetParent || configuredParent !== targetParent) {
|
|
93
|
+
return configuredTokenUrl;
|
|
94
|
+
}
|
|
95
|
+
const rebound = new URL(configuredTokenUrl);
|
|
96
|
+
rebound.hostname = targetHost;
|
|
97
|
+
return rebound.toString();
|
|
98
|
+
};
|
|
65
99
|
var isOAuth2Error = Predicate.isTagged("OAuth2Error");
|
|
66
100
|
var responseFromOAuthErrorCause = (cause) => {
|
|
67
101
|
if (cause instanceof Response) return cause;
|
|
@@ -296,9 +330,10 @@ export {
|
|
|
296
330
|
createOAuthState,
|
|
297
331
|
buildAuthorizationUrl,
|
|
298
332
|
providerAuthorizeExtras,
|
|
333
|
+
rebindTokenEndpointHostToCallbackDomain,
|
|
299
334
|
exchangeAuthorizationCode,
|
|
300
335
|
exchangeClientCredentials,
|
|
301
336
|
refreshAccessToken,
|
|
302
337
|
shouldRefreshToken
|
|
303
338
|
};
|
|
304
|
-
//# sourceMappingURL=chunk-
|
|
339
|
+
//# sourceMappingURL=chunk-4XPVLX62.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/oauth-helpers.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// OAuth 2.0 helpers — generic, isomorphic building blocks.\n//\n// Thin wrappers around `oauth4webapi` (stateless; pure Web Crypto +\n// `fetch`, no deps; runs unchanged in Node, CF Workers, and browsers).\n// Each public helper is a single `Effect.tryPromise` call that delegates\n// the RFC work to the library and normalises the failure surface into\n// `OAuth2Error`.\n//\n// What stays hand-rolled:\n// - `OAuth2Error` — our tagged error; we want a stable shape across\n// every token-endpoint call\n// - `shouldRefreshToken` — skew check, trivial\n// - `buildAuthorizationUrl` — the library doesn't expose a raw\n// authorization-URL builder (it prefers PAR); a 30-line manual\n// construction keeps the call sync and lets callers opt out of PAR\n// ---------------------------------------------------------------------------\n\nimport { Data, Effect, Predicate } from \"effect\";\nimport * as oauth from \"oauth4webapi\";\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nexport class OAuth2Error extends Data.TaggedError(\"OAuth2Error\")<{\n readonly message: string;\n /**\n * RFC 6749 §5.2 error code, when the token endpoint returned one\n * (`invalid_grant`, `invalid_client`, `unauthorized_client`, ...).\n * Callers use this to distinguish terminal failures (a refresh token\n * the AS no longer honours → re-auth required) from transient ones.\n */\n readonly error?: string;\n readonly cause?: unknown;\n}> {}\n\n// ---------------------------------------------------------------------------\n// Token response shape (RFC 6749 §5.1)\n// ---------------------------------------------------------------------------\n\nexport type OAuth2TokenResponse = {\n readonly access_token: string;\n readonly token_type?: string;\n readonly refresh_token?: string;\n readonly expires_in?: number;\n readonly scope?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Refresh tokens this many ms before expiry to avoid mid-request expiration. */\nexport const OAUTH2_REFRESH_SKEW_MS = 60_000;\n\n/** Default token-endpoint timeout. */\nexport const OAUTH2_DEFAULT_TIMEOUT_MS = 20_000;\n\nexport interface OAuthEndpointUrlPolicy {\n readonly allowHttp?: boolean;\n}\n\nconst isLoopbackHttpUrl = (value: string): boolean => {\n if (!URL.canParse(value)) return false;\n const url = new URL(value);\n if (url.protocol !== \"http:\") return false;\n const hostname = url.hostname.toLowerCase();\n return (\n hostname === \"localhost\" ||\n hostname === \"0.0.0.0\" ||\n hostname === \"::1\" ||\n hostname === \"[::1]\" ||\n hostname.startsWith(\"127.\")\n );\n};\n\nexport const isSupportedOAuthEndpointUrl = (\n value: string,\n policy: OAuthEndpointUrlPolicy = {},\n): boolean => {\n if (!URL.canParse(value)) return false;\n const url = new URL(value);\n return (\n url.protocol === \"https:\" ||\n isLoopbackHttpUrl(value) ||\n (url.protocol === \"http:\" && policy.allowHttp === true)\n );\n};\n\nexport const assertSupportedOAuthEndpointUrl = (\n value: string,\n label = \"OAuth endpoint URL\",\n policy: OAuthEndpointUrlPolicy = {},\n): string => {\n if (isSupportedOAuthEndpointUrl(value, policy)) return value;\n // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: synchronous assertion helper used by URL constructors and Effect.try wrappers\n throw new TypeError(`${label} must use https: or loopback http:`);\n};\n\n// ---------------------------------------------------------------------------\n// PKCE (RFC 7636) — straight delegation to `oauth4webapi`\n// ---------------------------------------------------------------------------\n\nexport const createPkceCodeVerifier = (): string => oauth.generateRandomCodeVerifier();\n\nexport const createPkceCodeChallenge = (verifier: string): Promise<string> =>\n oauth.calculatePKCECodeChallenge(verifier);\n\n/** RFC 6749 `state` — an unguessable correlation token minted by `oauth.start`\n * and redeemed by `oauth.complete`. */\nexport const createOAuthState = (): string => oauth.generateRandomState();\n\n// ---------------------------------------------------------------------------\n// Authorization URL builder\n// ---------------------------------------------------------------------------\n\nexport type BuildAuthorizationUrlInput = {\n readonly authorizationUrl: string;\n readonly clientId: string;\n readonly redirectUrl: string;\n readonly scopes: readonly string[];\n readonly state: string;\n /** Pre-computed base64url S256 challenge (from `createPkceCodeChallenge`). */\n readonly codeChallenge: string;\n /** Separator between scopes. RFC 6749 says space; some providers use comma. */\n readonly scopeSeparator?: string;\n /** RFC 8707 Resource Indicator. MCP Authorization 2025-06-18 §\"Resource\n * Parameter Implementation\" requires clients to send this on every\n * authorization request, regardless of AS support. */\n readonly resource?: string;\n /** Provider-specific extras (e.g. Google's `access_type=offline`). */\n readonly extraParams?: Readonly<Record<string, string>>;\n readonly endpointUrlPolicy?: OAuthEndpointUrlPolicy;\n};\n\n/** Build an RFC 6749 §4.1.1 authorization URL. Sync; pre-computed\n * challenge lets this stay out of the Promise world. */\nexport const buildAuthorizationUrl = (input: BuildAuthorizationUrlInput): string => {\n const url = new URL(\n assertSupportedOAuthEndpointUrl(\n input.authorizationUrl,\n \"Authorization URL\",\n input.endpointUrlPolicy,\n ),\n );\n // Benign default kept by design: a single space is the RFC 6749 scope\n // separator. Callers targeting a legacy comma-separated provider pass\n // `scopeSeparator` explicitly (see the field's JSDoc).\n const separator = input.scopeSeparator ?? \" \";\n url.searchParams.set(\"client_id\", input.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUrl);\n url.searchParams.set(\"response_type\", \"code\");\n if (input.scopes.length > 0) {\n url.searchParams.set(\"scope\", input.scopes.join(separator));\n }\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n if (input.resource) {\n url.searchParams.set(\"resource\", input.resource);\n }\n if (input.extraParams) {\n for (const [k, v] of Object.entries(input.extraParams)) {\n url.searchParams.set(k, v);\n }\n }\n return url.toString();\n};\n\n/** Provider-specific authorize-URL extras that are NOT RFC 6749 params, so the\n * generic flow must add them per-provider (keyed off the authorization host).\n *\n * Google: `access_type=offline` + `prompt=consent` are required to receive (and\n * keep receiving, across reconnects / scope changes) a REFRESH TOKEN — without\n * them Google issues an access-token-only grant that dies in ~1h and a\n * re-consent can silently keep the old scope set. Do not add\n * `include_granted_scopes=true` here: with historical grants on the same Google\n * consent app, Google folds those unrelated scopes into the new consent flow and\n * can fail inside accounts.google.com before returning to our callback. */\nexport const providerAuthorizeExtras = (\n authorizationUrl: string,\n): Readonly<Record<string, string>> => {\n // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: URL() throws on invalid input → no provider extras\n try {\n const host = new URL(authorizationUrl).host.toLowerCase();\n if (host === \"accounts.google.com\") {\n return { access_type: \"offline\", prompt: \"consent\" };\n }\n } catch {\n // Unparseable authorization URL — let buildAuthorizationUrl surface the error.\n }\n return {};\n};\n\n// ---------------------------------------------------------------------------\n// Regional token-endpoint rebind\n//\n// Some authorization servers publish a single static metadata document that\n// advertises one region's token endpoint, but issue authorization codes that\n// are only redeemable at the *regional* host the user's org actually lives on.\n// The region comes back on the callback as a non-standard `domain` (or `site`)\n// query param: Datadog returns `domain=us5.datadoghq.com` while its metadata\n// statically advertises `app.datadoghq.com`. Redeeming the code at the\n// advertised host then fails with `invalid_grant`.\n//\n// `rebindTokenEndpointHostToCallbackDomain` swaps ONLY the hostname of the\n// configured token URL to the callback-supplied host, and ONLY when that host\n// is a sibling subdomain of the configured one (same parent after stripping the\n// leftmost DNS label, e.g. `app.datadoghq.com` and `us5.datadoghq.com` both\n// reduce to `datadoghq.com`). The token request carries the client secret, the\n// code, and the PKCE verifier, so an attacker-influenced `domain` must never be\n// able to point it at an arbitrary origin. Anything that fails the sibling\n// check, fails to parse, or isn't https falls back to the configured URL\n// unchanged.\n// ---------------------------------------------------------------------------\n\nconst hostnameFromCallbackDomain = (callbackDomain: string): string | undefined => {\n const trimmed = callbackDomain.trim();\n if (trimmed.length === 0) return undefined;\n // Datadog sends `domain` as a bare host and `site` as a full origin; accept\n // either by tolerating an optional scheme, then taking only the hostname.\n const candidate = trimmed.includes(\"://\") ? trimmed : `https://${trimmed}`;\n if (!URL.canParse(candidate)) return undefined;\n const url = new URL(candidate);\n // A legitimate regional host carries no port, credentials, or path.\n if (url.port !== \"\" || url.username !== \"\" || url.password !== \"\") return undefined;\n if (url.pathname !== \"/\" && url.pathname !== \"\") return undefined;\n return url.hostname.toLowerCase();\n};\n\n/** Parent domain after stripping the leftmost DNS label, or `undefined` when\n * the host has no sibling space (a single label, or a parent that is a bare\n * TLD). `app.datadoghq.com` -> `datadoghq.com`; `foo.com` -> undefined. */\nconst siblingParentDomainOf = (hostname: string): string | undefined => {\n const labels = hostname.split(\".\");\n if (labels.length < 3) return undefined;\n const parent = labels.slice(1).join(\".\");\n // Require the parent to itself be multi-label so a 2-label configured host\n // can never rebind across an entire TLD (e.g. foo.com -> bar.com).\n return parent.includes(\".\") ? parent : undefined;\n};\n\nexport const rebindTokenEndpointHostToCallbackDomain = (\n configuredTokenUrl: string,\n callbackDomain: string | null | undefined,\n): string => {\n if (!callbackDomain) return configuredTokenUrl;\n if (!URL.canParse(configuredTokenUrl)) return configuredTokenUrl;\n const configured = new URL(configuredTokenUrl);\n if (configured.protocol !== \"https:\") return configuredTokenUrl;\n const targetHost = hostnameFromCallbackDomain(callbackDomain);\n if (!targetHost) return configuredTokenUrl;\n const configuredHost = configured.hostname.toLowerCase();\n if (targetHost === configuredHost) return configuredTokenUrl;\n const configuredParent = siblingParentDomainOf(configuredHost);\n const targetParent = siblingParentDomainOf(targetHost);\n if (!configuredParent || !targetParent || configuredParent !== targetParent) {\n return configuredTokenUrl;\n }\n const rebound = new URL(configuredTokenUrl);\n rebound.hostname = targetHost;\n return rebound.toString();\n};\n\n// ---------------------------------------------------------------------------\n// Error mapping — `oauth4webapi`'s `process*Response` failure shapes are\n// either a WWW-Authenticate challenge or an RFC 6749 §5.2 error body,\n// both exposed via `.error` / `.error_description`. Probing the envelope\n// preserves RFC 6749 error-code semantics (e.g., mapping `invalid_grant`\n// to reauth-required) across wrappers.\n// ---------------------------------------------------------------------------\n\nconst isOAuth2Error = Predicate.isTagged(\"OAuth2Error\") as (cause: unknown) => cause is OAuth2Error;\n\nconst responseFromOAuthErrorCause = (cause: unknown): Response | undefined => {\n if (cause instanceof Response) return cause;\n if (typeof cause !== \"object\" || cause === null) return undefined;\n const envelope = cause as {\n readonly cause?: unknown;\n readonly response?: unknown;\n };\n if (envelope.response instanceof Response) return envelope.response;\n if (envelope.cause instanceof Response) return envelope.cause;\n return undefined;\n};\n\nconst redactTokenEndpointBody = (body: string): string =>\n body\n .replaceAll(\n /(\"(?:access_token|refresh_token|id_token|client_secret)\"\\s*:\\s*\")[^\"]*(\")/gi,\n \"$1[redacted]$2\",\n )\n .replaceAll(\n /((?:access_token|refresh_token|id_token|client_secret|code)=)[^&\\s]*/gi,\n \"$1[redacted]\",\n );\n\nconst tokenEndpointHttpSummary = async (response: Response): Promise<string> => {\n const status = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : \"\"}`;\n const contentType = response.headers.get(\"content-type\");\n const url = response.url ? ` from ${response.url}` : \"\";\n const parts = [`${status}${url}`];\n if (contentType) parts.push(`content-type ${contentType}`);\n const preview = await bodyPreviewFromResponse(response);\n if (preview) parts.push(`body: ${preview}`);\n return parts.join(\"; \");\n};\n\nconst bodyPreviewFromResponse = async (response: Response): Promise<string | undefined> => {\n const text = await Promise.resolve()\n .then(() => response.clone().text())\n .then(\n (value) => value.trim(),\n () => \"\",\n );\n if (!text) return undefined;\n const redacted = redactTokenEndpointBody(text.replaceAll(/\\s+/g, \" \"));\n return redacted.length > 500 ? `${redacted.slice(0, 500)}...` : redacted;\n};\n\nconst toOAuth2Error = (cause: unknown): OAuth2Error => {\n if (isOAuth2Error(cause)) return cause;\n if (typeof cause === \"object\" && cause !== null) {\n const c = cause as {\n error?: unknown;\n error_description?: unknown;\n message?: unknown;\n };\n const code = typeof c.error === \"string\" ? c.error : undefined;\n const description =\n typeof c.error_description === \"string\"\n ? c.error_description\n : typeof c.message === \"string\"\n ? c.message\n : undefined;\n return new OAuth2Error({\n message: `OAuth token exchange failed: ${description ?? code ?? \"unknown error\"}`,\n error: code,\n cause,\n });\n }\n return new OAuth2Error({\n message: \"OAuth token exchange failed\",\n cause,\n });\n};\n\nconst toOAuth2ErrorWithHttpSummary = (cause: unknown): Effect.Effect<OAuth2Error> => {\n if (isOAuth2Error(cause)) return Effect.succeed(cause);\n const base = toOAuth2Error(cause);\n const response = responseFromOAuthErrorCause(cause);\n if (!response) return Effect.succeed(base);\n return Effect.promise(() => tokenEndpointHttpSummary(response)).pipe(\n Effect.map(\n (summary) =>\n new OAuth2Error({\n message: `${base.message} (${summary})`,\n error: base.error,\n cause,\n }),\n ),\n );\n};\n\nconst failOAuth2WithHttpSummary = (cause: unknown): Effect.Effect<never, OAuth2Error> =>\n toOAuth2ErrorWithHttpSummary(cause).pipe(Effect.flatMap((error) => Effect.fail(error)));\n\n// ---------------------------------------------------------------------------\n// oauth4webapi adapter helpers\n// ---------------------------------------------------------------------------\n\nexport type ClientAuthMethod = \"body\" | \"basic\";\n\n/**\n * The token-endpoint client-auth transport used when a caller doesn't specify\n * one. `\"body\"` is `client_secret_post` (the secret in the form body) — the\n * method our DCR registers (`token_endpoint_auth_method: client_secret_post`)\n * and the one every confidential client in the v2 model uses. EXPLICIT and\n * documented rather than a hidden inline `?? \"body\"`: callers that need\n * `client_secret_basic` pass `clientAuth: \"basic\"`. For PUBLIC clients (no\n * secret) the method is irrelevant — `pickClientAuth` returns `None()`.\n */\nexport const DEFAULT_CLIENT_AUTH_METHOD: ClientAuthMethod = \"body\";\n\nconst asFromTokenUrl = (\n tokenUrl: string,\n endpointUrlPolicy: OAuthEndpointUrlPolicy = {},\n): oauth.AuthorizationServer => {\n assertSupportedOAuthEndpointUrl(tokenUrl, \"Token URL\", endpointUrlPolicy);\n const url = new URL(tokenUrl);\n return {\n issuer: `${url.protocol}//${url.host}`,\n token_endpoint: tokenUrl,\n };\n};\n\nconst asFromTokenUrlAndIssuer = (\n tokenUrl: string,\n issuerUrl: string | null | undefined,\n options: {\n readonly idTokenSigningAlgValuesSupported?: readonly string[];\n readonly endpointUrlPolicy?: OAuthEndpointUrlPolicy;\n } = {},\n): oauth.AuthorizationServer => {\n const as = asFromTokenUrl(tokenUrl, options.endpointUrlPolicy);\n const withIssuer = issuerUrl ? { ...as, issuer: issuerUrl } : as;\n return options.idTokenSigningAlgValuesSupported\n ? {\n ...withIssuer,\n id_token_signing_alg_values_supported: [...options.idTokenSigningAlgValuesSupported],\n }\n : withIssuer;\n};\n\nconst oauth4webapiRequestOptions = (\n targetUrl: string,\n timeoutMs: number | undefined,\n endpointUrlPolicy: OAuthEndpointUrlPolicy = {},\n): Record<string, unknown> => {\n const options: Record<string, unknown> = {\n signal: AbortSignal.timeout(timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS),\n };\n if (\n isLoopbackHttpUrl(targetUrl) ||\n (URL.canParse(targetUrl) &&\n new URL(targetUrl).protocol === \"http:\" &&\n endpointUrlPolicy.allowHttp === true)\n ) {\n (options as { [oauth.allowInsecureRequests]?: boolean })[oauth.allowInsecureRequests] = true;\n }\n return options;\n};\n\n// Select the token-endpoint client authentication. The secret's presence is the\n// EXPLICIT public-vs-confidential discriminator in the v2 model: a registered\n// client either has a secret (confidential — authenticate it) or has none\n// (public PKCE — `None()`, RFC 7636). This is not a silent guess: `loadClient`\n// persists a non-empty secret for confidential clients and null/\"\" for public\n// ones, so an absent secret here unambiguously means \"public client\". The\n// `method` only chooses HOW a present secret is sent (post vs basic).\nconst pickClientAuth = (\n clientSecret: string | null | undefined,\n method: ClientAuthMethod,\n): oauth.ClientAuth => {\n if (!clientSecret) return oauth.None();\n return method === \"basic\"\n ? oauth.ClientSecretBasic(clientSecret)\n : oauth.ClientSecretPost(clientSecret);\n};\n\nconst tokenResponseFrom = (r: oauth.TokenEndpointResponse): OAuth2TokenResponse => ({\n access_token: r.access_token,\n token_type: r.token_type,\n refresh_token: r.refresh_token,\n expires_in: typeof r.expires_in === \"number\" ? r.expires_in : undefined,\n scope: r.scope,\n});\n\n// MCP source connections are pure OAuth 2.0 — we never request `openid` and\n// never consume `id_token`. Some providers (PostHog, etc.) front an OIDC\n// backend and emit an `id_token` anyway; oauth4webapi then strict-validates\n// its claims against the AS metadata and rejects mismatches we don't care\n// about. Strip the field before delegation.\nconst stripIdToken = async (response: Response): Promise<Response> => {\n const body = await response\n .clone()\n .json()\n .then(\n (value: unknown) => value,\n () => null,\n );\n if (!body || typeof body !== \"object\" || !(\"id_token\" in (body as Record<string, unknown>))) {\n return response;\n }\n const { id_token: _ignored, ...rest } = body as Record<string, unknown>;\n return new Response(JSON.stringify(rest), {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n};\n\nconst processTokenEndpointResponse = async (\n as: oauth.AuthorizationServer,\n client: oauth.Client,\n response: Response,\n): Promise<OAuth2TokenResponse> =>\n tokenResponseFrom(\n await oauth.processGenericTokenEndpointResponse(as, client, await stripIdToken(response)),\n );\n\n// ---------------------------------------------------------------------------\n// Exchange authorization code → tokens\n// ---------------------------------------------------------------------------\n\nexport type ExchangeAuthorizationCodeInput = {\n readonly tokenUrl: string;\n readonly issuerUrl?: string | null;\n readonly clientId: string;\n readonly clientSecret?: string | null;\n readonly redirectUrl: string;\n readonly codeVerifier: string;\n readonly code: string;\n readonly clientAuth?: ClientAuthMethod;\n readonly idTokenSigningAlgValuesSupported?: readonly string[];\n /** RFC 8707 Resource Indicator. MCP Auth spec MUST-requires this on\n * the token request when the client knows the resource it intends\n * to call. */\n readonly resource?: string;\n readonly timeoutMs?: number;\n readonly endpointUrlPolicy?: OAuthEndpointUrlPolicy;\n};\n\nexport const exchangeAuthorizationCode = (\n input: ExchangeAuthorizationCodeInput,\n): Effect.Effect<OAuth2TokenResponse, OAuth2Error> =>\n Effect.tryPromise({\n try: async () => {\n const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, {\n idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported,\n endpointUrlPolicy: input.endpointUrlPolicy,\n });\n const client: oauth.Client = { client_id: input.clientId };\n const clientAuth = pickClientAuth(\n input.clientSecret,\n input.clientAuth ?? DEFAULT_CLIENT_AUTH_METHOD,\n );\n // `authorizationCodeGrantRequest` requires its `callbackParameters`\n // to have been returned from `validateAuthResponse`. Our public API\n // takes the `code` directly (the UI already validated `state` by\n // looking up the session), so skip the library's state-validation\n // rail and go through the generic grant request instead.\n const params = new URLSearchParams({\n code: input.code,\n redirect_uri: input.redirectUrl,\n code_verifier: input.codeVerifier,\n });\n if (input.resource) {\n params.set(\"resource\", input.resource);\n }\n const response = await oauth.genericTokenEndpointRequest(\n as,\n client,\n clientAuth,\n \"authorization_code\",\n params,\n oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs, input.endpointUrlPolicy),\n );\n return await processTokenEndpointResponse(as, client, response);\n },\n catch: (cause) => cause,\n }).pipe(Effect.catch(failOAuth2WithHttpSummary));\n\n// ---------------------------------------------------------------------------\n// Exchange client credentials → tokens (RFC 6749 §4.4)\n// ---------------------------------------------------------------------------\n\nexport type ExchangeClientCredentialsInput = {\n readonly tokenUrl: string;\n readonly clientId: string;\n readonly clientSecret: string;\n readonly scopes?: readonly string[];\n readonly scopeSeparator?: string;\n readonly clientAuth?: ClientAuthMethod;\n /** RFC 8707 Resource Indicator. MCP Authorization 2025-06-18 requires this\n * on token requests when the client knows the protected resource. */\n readonly resource?: string;\n readonly timeoutMs?: number;\n readonly endpointUrlPolicy?: OAuthEndpointUrlPolicy;\n};\n\nexport const exchangeClientCredentials = (\n input: ExchangeClientCredentialsInput,\n): Effect.Effect<OAuth2TokenResponse, OAuth2Error> =>\n Effect.tryPromise({\n try: async () => {\n const as = asFromTokenUrl(input.tokenUrl, input.endpointUrlPolicy);\n const client: oauth.Client = { client_id: input.clientId };\n const clientAuth = pickClientAuth(\n input.clientSecret,\n input.clientAuth ?? DEFAULT_CLIENT_AUTH_METHOD,\n );\n const params = new URLSearchParams();\n if (input.scopes && input.scopes.length > 0) {\n params.set(\"scope\", input.scopes.join(input.scopeSeparator ?? \" \"));\n }\n if (input.resource) {\n params.set(\"resource\", input.resource);\n }\n const response = await oauth.clientCredentialsGrantRequest(\n as,\n client,\n clientAuth,\n params,\n oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs, input.endpointUrlPolicy),\n );\n const result = await oauth.processClientCredentialsResponse(as, client, response);\n return tokenResponseFrom(result);\n },\n catch: (cause) => cause,\n }).pipe(Effect.catch(failOAuth2WithHttpSummary));\n\n// ---------------------------------------------------------------------------\n// Refresh access token\n// ---------------------------------------------------------------------------\n\nexport type RefreshAccessTokenInput = {\n readonly tokenUrl: string;\n readonly issuerUrl?: string | null;\n readonly clientId: string;\n readonly clientSecret?: string | null;\n readonly refreshToken: string;\n readonly scopes?: readonly string[];\n readonly scopeSeparator?: string;\n readonly clientAuth?: ClientAuthMethod;\n readonly idTokenSigningAlgValuesSupported?: readonly string[];\n /** RFC 8707 Resource Indicator — MCP spec MUST-requires this on\n * refresh requests so the new access token's audience is bound to\n * the same resource. */\n readonly resource?: string;\n readonly timeoutMs?: number;\n readonly endpointUrlPolicy?: OAuthEndpointUrlPolicy;\n};\n\nexport const refreshAccessToken = (\n input: RefreshAccessTokenInput,\n): Effect.Effect<OAuth2TokenResponse, OAuth2Error> =>\n Effect.tryPromise({\n try: async () => {\n const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, {\n idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported,\n endpointUrlPolicy: input.endpointUrlPolicy,\n });\n const client: oauth.Client = { client_id: input.clientId };\n const clientAuth = pickClientAuth(\n input.clientSecret,\n input.clientAuth ?? DEFAULT_CLIENT_AUTH_METHOD,\n );\n const extraParams = new URLSearchParams();\n if (input.scopes && input.scopes.length > 0) {\n extraParams.set(\"scope\", input.scopes.join(input.scopeSeparator ?? \" \"));\n }\n if (input.resource) {\n extraParams.set(\"resource\", input.resource);\n }\n const additionalParameters =\n Array.from(extraParams.keys()).length > 0 ? extraParams : undefined;\n const response = await oauth.refreshTokenGrantRequest(\n as,\n client,\n clientAuth,\n input.refreshToken,\n {\n ...oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs, input.endpointUrlPolicy),\n additionalParameters,\n },\n );\n const result = await oauth.processRefreshTokenResponse(\n as,\n client,\n await stripIdToken(response),\n );\n return tokenResponseFrom(result);\n },\n catch: (cause) => cause,\n }).pipe(Effect.catch(failOAuth2WithHttpSummary));\n\n// ---------------------------------------------------------------------------\n// Refresh-needed predicate\n// ---------------------------------------------------------------------------\n\nexport const shouldRefreshToken = (input: {\n readonly expiresAt: number | null;\n readonly now?: number;\n readonly skewMs?: number;\n}): boolean => {\n if (input.expiresAt === null) return false;\n const now = input.now ?? Date.now();\n const skew = input.skewMs ?? OAUTH2_REFRESH_SKEW_MS;\n return input.expiresAt <= now + skew;\n};\n"],"mappings":"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","names":[]}
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
isValidPattern,
|
|
25
25
|
resolveEffectivePolicy,
|
|
26
26
|
rowToToolPolicy
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-PCSRC6WP.js";
|
|
28
28
|
import {
|
|
29
29
|
OAUTH2_DEFAULT_TIMEOUT_MS,
|
|
30
30
|
assertSupportedOAuthEndpointUrl,
|
|
@@ -35,9 +35,10 @@ import {
|
|
|
35
35
|
exchangeAuthorizationCode,
|
|
36
36
|
exchangeClientCredentials,
|
|
37
37
|
providerAuthorizeExtras,
|
|
38
|
+
rebindTokenEndpointHostToCallbackDomain,
|
|
38
39
|
refreshAccessToken,
|
|
39
40
|
shouldRefreshToken
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-4XPVLX62.js";
|
|
41
42
|
import {
|
|
42
43
|
AuthTemplateSlug,
|
|
43
44
|
ConnectionAddress,
|
|
@@ -549,6 +550,11 @@ var coreTables = defineTables({
|
|
|
549
550
|
refresh_item_id: nullableTextColumn("refresh_item_id"),
|
|
550
551
|
expires_at: nullableBigintColumn("expires_at"),
|
|
551
552
|
oauth_scope: nullableTextColumn("oauth_scope"),
|
|
553
|
+
// Per-connection token endpoint override. Set only when the code was
|
|
554
|
+
// redeemed at a region other than the oauth_client's configured token host
|
|
555
|
+
// (multi-site providers like Datadog signal the org's region on the
|
|
556
|
+
// callback). Null means refresh uses the oauth_client's `token_url`.
|
|
557
|
+
oauth_token_url: nullableTextColumn("oauth_token_url"),
|
|
552
558
|
provider_state: nullableJsonColumn("provider_state"),
|
|
553
559
|
created_at: dateColumn("created_at"),
|
|
554
560
|
updated_at: dateColumn("updated_at")
|
|
@@ -3114,10 +3120,26 @@ var ConnectionOutput = Schema2.Struct({
|
|
|
3114
3120
|
});
|
|
3115
3121
|
var ConnectionsListInput = Schema2.Struct({
|
|
3116
3122
|
integration: Schema2.optional(Schema2.String),
|
|
3117
|
-
owner: Schema2.optional(OwnerSchema)
|
|
3123
|
+
owner: Schema2.optional(OwnerSchema),
|
|
3124
|
+
verbose: Schema2.optional(Schema2.Boolean)
|
|
3125
|
+
});
|
|
3126
|
+
var ConnectionListItem = Schema2.Struct({
|
|
3127
|
+
owner: OwnerSchema,
|
|
3128
|
+
name: Schema2.String,
|
|
3129
|
+
integration: Schema2.String,
|
|
3130
|
+
template: Schema2.String,
|
|
3131
|
+
provider: Schema2.String,
|
|
3132
|
+
address: Schema2.String,
|
|
3133
|
+
identityLabel: Schema2.optional(Schema2.NullOr(Schema2.String)),
|
|
3134
|
+
description: Schema2.optional(Schema2.NullOr(Schema2.String)),
|
|
3135
|
+
expiresAt: Schema2.NullOr(Schema2.Number),
|
|
3136
|
+
oauthClient: Schema2.NullOr(Schema2.String),
|
|
3137
|
+
oauthClientOwner: Schema2.NullOr(OwnerSchema),
|
|
3138
|
+
oauthScopeCount: Schema2.NullOr(Schema2.Number),
|
|
3139
|
+
oauthScope: Schema2.optional(Schema2.NullOr(Schema2.String))
|
|
3118
3140
|
});
|
|
3119
3141
|
var ConnectionsListOutput = Schema2.Struct({
|
|
3120
|
-
connections: Schema2.Array(
|
|
3142
|
+
connections: Schema2.Array(ConnectionListItem)
|
|
3121
3143
|
});
|
|
3122
3144
|
var ConnectionCreateHandoffInput = Schema2.Struct({
|
|
3123
3145
|
integration: Schema2.String,
|
|
@@ -3332,6 +3354,22 @@ var connectionToOutput = (connection) => ({
|
|
|
3332
3354
|
oauthClientOwner: connection.oauthClientOwner ?? null,
|
|
3333
3355
|
oauthScope: connection.oauthScope ?? null
|
|
3334
3356
|
});
|
|
3357
|
+
var oauthScopeCount = (scope) => scope == null ? null : scope.split(/\s+/).filter(Boolean).length;
|
|
3358
|
+
var connectionToListItem = (connection, verbose) => ({
|
|
3359
|
+
owner: connection.owner,
|
|
3360
|
+
name: String(connection.name),
|
|
3361
|
+
integration: String(connection.integration),
|
|
3362
|
+
template: String(connection.template),
|
|
3363
|
+
provider: String(connection.provider),
|
|
3364
|
+
address: String(connection.address),
|
|
3365
|
+
identityLabel: connection.identityLabel ?? null,
|
|
3366
|
+
description: connection.description ?? null,
|
|
3367
|
+
expiresAt: connection.expiresAt ?? null,
|
|
3368
|
+
oauthClient: connection.oauthClient == null ? null : String(connection.oauthClient),
|
|
3369
|
+
oauthClientOwner: connection.oauthClientOwner ?? null,
|
|
3370
|
+
oauthScopeCount: oauthScopeCount(connection.oauthScope),
|
|
3371
|
+
...verbose ? { oauthScope: connection.oauthScope ?? null } : {}
|
|
3372
|
+
});
|
|
3335
3373
|
var toolToOutput = (toolRow) => ({
|
|
3336
3374
|
address: String(toolRow.address),
|
|
3337
3375
|
owner: toolRow.owner,
|
|
@@ -3430,7 +3468,7 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
|
|
|
3430
3468
|
}),
|
|
3431
3469
|
tool({
|
|
3432
3470
|
name: "connections.list",
|
|
3433
|
-
description: "List saved connections (the credential for one integration). Never returns the credential value. Optionally filter by integration or owner.",
|
|
3471
|
+
description: "List saved connections (the credential for one integration). Never returns the credential value. Optionally filter by integration or owner. OAuth scopes are summarized as `oauthScopeCount` by default; pass `verbose: true` to include the full `oauthScope` grant string per connection.",
|
|
3434
3472
|
inputSchema: ConnectionsListInputStd,
|
|
3435
3473
|
outputSchema: ConnectionsListOutputStd,
|
|
3436
3474
|
execute: (input, { ctx }) => Effect5.map(
|
|
@@ -3439,7 +3477,9 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
|
|
|
3439
3477
|
owner: input.owner === void 0 ? void 0 : input.owner
|
|
3440
3478
|
}),
|
|
3441
3479
|
(connections) => ({
|
|
3442
|
-
connections: connections.map(
|
|
3480
|
+
connections: connections.map(
|
|
3481
|
+
(connection) => connectionToListItem(connection, input.verbose === true)
|
|
3482
|
+
)
|
|
3443
3483
|
})
|
|
3444
3484
|
)
|
|
3445
3485
|
}),
|
|
@@ -4339,7 +4379,9 @@ var makeOAuthService = (deps) => {
|
|
|
4339
4379
|
client,
|
|
4340
4380
|
token,
|
|
4341
4381
|
requestedScopes,
|
|
4342
|
-
input.clientOwner
|
|
4382
|
+
input.clientOwner,
|
|
4383
|
+
// client_credentials has no callback, so no regional rebind applies.
|
|
4384
|
+
null
|
|
4343
4385
|
).pipe(
|
|
4344
4386
|
Effect7.mapError(
|
|
4345
4387
|
(cause) => new OAuthStartError({
|
|
@@ -4452,8 +4494,12 @@ var makeOAuthService = (deps) => {
|
|
|
4452
4494
|
restartRequired: true
|
|
4453
4495
|
});
|
|
4454
4496
|
}
|
|
4497
|
+
const tokenUrl = rebindTokenEndpointHostToCallbackDomain(
|
|
4498
|
+
client.tokenUrl,
|
|
4499
|
+
input.callbackDomain
|
|
4500
|
+
);
|
|
4455
4501
|
const token = yield* exchangeAuthorizationCode({
|
|
4456
|
-
tokenUrl
|
|
4502
|
+
tokenUrl,
|
|
4457
4503
|
clientId: client.clientId,
|
|
4458
4504
|
clientSecret: client.clientSecret,
|
|
4459
4505
|
redirectUrl: session.redirectUrl,
|
|
@@ -4483,7 +4529,10 @@ var makeOAuthService = (deps) => {
|
|
|
4483
4529
|
// The scopes `start` requested (the integration's declared set), persisted
|
|
4484
4530
|
// on the session. Empty only for a corrupt/legacy session with no payload.
|
|
4485
4531
|
session.requestedScopes ?? [],
|
|
4486
|
-
session.clientOwner
|
|
4532
|
+
session.clientOwner,
|
|
4533
|
+
// Persist the regional token endpoint ONLY when it differs from the
|
|
4534
|
+
// client's configured one, so refresh redeems against the same region.
|
|
4535
|
+
tokenUrl === client.tokenUrl ? null : tokenUrl
|
|
4487
4536
|
).pipe(
|
|
4488
4537
|
Effect7.mapError(
|
|
4489
4538
|
(cause) => new OAuthCompleteError({
|
|
@@ -4496,7 +4545,7 @@ var makeOAuthService = (deps) => {
|
|
|
4496
4545
|
yield* deleteSession(input.state);
|
|
4497
4546
|
return connection;
|
|
4498
4547
|
});
|
|
4499
|
-
const mintFromToken = (target, client, token, requestedScopes, clientOwner) => Effect7.gen(function* () {
|
|
4548
|
+
const mintFromToken = (target, client, token, requestedScopes, clientOwner, oauthTokenUrl) => Effect7.gen(function* () {
|
|
4500
4549
|
const provider = deps.defaultWritableProvider();
|
|
4501
4550
|
if (!provider || !provider.set) {
|
|
4502
4551
|
return yield* new StorageError({
|
|
@@ -4527,7 +4576,8 @@ var makeOAuthService = (deps) => {
|
|
|
4527
4576
|
// Microsoft, issue a refresh token for `offline_access` but omit that
|
|
4528
4577
|
// non-resource scope from the token `scope` string, so preserve it when
|
|
4529
4578
|
// the refresh token proves it was granted.
|
|
4530
|
-
oauthScope: recordedOAuthScope(token, requestedScopes)
|
|
4579
|
+
oauthScope: recordedOAuthScope(token, requestedScopes),
|
|
4580
|
+
oauthTokenUrl
|
|
4531
4581
|
});
|
|
4532
4582
|
});
|
|
4533
4583
|
const deleteSession = (state) => deps.fuma.use(
|
|
@@ -5249,8 +5299,9 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5249
5299
|
}
|
|
5250
5300
|
const clientSecret = clientRow.client_secret_item_id ? (yield* provider.get(ProviderItemId.make(String(clientRow.client_secret_item_id)))) ?? "" : "";
|
|
5251
5301
|
const grantedScopes = row.oauth_scope ? String(row.oauth_scope).split(/\s+/).filter(Boolean) : [];
|
|
5302
|
+
const tokenUrl = row.oauth_token_url ? String(row.oauth_token_url) : String(clientRow.token_url);
|
|
5252
5303
|
const token = String(clientRow.grant) === "client_credentials" ? yield* exchangeClientCredentials({
|
|
5253
|
-
tokenUrl
|
|
5304
|
+
tokenUrl,
|
|
5254
5305
|
clientId: String(clientRow.client_id),
|
|
5255
5306
|
clientSecret,
|
|
5256
5307
|
scopes: grantedScopes,
|
|
@@ -5277,7 +5328,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5277
5328
|
return yield* reauth("Stored refresh token could not be resolved.");
|
|
5278
5329
|
}
|
|
5279
5330
|
return yield* refreshAccessToken({
|
|
5280
|
-
tokenUrl
|
|
5331
|
+
tokenUrl,
|
|
5281
5332
|
clientId: String(clientRow.client_id),
|
|
5282
5333
|
clientSecret,
|
|
5283
5334
|
refreshToken,
|
|
@@ -5817,6 +5868,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5817
5868
|
refresh_item_id: input.refreshItemId,
|
|
5818
5869
|
expires_at: input.expiresAt,
|
|
5819
5870
|
oauth_scope: input.oauthScope,
|
|
5871
|
+
oauth_token_url: input.oauthTokenUrl ?? null,
|
|
5820
5872
|
updated_at: now
|
|
5821
5873
|
};
|
|
5822
5874
|
if (existing) {
|
|
@@ -5847,6 +5899,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5847
5899
|
refresh_item_id: input.refreshItemId,
|
|
5848
5900
|
expires_at: input.expiresAt,
|
|
5849
5901
|
oauth_scope: input.oauthScope,
|
|
5902
|
+
oauth_token_url: input.oauthTokenUrl ?? null,
|
|
5850
5903
|
provider_state: null,
|
|
5851
5904
|
created_at: now,
|
|
5852
5905
|
updated_at: now
|
|
@@ -5874,6 +5927,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5874
5927
|
refresh_item_id: input.refreshItemId,
|
|
5875
5928
|
expires_at: input.expiresAt,
|
|
5876
5929
|
oauth_scope: input.oauthScope,
|
|
5930
|
+
oauth_token_url: input.oauthTokenUrl ?? null,
|
|
5877
5931
|
provider_state: null,
|
|
5878
5932
|
created_at: now,
|
|
5879
5933
|
updated_at: now
|
|
@@ -6685,4 +6739,4 @@ export {
|
|
|
6685
6739
|
collectTables,
|
|
6686
6740
|
createExecutor
|
|
6687
6741
|
};
|
|
6688
|
-
//# sourceMappingURL=chunk-
|
|
6742
|
+
//# sourceMappingURL=chunk-5Q35SELN.js.map
|