@executor-js/sdk 1.5.15 → 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-QEKKFEJL.js → chunk-5Q35SELN.js} +100 -33
- 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/oauth-test-server.d.ts +1 -0
- package/dist/testing/oauth-test-server.d.ts.map +1 -1
- package/dist/testing.js +13 -5
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EIHWBY6T.js.map +0 -1
- package/dist/chunk-QEKKFEJL.js.map +0 -1
- package/dist/chunk-SK3PCJHJ.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
|
}),
|
|
@@ -4041,6 +4081,13 @@ var looseDb = (db) => db;
|
|
|
4041
4081
|
var accessItemId = (owner, integration, name) => `oauth:${owner}:${integration}:${name}`;
|
|
4042
4082
|
var refreshItemIdFor = (accessId) => `${accessId}:refresh`;
|
|
4043
4083
|
var dedupeScopes = (scopes) => [...new Set(scopes)];
|
|
4084
|
+
var recordedOAuthScope = (token, requestedScopes) => {
|
|
4085
|
+
if (token.scope == null) return requestedScopes.join(" ") || null;
|
|
4086
|
+
const granted = token.scope.split(/\s+/).filter(Boolean);
|
|
4087
|
+
const coveredByRefreshToken = token.refresh_token && requestedScopes.includes("offline_access") ? ["offline_access"] : [];
|
|
4088
|
+
const recorded = dedupeScopes([...granted, ...coveredByRefreshToken]);
|
|
4089
|
+
return recorded.join(" ") || null;
|
|
4090
|
+
};
|
|
4044
4091
|
var decodeJsonPayload = Schema4.decodeUnknownOption(Schema4.UnknownFromJsonString);
|
|
4045
4092
|
var requestedScopesFromPayload = (payload) => {
|
|
4046
4093
|
const decoded = typeof payload === "string" ? decodeJsonPayload(payload).pipe(Option2.getOrElse(() => payload)) : payload;
|
|
@@ -4332,7 +4379,9 @@ var makeOAuthService = (deps) => {
|
|
|
4332
4379
|
client,
|
|
4333
4380
|
token,
|
|
4334
4381
|
requestedScopes,
|
|
4335
|
-
input.clientOwner
|
|
4382
|
+
input.clientOwner,
|
|
4383
|
+
// client_credentials has no callback, so no regional rebind applies.
|
|
4384
|
+
null
|
|
4336
4385
|
).pipe(
|
|
4337
4386
|
Effect7.mapError(
|
|
4338
4387
|
(cause) => new OAuthStartError({
|
|
@@ -4445,8 +4494,12 @@ var makeOAuthService = (deps) => {
|
|
|
4445
4494
|
restartRequired: true
|
|
4446
4495
|
});
|
|
4447
4496
|
}
|
|
4497
|
+
const tokenUrl = rebindTokenEndpointHostToCallbackDomain(
|
|
4498
|
+
client.tokenUrl,
|
|
4499
|
+
input.callbackDomain
|
|
4500
|
+
);
|
|
4448
4501
|
const token = yield* exchangeAuthorizationCode({
|
|
4449
|
-
tokenUrl
|
|
4502
|
+
tokenUrl,
|
|
4450
4503
|
clientId: client.clientId,
|
|
4451
4504
|
clientSecret: client.clientSecret,
|
|
4452
4505
|
redirectUrl: session.redirectUrl,
|
|
@@ -4476,7 +4529,10 @@ var makeOAuthService = (deps) => {
|
|
|
4476
4529
|
// The scopes `start` requested (the integration's declared set), persisted
|
|
4477
4530
|
// on the session. Empty only for a corrupt/legacy session with no payload.
|
|
4478
4531
|
session.requestedScopes ?? [],
|
|
4479
|
-
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
|
|
4480
4536
|
).pipe(
|
|
4481
4537
|
Effect7.mapError(
|
|
4482
4538
|
(cause) => new OAuthCompleteError({
|
|
@@ -4489,7 +4545,7 @@ var makeOAuthService = (deps) => {
|
|
|
4489
4545
|
yield* deleteSession(input.state);
|
|
4490
4546
|
return connection;
|
|
4491
4547
|
});
|
|
4492
|
-
const mintFromToken = (target, client, token, requestedScopes, clientOwner) => Effect7.gen(function* () {
|
|
4548
|
+
const mintFromToken = (target, client, token, requestedScopes, clientOwner, oauthTokenUrl) => Effect7.gen(function* () {
|
|
4493
4549
|
const provider = deps.defaultWritableProvider();
|
|
4494
4550
|
if (!provider || !provider.set) {
|
|
4495
4551
|
return yield* new StorageError({
|
|
@@ -4516,12 +4572,12 @@ var makeOAuthService = (deps) => {
|
|
|
4516
4572
|
oauthClientOwner: clientOwner,
|
|
4517
4573
|
refreshItemId,
|
|
4518
4574
|
expiresAt: expiresAtFrom(token),
|
|
4519
|
-
//
|
|
4520
|
-
//
|
|
4521
|
-
//
|
|
4522
|
-
//
|
|
4523
|
-
|
|
4524
|
-
|
|
4575
|
+
// Record the granted scope the AS echoed back. Some providers, including
|
|
4576
|
+
// Microsoft, issue a refresh token for `offline_access` but omit that
|
|
4577
|
+
// non-resource scope from the token `scope` string, so preserve it when
|
|
4578
|
+
// the refresh token proves it was granted.
|
|
4579
|
+
oauthScope: recordedOAuthScope(token, requestedScopes),
|
|
4580
|
+
oauthTokenUrl
|
|
4525
4581
|
});
|
|
4526
4582
|
});
|
|
4527
4583
|
const deleteSession = (state) => deps.fuma.use(
|
|
@@ -4573,6 +4629,7 @@ var makeOAuthService = (deps) => {
|
|
|
4573
4629
|
|
|
4574
4630
|
// src/executor.ts
|
|
4575
4631
|
var PLUGIN_STORAGE_DELETE_KEY_BATCH_SIZE = 90;
|
|
4632
|
+
var PLUGIN_STORAGE_CREATE_ROW_BATCH_SIZE = 90;
|
|
4576
4633
|
var MAX_APPROVAL_ARGUMENT_PREVIEW_CHARS = 4e3;
|
|
4577
4634
|
var acceptAllHandler = () => Effect8.succeed(ElicitationResponse.make({ action: "accept" }));
|
|
4578
4635
|
var resolveElicitationHandler = (onElicitation) => onElicitation === "accept-all" ? acceptAllHandler : onElicitation;
|
|
@@ -4970,20 +5027,26 @@ var makePluginStorageFacade = (input) => {
|
|
|
4970
5027
|
if (uniqueEntries.length === 0) return;
|
|
4971
5028
|
yield* deleteManyImpl(owner, os.subject, uniqueEntries);
|
|
4972
5029
|
const now = /* @__PURE__ */ new Date();
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
5030
|
+
for (let offset = 0; offset < uniqueEntries.length; offset += PLUGIN_STORAGE_CREATE_ROW_BATCH_SIZE) {
|
|
5031
|
+
const batchEntries = uniqueEntries.slice(
|
|
5032
|
+
offset,
|
|
5033
|
+
offset + PLUGIN_STORAGE_CREATE_ROW_BATCH_SIZE
|
|
5034
|
+
);
|
|
5035
|
+
yield* input.core.createMany(
|
|
5036
|
+
"plugin_storage",
|
|
5037
|
+
batchEntries.map((entry) => ({
|
|
5038
|
+
tenant,
|
|
5039
|
+
owner: os.owner,
|
|
5040
|
+
subject: os.subject,
|
|
5041
|
+
plugin_id: input.pluginId,
|
|
5042
|
+
collection: entry.collection,
|
|
5043
|
+
key: entry.key,
|
|
5044
|
+
data: entry.data,
|
|
5045
|
+
created_at: now,
|
|
5046
|
+
updated_at: now
|
|
5047
|
+
}))
|
|
5048
|
+
);
|
|
5049
|
+
}
|
|
4987
5050
|
});
|
|
4988
5051
|
const removeManyImpl = (owner, entries) => Effect8.gen(function* () {
|
|
4989
5052
|
const os = ownerSubject(owner);
|
|
@@ -5236,8 +5299,9 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5236
5299
|
}
|
|
5237
5300
|
const clientSecret = clientRow.client_secret_item_id ? (yield* provider.get(ProviderItemId.make(String(clientRow.client_secret_item_id)))) ?? "" : "";
|
|
5238
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);
|
|
5239
5303
|
const token = String(clientRow.grant) === "client_credentials" ? yield* exchangeClientCredentials({
|
|
5240
|
-
tokenUrl
|
|
5304
|
+
tokenUrl,
|
|
5241
5305
|
clientId: String(clientRow.client_id),
|
|
5242
5306
|
clientSecret,
|
|
5243
5307
|
scopes: grantedScopes,
|
|
@@ -5264,7 +5328,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5264
5328
|
return yield* reauth("Stored refresh token could not be resolved.");
|
|
5265
5329
|
}
|
|
5266
5330
|
return yield* refreshAccessToken({
|
|
5267
|
-
tokenUrl
|
|
5331
|
+
tokenUrl,
|
|
5268
5332
|
clientId: String(clientRow.client_id),
|
|
5269
5333
|
clientSecret,
|
|
5270
5334
|
refreshToken,
|
|
@@ -5804,6 +5868,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5804
5868
|
refresh_item_id: input.refreshItemId,
|
|
5805
5869
|
expires_at: input.expiresAt,
|
|
5806
5870
|
oauth_scope: input.oauthScope,
|
|
5871
|
+
oauth_token_url: input.oauthTokenUrl ?? null,
|
|
5807
5872
|
updated_at: now
|
|
5808
5873
|
};
|
|
5809
5874
|
if (existing) {
|
|
@@ -5834,6 +5899,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5834
5899
|
refresh_item_id: input.refreshItemId,
|
|
5835
5900
|
expires_at: input.expiresAt,
|
|
5836
5901
|
oauth_scope: input.oauthScope,
|
|
5902
|
+
oauth_token_url: input.oauthTokenUrl ?? null,
|
|
5837
5903
|
provider_state: null,
|
|
5838
5904
|
created_at: now,
|
|
5839
5905
|
updated_at: now
|
|
@@ -5861,6 +5927,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
|
|
|
5861
5927
|
refresh_item_id: input.refreshItemId,
|
|
5862
5928
|
expires_at: input.expiresAt,
|
|
5863
5929
|
oauth_scope: input.oauthScope,
|
|
5930
|
+
oauth_token_url: input.oauthTokenUrl ?? null,
|
|
5864
5931
|
provider_state: null,
|
|
5865
5932
|
created_at: now,
|
|
5866
5933
|
updated_at: now
|
|
@@ -6672,4 +6739,4 @@ export {
|
|
|
6672
6739
|
collectTables,
|
|
6673
6740
|
createExecutor
|
|
6674
6741
|
};
|
|
6675
|
-
//# sourceMappingURL=chunk-
|
|
6742
|
+
//# sourceMappingURL=chunk-5Q35SELN.js.map
|