@zackbart/connecta 0.28.0 → 0.29.0
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/CHANGELOG.md +961 -5
- package/README.md +62 -39
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1632 -1509
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +526 -0
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +220 -0
- package/dist/connectors/api-connector.js +214 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +13 -99
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +108 -25
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1780 -351
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +25 -257
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-partition.js +17 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/basecamp/index.d.ts +43 -0
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/breeze/index.d.ts +34 -0
- package/dist/providers/breeze/index.js +1446 -0
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/ccb/index.d.ts +103 -0
- package/dist/providers/ccb/index.js +1725 -0
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/overflow/index.d.ts +60 -0
- package/dist/providers/overflow/index.js +1535 -0
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/planning-center/index.d.ts +59 -0
- package/dist/providers/planning-center/index.js +2199 -0
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/tithely/index.d.ts +49 -0
- package/dist/providers/tithely/index.js +1003 -0
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +136 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +665 -230
- package/documentation/auth.md +962 -458
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +125 -35
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { auth, OAuthError, UnauthorizedError } from "@modelcontextprotocol/client";
|
|
2
|
+
import { byteReadResponse } from "../byte-read-response.js";
|
|
3
|
+
import { sentSecretsFor, sentSecretsFetch, shortSecretWarning } from "../sent-secrets.js";
|
|
4
|
+
import { ConnectorCallError, msg } from "../errors.js";
|
|
5
|
+
import { oauthPartitionFor, retainOAuthPartition, retainingOAuthPartition } from "../oauth-partition.js";
|
|
6
|
+
import { oauthSealerFor } from "../oauth-sealing.js";
|
|
7
|
+
import { describeFailure } from "../operator-record.js";
|
|
8
|
+
import { assertOAuthScope, authorizingContext, KvOAuthProvider, refreshCoordinatorsByPartition, } from "./downstream-oauth.js";
|
|
9
|
+
import { trackRemoteClientRequest } from "./downstream-client-metadata.js";
|
|
10
|
+
import { trackOAuthStartReset } from "./oauth-start-reset.js";
|
|
11
|
+
/**
|
|
12
|
+
* Downstream OAuth for a hand-written `api()` connector: the authorization
|
|
13
|
+
* code grant against endpoints and a pre-registered client the deployment
|
|
14
|
+
* declares, rather than ones a server advertises.
|
|
15
|
+
*
|
|
16
|
+
* Everything that makes the `remoteMcp()` grant safe is reused, not copied.
|
|
17
|
+
* `StaticOAuthProvider` is a `KvOAuthProvider` — the same grant record and
|
|
18
|
+
* consents, sealing, owner partitions, epoch fence, and refresh-failure
|
|
19
|
+
* verdicts — whose discovery state and client are constants, and the SDK's
|
|
20
|
+
* own `auth()` drives it through the same `OAuthRefreshCoordinator`. What a
|
|
21
|
+
* static configuration subtracts is the network learning: no RFC 9728 or 8414
|
|
22
|
+
* discovery and no dynamic registration, so no URL here was taught to
|
|
23
|
+
* connecta by a downstream.
|
|
24
|
+
*/
|
|
25
|
+
/** Parameters the grant itself owns; a deployment may not restate them. */
|
|
26
|
+
const RESERVED_AUTHORIZATION_PARAMS = new Set([
|
|
27
|
+
"response_type",
|
|
28
|
+
"client_id",
|
|
29
|
+
"redirect_uri",
|
|
30
|
+
"state",
|
|
31
|
+
"scope",
|
|
32
|
+
"code_challenge",
|
|
33
|
+
"code_challenge_method",
|
|
34
|
+
"resource",
|
|
35
|
+
]);
|
|
36
|
+
/** Token-request headers the grant owns: client authentication and framing. */
|
|
37
|
+
const RESERVED_TOKEN_HEADERS = new Set([
|
|
38
|
+
"authorization",
|
|
39
|
+
"content-type",
|
|
40
|
+
"content-length",
|
|
41
|
+
"cookie",
|
|
42
|
+
"host",
|
|
43
|
+
]);
|
|
44
|
+
/** An RFC 9110 field-name token. */
|
|
45
|
+
const HEADER_NAME = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
46
|
+
const CLIENT_AUTHENTICATION = new Set(["client_secret_basic", "client_secret_post", "none"]);
|
|
47
|
+
function isLoopback(hostname) {
|
|
48
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
49
|
+
}
|
|
50
|
+
/** An absolute HTTPS URL (or HTTP on loopback), without credentials or fragment. */
|
|
51
|
+
function configuredUrl(id, option, value) {
|
|
52
|
+
let url;
|
|
53
|
+
try {
|
|
54
|
+
url = new URL(String(value));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Reported below without echoing the value.
|
|
58
|
+
}
|
|
59
|
+
if (!url ||
|
|
60
|
+
!(url.protocol === "https:" || (url.protocol === "http:" && isLoopback(url.hostname))) ||
|
|
61
|
+
url.username ||
|
|
62
|
+
url.password ||
|
|
63
|
+
url.hash) {
|
|
64
|
+
throw new Error(`[connecta] connector "${id}" oauth.${option} must be an absolute https URL ` +
|
|
65
|
+
"(http only on loopback), without credentials or a fragment.");
|
|
66
|
+
}
|
|
67
|
+
return url;
|
|
68
|
+
}
|
|
69
|
+
/** Structural mistakes throw here, where they are written. */
|
|
70
|
+
function settingsFor(id, config) {
|
|
71
|
+
if (!config || typeof config !== "object") {
|
|
72
|
+
throw new Error(`[connecta] connector "${id}" oauth must be an object.`);
|
|
73
|
+
}
|
|
74
|
+
const authorizationEndpoint = configuredUrl(id, "authorizationEndpoint", config.authorizationEndpoint);
|
|
75
|
+
const tokenEndpoint = configuredUrl(id, "tokenEndpoint", config.tokenEndpoint);
|
|
76
|
+
if (tokenEndpoint.search) {
|
|
77
|
+
throw new Error(`[connecta] connector "${id}" oauth.tokenEndpoint must not carry a query.`);
|
|
78
|
+
}
|
|
79
|
+
if (typeof config.clientId !== "string" || config.clientId.trim() === "") {
|
|
80
|
+
throw new Error(`[connecta] connector "${id}" oauth.clientId must be a non-empty string.`);
|
|
81
|
+
}
|
|
82
|
+
if (config.clientSecret !== undefined && (typeof config.clientSecret !== "string" || config.clientSecret === "")) {
|
|
83
|
+
// Usually an unset environment variable read as "". Never echo it.
|
|
84
|
+
throw new Error(`[connecta] connector "${id}" oauth.clientSecret must be a non-empty string when set.`);
|
|
85
|
+
}
|
|
86
|
+
const clientAuthentication = config.tokenEndpointAuthMethod ?? (config.clientSecret !== undefined ? "client_secret_basic" : "none");
|
|
87
|
+
if (!CLIENT_AUTHENTICATION.has(clientAuthentication)) {
|
|
88
|
+
throw new Error(`[connecta] connector "${id}" oauth.tokenEndpointAuthMethod must be ` +
|
|
89
|
+
'"client_secret_basic", "client_secret_post", or "none".');
|
|
90
|
+
}
|
|
91
|
+
if (clientAuthentication === "none" && config.clientSecret !== undefined) {
|
|
92
|
+
throw new Error(`[connecta] connector "${id}" oauth declares a clientSecret with ` +
|
|
93
|
+
'tokenEndpointAuthMethod "none"; a public client sends no secret.');
|
|
94
|
+
}
|
|
95
|
+
if (clientAuthentication !== "none" && config.clientSecret === undefined) {
|
|
96
|
+
throw new Error(`[connecta] connector "${id}" oauth.tokenEndpointAuthMethod ` + `"${clientAuthentication}" needs a clientSecret.`);
|
|
97
|
+
}
|
|
98
|
+
assertOAuthScope(id, config.scope);
|
|
99
|
+
const authorizationParams = [];
|
|
100
|
+
for (const [name, value] of Object.entries(config.authorizationParams ?? {})) {
|
|
101
|
+
if (RESERVED_AUTHORIZATION_PARAMS.has(name)) {
|
|
102
|
+
throw new Error(`[connecta] connector "${id}" oauth.authorizationParams may not set ` +
|
|
103
|
+
`"${name}"; the authorization request owns it.`);
|
|
104
|
+
}
|
|
105
|
+
if (typeof value !== "string") {
|
|
106
|
+
throw new Error(`[connecta] connector "${id}" oauth.authorizationParams.${name} must be a string.`);
|
|
107
|
+
}
|
|
108
|
+
authorizationParams.push([name, value]);
|
|
109
|
+
}
|
|
110
|
+
const tokenRequestHeaders = [];
|
|
111
|
+
for (const [name, value] of Object.entries(config.tokenRequestHeaders ?? {})) {
|
|
112
|
+
if (!HEADER_NAME.test(name)) {
|
|
113
|
+
throw new Error(`[connecta] connector "${id}" oauth.tokenRequestHeaders has an invalid header name.`);
|
|
114
|
+
}
|
|
115
|
+
if (RESERVED_TOKEN_HEADERS.has(name.toLowerCase())) {
|
|
116
|
+
throw new Error(`[connecta] connector "${id}" oauth.tokenRequestHeaders may not set ` + `"${name}"; the token request owns it.`);
|
|
117
|
+
}
|
|
118
|
+
if (typeof value !== "string" || value.includes("\r") || value.includes("\n") || value.includes("\0")) {
|
|
119
|
+
throw new Error(`[connecta] connector "${id}" oauth.tokenRequestHeaders.${name} must be a single-line string.`);
|
|
120
|
+
}
|
|
121
|
+
tokenRequestHeaders.push([name, value]);
|
|
122
|
+
}
|
|
123
|
+
const origins = Array.isArray(config.apiOrigins) ? config.apiOrigins : [];
|
|
124
|
+
if (origins.length === 0) {
|
|
125
|
+
throw new Error(`[connecta] connector "${id}" oauth.apiOrigins must name at least one ` +
|
|
126
|
+
"origin; ctx.oauth.fetch sends the access token nowhere else.");
|
|
127
|
+
}
|
|
128
|
+
const apiOrigins = new Set();
|
|
129
|
+
for (const origin of origins) {
|
|
130
|
+
const url = configuredUrl(id, "apiOrigins", origin);
|
|
131
|
+
if (url.origin !== origin) {
|
|
132
|
+
throw new Error(`[connecta] connector "${id}" oauth.apiOrigins entries must be exact ` +
|
|
133
|
+
'origins, like "https://api.example.com", with no path or trailing slash.');
|
|
134
|
+
}
|
|
135
|
+
apiOrigins.add(url.origin);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
authorizationEndpoint,
|
|
139
|
+
tokenEndpoint,
|
|
140
|
+
clientId: config.clientId,
|
|
141
|
+
...(config.clientSecret !== undefined ? { clientSecret: config.clientSecret } : {}),
|
|
142
|
+
clientAuthentication,
|
|
143
|
+
...(config.scope !== undefined ? { scope: config.scope } : {}),
|
|
144
|
+
pkce: config.pkce ?? true,
|
|
145
|
+
authorizationParams,
|
|
146
|
+
tokenRequestHeaders,
|
|
147
|
+
apiOrigins,
|
|
148
|
+
serverUrl: new URL(origins[0]),
|
|
149
|
+
identity: tokenEndpoint.href,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* A `KvOAuthProvider` whose discovery and client are constants. Storage,
|
|
154
|
+
* sealing, epochs, refresh coordination, and the authorization hooks are the
|
|
155
|
+
* base class's, unchanged; only what discovery and registration would have
|
|
156
|
+
* written is answered from configuration and never written.
|
|
157
|
+
*/
|
|
158
|
+
class StaticOAuthProvider extends KvOAuthProvider {
|
|
159
|
+
settings;
|
|
160
|
+
/** Set only for the code exchange of a PKCE-less flow. */
|
|
161
|
+
exchangeCode;
|
|
162
|
+
constructor(connectorId, ctx, coordinator, settings) {
|
|
163
|
+
super(connectorId, ctx.storage, `${ctx.baseUrl}/oauth/callback/${connectorId}`, coordinator, ctx.allowAuthorization === true, oauthSealerFor(ctx), ctx.signal,
|
|
164
|
+
// No client binding: the client is configuration, so there is no
|
|
165
|
+
// registration for a restart to carry forward or discard.
|
|
166
|
+
undefined, (reset) => trackOAuthStartReset(ctx.requestScope ?? ctx, reset), undefined, settings.scope, undefined, { secrets: sentSecretsFor(ctx) });
|
|
167
|
+
this.settings = settings;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The pre-registered client, answered from configuration. Its secret is
|
|
171
|
+
* never written to storage, sealed or otherwise: it is deployment config,
|
|
172
|
+
* not grant state, and an operator disconnect cannot delete it.
|
|
173
|
+
*/
|
|
174
|
+
async clientInformation() {
|
|
175
|
+
const { settings } = this;
|
|
176
|
+
return {
|
|
177
|
+
client_id: settings.clientId,
|
|
178
|
+
...(settings.clientSecret !== undefined ? { client_secret: settings.clientSecret } : {}),
|
|
179
|
+
redirect_uris: [this.redirectUrl],
|
|
180
|
+
token_endpoint_auth_method: settings.clientAuthentication,
|
|
181
|
+
issuer: settings.identity,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
configuredAuthorizationBinding() {
|
|
185
|
+
return {
|
|
186
|
+
issuer: this.settings.identity,
|
|
187
|
+
clientId: this.settings.clientId,
|
|
188
|
+
tokenEndpoint: this.settings.tokenEndpoint.href,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async saveClientInformation() {
|
|
192
|
+
// Nothing to save: the SDK only saves a client it registered or stamped.
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Discovery as a constant. Supplying both halves keeps the SDK from
|
|
196
|
+
* fetching either: no protected-resource probe of the API origin, and no
|
|
197
|
+
* metadata request to an authorization server that may not publish any.
|
|
198
|
+
* The metadata names no `issuer`, so the SDK binds grants to the configured
|
|
199
|
+
* token endpoint and does not demand an RFC 9207 `iss` it cannot know.
|
|
200
|
+
*/
|
|
201
|
+
async discoveryState() {
|
|
202
|
+
const { settings } = this;
|
|
203
|
+
const metadata = {
|
|
204
|
+
authorization_endpoint: settings.authorizationEndpoint.href,
|
|
205
|
+
token_endpoint: settings.tokenEndpoint.href,
|
|
206
|
+
response_types_supported: ["code"],
|
|
207
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
208
|
+
code_challenge_methods_supported: ["S256"],
|
|
209
|
+
token_endpoint_auth_methods_supported: [settings.clientAuthentication],
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
authorizationServerUrl: settings.identity,
|
|
213
|
+
authorizationServerMetadata: metadata,
|
|
214
|
+
resourceMetadata: { resource: settings.serverUrl.origin },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async saveDiscoveryState() {
|
|
218
|
+
// Configuration is the discovery; there is nothing to remember.
|
|
219
|
+
}
|
|
220
|
+
/** No RFC 8707 `resource` parameter: a plain REST API names no resource. */
|
|
221
|
+
async validateResourceURL() {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The code exchange without `code_verifier`, for a flow whose authorization
|
|
226
|
+
* request carried no challenge. Undefined everywhere else, which hands the
|
|
227
|
+
* SDK back its own request.
|
|
228
|
+
*/
|
|
229
|
+
prepareTokenRequest() {
|
|
230
|
+
if (this.settings.pkce || this.exchangeCode === undefined)
|
|
231
|
+
return undefined;
|
|
232
|
+
return new URLSearchParams({
|
|
233
|
+
grant_type: "authorization_code",
|
|
234
|
+
code: this.exchangeCode,
|
|
235
|
+
redirect_uri: this.redirectUrl,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** @internal Remember the code a PKCE-less exchange sends. */
|
|
239
|
+
exchanging(code) {
|
|
240
|
+
this.exchangeCode = code;
|
|
241
|
+
}
|
|
242
|
+
async redirectToAuthorization(authorizationUrl) {
|
|
243
|
+
for (const [name, value] of this.settings.authorizationParams) {
|
|
244
|
+
authorizationUrl.searchParams.set(name, value);
|
|
245
|
+
}
|
|
246
|
+
if (!this.settings.pkce) {
|
|
247
|
+
authorizationUrl.searchParams.delete("code_challenge");
|
|
248
|
+
authorizationUrl.searchParams.delete("code_challenge_method");
|
|
249
|
+
}
|
|
250
|
+
await super.redirectToAuthorization(authorizationUrl);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const AUTH_REQUIRED_MESSAGE = "Authorization required — open the URL to connect.";
|
|
254
|
+
/** The grant behind one `api()` connector's `oauth`, as the hooks it exposes. */
|
|
255
|
+
export function staticOAuth(id, config) {
|
|
256
|
+
const settings = settingsFor(id, config);
|
|
257
|
+
const issuerContext = { issuer: settings.identity };
|
|
258
|
+
const coordinatorFor = refreshCoordinatorsByPartition();
|
|
259
|
+
// Keyed by request scope, so nothing here outlives the request that set it.
|
|
260
|
+
const callbackProviders = new WeakMap();
|
|
261
|
+
const rejectedScopes = new WeakSet();
|
|
262
|
+
const scopeOf = (ctx) => ctx.requestScope ?? ctx;
|
|
263
|
+
const warnShortSecret = shortSecretWarning();
|
|
264
|
+
const providerFor = (ctx) => {
|
|
265
|
+
warnShortSecret(settings.clientSecret, ctx.logger);
|
|
266
|
+
return new StaticOAuthProvider(id, ctx, coordinatorFor(ctx), settings);
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* The global `fetch`, read per request, with the configured token-request
|
|
270
|
+
* headers laid over the SDK's own. With discovery and registration answered
|
|
271
|
+
* from configuration the token endpoint is the only URL the SDK fetches here;
|
|
272
|
+
* the check keeps the headers on it even if that ever changes.
|
|
273
|
+
*/
|
|
274
|
+
const tokenEndpointFetch = async (ctx, input, init = {}) => {
|
|
275
|
+
const url = new URL(input);
|
|
276
|
+
if (settings.tokenRequestHeaders.length === 0 || `${url.origin}${url.pathname}` !== settings.identity) {
|
|
277
|
+
trackRemoteClientRequest(sentSecretsFor(ctx), input, init);
|
|
278
|
+
return byteReadResponse(await sentSecretsFetch(ctx)(input, { ...init, redirect: "manual" }));
|
|
279
|
+
}
|
|
280
|
+
const headers = new Headers(init.headers);
|
|
281
|
+
for (const [name, value] of settings.tokenRequestHeaders)
|
|
282
|
+
headers.set(name, value);
|
|
283
|
+
for (const [, value] of settings.tokenRequestHeaders)
|
|
284
|
+
sentSecretsFor(ctx).header(value);
|
|
285
|
+
trackRemoteClientRequest(sentSecretsFor(ctx), input, { ...init, headers });
|
|
286
|
+
return byteReadResponse(await sentSecretsFetch(ctx)(input, { ...init, headers, redirect: "manual" }));
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* The SDK's `auth()` over this provider. The token endpoint is fetched
|
|
290
|
+
* through the refresh coordinator, with permanent spent fingerprints,
|
|
291
|
+
* rotation commits, and re-consent verdicts. Credential-bearing requests
|
|
292
|
+
* never follow redirects in either OAuth adapter.
|
|
293
|
+
*/
|
|
294
|
+
const runAuth = (provider, ctx, exchange) => auth(provider, {
|
|
295
|
+
serverUrl: settings.serverUrl,
|
|
296
|
+
...exchange,
|
|
297
|
+
fetchFn: coordinatorFor(ctx).coordinatedFetch(provider, (input, init) => tokenEndpointFetch(ctx, input, init), ctx.signal, ctx.defer),
|
|
298
|
+
});
|
|
299
|
+
// No cause, as in remoteMcp(): the `UnauthorizedError` class is the whole
|
|
300
|
+
// verdict, and a logger rendering the chain renders whatever it says.
|
|
301
|
+
const authRequiredError = () => new ConnectorCallError("downstream_oauth_required", `Connector "${id}" requires authorization — call authorize_connector({ connector: "${id}" }) and open the returned URL.`);
|
|
302
|
+
const disconnectedMessage = `Connector "${id}" was disconnected by an operator — explicitly start authorization to reconnect it.`;
|
|
303
|
+
/**
|
|
304
|
+
* One request through the owner's grant. A 401 earns exactly one recovery:
|
|
305
|
+
* a token another request has already rotated in, or else one coordinated
|
|
306
|
+
* refresh. A second 401 is `auth_required`, latched for the request scope
|
|
307
|
+
* so a program's later calls do not each spend a refresh on it.
|
|
308
|
+
*/
|
|
309
|
+
const authorizedFetch = async (ctx, input, init = {}) => {
|
|
310
|
+
const url = new URL(input);
|
|
311
|
+
if (!settings.apiOrigins.has(url.origin)) {
|
|
312
|
+
throw new ConnectorCallError("connector_call_failed", `Connector "${id}" sends its OAuth access token only to its declared ` +
|
|
313
|
+
`apiOrigins, and ${url.origin} is not one of them.`, { retryable: false });
|
|
314
|
+
}
|
|
315
|
+
if (init.body instanceof ReadableStream) {
|
|
316
|
+
throw new TypeError(`Connector "${id}": ctx.oauth.fetch may replay a request once after a ` +
|
|
317
|
+
"refresh, so it cannot send a stream body. Buffer the body first.");
|
|
318
|
+
}
|
|
319
|
+
const headers = new Headers(init.headers);
|
|
320
|
+
if (headers.has("authorization")) {
|
|
321
|
+
throw new ConnectorCallError("connector_call_failed", `Connector "${id}": a request through ctx.oauth.fetch may not set ` +
|
|
322
|
+
"Authorization; the connector's grant owns it.", { retryable: false });
|
|
323
|
+
}
|
|
324
|
+
const signal = init.signal && ctx.signal ? AbortSignal.any([init.signal, ctx.signal]) : (init.signal ?? ctx.signal);
|
|
325
|
+
const send = async (accessToken) => {
|
|
326
|
+
sentSecretsFor(ctx).add(accessToken);
|
|
327
|
+
const sent = new Headers(headers);
|
|
328
|
+
sent.set("Authorization", `Bearer ${accessToken}`);
|
|
329
|
+
// The token rides to a declared origin and no further: a redirect is
|
|
330
|
+
// handed back to the handler unfollowed rather than re-sent anywhere.
|
|
331
|
+
// The handler reads the answer with `.json()` or `.text()`, which read
|
|
332
|
+
// bytes here: workerd quotes a text read's non-text Content-Type in
|
|
333
|
+
// its own log, out of the handler's reach.
|
|
334
|
+
const response = await sentSecretsFetch(ctx)(url, {
|
|
335
|
+
...init,
|
|
336
|
+
headers: sent,
|
|
337
|
+
redirect: "manual",
|
|
338
|
+
...(signal ? { signal } : {}),
|
|
339
|
+
});
|
|
340
|
+
return byteReadResponse(response);
|
|
341
|
+
};
|
|
342
|
+
if (rejectedScopes.has(scopeOf(ctx)))
|
|
343
|
+
throw authRequiredError();
|
|
344
|
+
const release = retainOAuthPartition(oauthPartitionFor(ctx));
|
|
345
|
+
try {
|
|
346
|
+
if (signal?.aborted)
|
|
347
|
+
throw signal.reason;
|
|
348
|
+
const provider = providerFor(ctx);
|
|
349
|
+
// Bind this call's reads and writes to the live epoch: a reset landing
|
|
350
|
+
// meanwhile fails the call rather than handing it another flow's grant.
|
|
351
|
+
const epoch = await provider.beginFlow();
|
|
352
|
+
if (provider.isOperatorDisconnectedEpoch(epoch)) {
|
|
353
|
+
throw new ConnectorCallError("downstream_oauth_required", disconnectedMessage);
|
|
354
|
+
}
|
|
355
|
+
const tokens = await provider.tokens(issuerContext);
|
|
356
|
+
if (!tokens)
|
|
357
|
+
throw authRequiredError();
|
|
358
|
+
const first = await send(tokens.access_token);
|
|
359
|
+
if (first.status !== 401)
|
|
360
|
+
return first;
|
|
361
|
+
void first.body?.cancel().catch(() => { });
|
|
362
|
+
let accessToken;
|
|
363
|
+
const current = await provider.tokens(issuerContext);
|
|
364
|
+
if (current && current.access_token !== tokens.access_token) {
|
|
365
|
+
accessToken = current.access_token;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
if ((await runAuth(provider, ctx)) !== "AUTHORIZED")
|
|
369
|
+
throw authRequiredError();
|
|
370
|
+
const refreshed = await provider.tokens(issuerContext);
|
|
371
|
+
if (!refreshed)
|
|
372
|
+
throw authRequiredError();
|
|
373
|
+
accessToken = refreshed.access_token;
|
|
374
|
+
}
|
|
375
|
+
const second = await send(accessToken);
|
|
376
|
+
if (second.status !== 401)
|
|
377
|
+
return second;
|
|
378
|
+
void second.body?.cancel().catch(() => { });
|
|
379
|
+
rejectedScopes.add(scopeOf(ctx));
|
|
380
|
+
throw authRequiredError();
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (error instanceof UnauthorizedError) {
|
|
384
|
+
rejectedScopes.add(scopeOf(ctx));
|
|
385
|
+
throw authRequiredError();
|
|
386
|
+
}
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
finally {
|
|
390
|
+
release();
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const status = async (ctx) => {
|
|
394
|
+
if (rejectedScopes.has(scopeOf(ctx))) {
|
|
395
|
+
return {
|
|
396
|
+
state: "auth_required",
|
|
397
|
+
message: "Authorization required — the downstream rejected this connector's OAuth grant.",
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
// A status read never starts authorization: this provider cannot, and
|
|
401
|
+
// nothing here calls the SDK. A stored grant for the configured token
|
|
402
|
+
// endpoint is healthy until a call says otherwise — the downstream is
|
|
403
|
+
// asked nothing.
|
|
404
|
+
const provider = providerFor(ctx);
|
|
405
|
+
if (await provider.operatorDisconnected()) {
|
|
406
|
+
return { state: "auth_required", message: disconnectedMessage };
|
|
407
|
+
}
|
|
408
|
+
return (await provider.tokens(issuerContext))
|
|
409
|
+
? { state: "ok" }
|
|
410
|
+
: { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
|
|
411
|
+
};
|
|
412
|
+
const startAuth = async (original, opts) => {
|
|
413
|
+
const ctx = authorizingContext(original);
|
|
414
|
+
const provider = providerFor(ctx);
|
|
415
|
+
if (ctx.signal?.aborted)
|
|
416
|
+
throw ctx.signal.reason;
|
|
417
|
+
const disconnected = opts?.force ? false : await provider.operatorDisconnected();
|
|
418
|
+
if (ctx.signal?.aborted)
|
|
419
|
+
throw ctx.signal.reason;
|
|
420
|
+
if (opts?.force || disconnected) {
|
|
421
|
+
await provider.resetAuthorization();
|
|
422
|
+
rejectedScopes.delete(scopeOf(original));
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// Hand back a recent consent URL rather than overwrite the verifier
|
|
426
|
+
// the operator may be mid-consent on.
|
|
427
|
+
const pending = await provider.reusablePendingAuthorizationUrl();
|
|
428
|
+
if (pending) {
|
|
429
|
+
return {
|
|
430
|
+
state: "auth_required",
|
|
431
|
+
authorizationUrl: pending,
|
|
432
|
+
authorizationReused: true,
|
|
433
|
+
message: AUTH_REQUIRED_MESSAGE,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (ctx.signal?.aborted)
|
|
438
|
+
throw ctx.signal.reason;
|
|
439
|
+
try {
|
|
440
|
+
// A grant from a since-repointed token endpoint reads as absent here,
|
|
441
|
+
// and the consent this start begins replaces it.
|
|
442
|
+
await provider.beginFlow();
|
|
443
|
+
if (await provider.tokens(issuerContext)) {
|
|
444
|
+
return { state: "ok", message: "Already authorized — connection is healthy." };
|
|
445
|
+
}
|
|
446
|
+
if ((await runAuth(provider, ctx)) === "AUTHORIZED") {
|
|
447
|
+
return { state: "ok", message: "Already authorized — connection is healthy." };
|
|
448
|
+
}
|
|
449
|
+
const authorizationUrl = await provider.consentUrl();
|
|
450
|
+
return {
|
|
451
|
+
state: "auth_required",
|
|
452
|
+
...(authorizationUrl !== undefined ? { authorizationUrl } : {}),
|
|
453
|
+
message: AUTH_REQUIRED_MESSAGE,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
if (error instanceof UnauthorizedError) {
|
|
458
|
+
return { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
|
|
459
|
+
}
|
|
460
|
+
// Reaches the agent through authorize_connector. Text already in
|
|
461
|
+
// connecta's words passes (token responses are rebuilt before the SDK
|
|
462
|
+
// reads them); anything else is told from its record.
|
|
463
|
+
return {
|
|
464
|
+
state: "error",
|
|
465
|
+
message: error instanceof ConnectorCallError || error instanceof OAuthError ? msg(error) : describeFailure(id, error),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
const disconnectAuth = async (ctx) => {
|
|
470
|
+
rejectedScopes.delete(scopeOf(ctx));
|
|
471
|
+
await providerFor(ctx).resetAuthorization(true);
|
|
472
|
+
};
|
|
473
|
+
const verifyState = async (state, ctx) => {
|
|
474
|
+
// The provider that verified the state captured its flow's epoch, and the
|
|
475
|
+
// exchange must write under exactly that one.
|
|
476
|
+
const provider = providerFor(ctx);
|
|
477
|
+
callbackProviders.set(scopeOf(ctx), provider);
|
|
478
|
+
return provider.verifyState(state);
|
|
479
|
+
};
|
|
480
|
+
const finishAuth = async (code, ctx, callbackParams) => {
|
|
481
|
+
const provider = callbackProviders.get(scopeOf(ctx)) ?? providerFor(ctx);
|
|
482
|
+
callbackProviders.delete(scopeOf(ctx));
|
|
483
|
+
const authorizationCode = callbackParams?.get("code") ?? code;
|
|
484
|
+
const iss = callbackParams?.get("iss") ?? undefined;
|
|
485
|
+
// Every exchange names and claims its own consent, including without PKCE.
|
|
486
|
+
const consent = callbackParams?.get("state") ?? null;
|
|
487
|
+
if (!consent || !(await provider.verifyCallbackState(consent))) {
|
|
488
|
+
throw new ConnectorCallError("connector_call_failed", `Connector "${id}" authorization callback matches no pending consent; nothing was exchanged.`);
|
|
489
|
+
}
|
|
490
|
+
provider.validateCallbackIssuer(callbackParams?.get("iss") ?? null);
|
|
491
|
+
provider.exchanging(authorizationCode);
|
|
492
|
+
await provider.bindFlow();
|
|
493
|
+
const result = await runAuth(provider, ctx, {
|
|
494
|
+
authorizationCode,
|
|
495
|
+
...(iss !== undefined ? { iss } : {}),
|
|
496
|
+
});
|
|
497
|
+
if (result !== "AUTHORIZED") {
|
|
498
|
+
throw new UnauthorizedError("Failed to authorize");
|
|
499
|
+
}
|
|
500
|
+
rejectedScopes.delete(scopeOf(ctx));
|
|
501
|
+
};
|
|
502
|
+
return {
|
|
503
|
+
status: retainingOAuthPartition(status, 0),
|
|
504
|
+
startAuth: retainingOAuthPartition(startAuth, 0),
|
|
505
|
+
disconnectAuth: retainingOAuthPartition(disconnectAuth, 0),
|
|
506
|
+
verifyState: retainingOAuthPartition(verifyState, 1),
|
|
507
|
+
verifyCallbackIssuer: retainingOAuthPartition(async (issuer, ctx) => {
|
|
508
|
+
const provider = callbackProviders.get(scopeOf(ctx));
|
|
509
|
+
if (!provider)
|
|
510
|
+
return false;
|
|
511
|
+
provider.validateCallbackIssuer(issuer);
|
|
512
|
+
return true;
|
|
513
|
+
}, 1),
|
|
514
|
+
consumeAuthError: retainingOAuthPartition(async (ctx) => {
|
|
515
|
+
const provider = callbackProviders.get(scopeOf(ctx));
|
|
516
|
+
callbackProviders.delete(scopeOf(ctx));
|
|
517
|
+
if (!provider)
|
|
518
|
+
throw new ConnectorCallError("connector_call_failed", "OAuth error callback matches no pending consent.");
|
|
519
|
+
await provider.consumeAuthError();
|
|
520
|
+
}, 0),
|
|
521
|
+
finishAuth: retainingOAuthPartition(finishAuth, 1),
|
|
522
|
+
access: (ctx) => ({
|
|
523
|
+
fetch: (input, init) => authorizedFetch(ctx, input, init),
|
|
524
|
+
}),
|
|
525
|
+
};
|
|
526
|
+
}
|