@zackbart/connecta 0.34.0 → 0.35.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 +393 -0
- package/README.md +62 -105
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2051 -1160
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +663 -271
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +15 -7
- package/dist/connectors/api-connector.js +147 -57
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/option-shapes.js +1 -1
- package/dist/connectors/remote-mcp.d.ts +12 -30
- package/dist/connectors/remote-mcp.js +960 -564
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +5 -1
- package/dist/describe-config.js +3 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +228 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +383 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +221 -87
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +534 -408
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +8 -4
- package/dist/operator-ui/connection-map.js +9 -6
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +5 -14
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/health.js +52 -0
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +11 -19
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +5 -29
- package/dist/operator-ui/view.js +3 -59
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +71 -32
- package/dist/provider.js +134 -40
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +537 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +8 -209
- package/dist/providers/_shared/google/workspace.js +7 -426
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/tools.js +101 -78
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +3 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +17 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +7 -22
- package/dist/providers/cloudflare/index.js +80 -194
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +3299 -3208
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +538 -18
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +802 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1024 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +3 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +6 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +734 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +7 -27
- package/dist/providers/stripe/index.js +75 -191
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -10
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +19 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +11 -25
- package/dist/providers/vercel/index.js +67 -261
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +398 -376
- package/dist/providers/vercel/{value-safety.js → safety.js} +261 -42
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +30 -28
- package/dist/registry.js +446 -413
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +3 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +29 -22
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +556 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +330 -224
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +11 -99
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +27 -7
- package/documentation/architecture.md +2550 -186
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +56 -21
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +144 -105
- package/documentation/meta-tools.md +411 -34
- package/documentation/operating.md +28 -4
- package/documentation/operator-tests.md +11 -2
- package/documentation/operator-ui.md +103 -33
- package/documentation/provider-auth-migration.md +38 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +965 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +27 -42
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -134
- package/dist/catalog-drift.js +0 -493
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -43
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1078
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -352
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -57
- package/dist/providers/mixpanel/index.js +0 -468
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -65
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -49
- package/dist/providers/revenuecat/index.js +0 -557
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
import { auth, OAuthError, UnauthorizedError } from "@modelcontextprotocol/client";
|
|
2
|
+
import { Effect } from "effect";
|
|
2
3
|
import { byteReadResponse } from "../byte-read-response.js";
|
|
3
|
-
import {
|
|
4
|
-
import { ConnectorCallError, msg } from "../errors.js";
|
|
5
|
-
import {
|
|
4
|
+
import { deriveContext } from "../derived-context.js";
|
|
5
|
+
import { ConnectorCallError, msg, resendAdviceOf, withResendAdvice } from "../errors.js";
|
|
6
|
+
import { guardedTransport, sendCredentialPost } from "../guarded-transport.js";
|
|
7
|
+
import { oauthPartitionFor, retainOAuthPartition } from "../oauth-partition.js";
|
|
6
8
|
import { oauthSealerFor } from "../oauth-sealing.js";
|
|
7
9
|
import { describeFailure } from "../operator-record.js";
|
|
8
|
-
import {
|
|
10
|
+
import { assertProviderCallOpen, bindProviderResponse, dispatchProviderRequest, providerRequest, readRequest, recordingFetch, recordProviderResponse, renewedAfterUnauthorized, takeProviderHandoff, vendorRefusal, } from "../provider-call.js";
|
|
11
|
+
import { viewSignal } from "../runtime/call.js";
|
|
12
|
+
import { runEdge } from "../runtime/run.js";
|
|
13
|
+
import { SharedRead } from "../runtime/shared-read.js";
|
|
14
|
+
import { sentSecretsFetch, sentSecretsFor, shortSecretWarning } from "../sent-secrets.js";
|
|
15
|
+
import { unsentAfterUnauthorized } from "../write-outcome.js";
|
|
9
16
|
import { trackRemoteClientRequest } from "./downstream-client-metadata.js";
|
|
17
|
+
import { assertOAuthScope, authorizingContext, codeExchangeResponse, KvOAuthProvider, refreshCoordinatorsByPartition, } from "./downstream-oauth.js";
|
|
18
|
+
import { authView } from "./native-call.js";
|
|
19
|
+
import { authCall, authFlow, authInvoke, authMethod, authOwnMethod, authProgram, authValue, registerAuth, registerAuthMethod, } from "./native.js";
|
|
10
20
|
import { trackOAuthStartReset } from "./oauth-start-reset.js";
|
|
21
|
+
import { bindRenewableGrant, OAUTH_CHALLENGE_MAX_BYTES } from "./renewable-grant.js";
|
|
22
|
+
/**
|
|
23
|
+
* Token-request header values Connecta itself sends, by lowercase name: JSON
|
|
24
|
+
* content negotiation and the media types maintained providers configure.
|
|
25
|
+
* Registering one would redact a vendor's name wherever its fragments recur.
|
|
26
|
+
* Any other configured value, under any name, may carry a credential and is
|
|
27
|
+
* registered.
|
|
28
|
+
*/
|
|
29
|
+
export const PUBLIC_TOKEN_REQUEST_HEADERS = new Map([
|
|
30
|
+
["accept", new Set(["application/json", "application/vnd.ccbchurch.v2+json"])],
|
|
31
|
+
]);
|
|
11
32
|
/**
|
|
12
33
|
* Downstream OAuth for a hand-written `api()` connector: the authorization
|
|
13
34
|
* code grant against endpoints and a pre-registered client the deployment
|
|
@@ -159,8 +180,8 @@ class StaticOAuthProvider extends KvOAuthProvider {
|
|
|
159
180
|
settings;
|
|
160
181
|
/** Set only for the code exchange of a PKCE-less flow. */
|
|
161
182
|
exchangeCode;
|
|
162
|
-
constructor(connectorId, ctx, coordinator, settings) {
|
|
163
|
-
super(connectorId, ctx.storage, `${ctx.baseUrl}/oauth/callback/${connectorId}`, coordinator, ctx.allowAuthorization === true, oauthSealerFor(ctx), ctx
|
|
183
|
+
constructor(connectorId, ctx, coordinator, settings, requestOwned = false) {
|
|
184
|
+
super(connectorId, ctx.storage, `${ctx.baseUrl}/oauth/callback/${connectorId}`, coordinator, ctx.allowAuthorization === true, oauthSealerFor(ctx), viewSignal(ctx, requestOwned),
|
|
164
185
|
// No client binding: the client is configuration, so there is no
|
|
165
186
|
// registration for a restart to carry forward or discard.
|
|
166
187
|
undefined, (reset) => trackOAuthStartReset(ctx.requestScope ?? ctx, reset), undefined, settings.scope, undefined, { secrets: sentSecretsFor(ctx) });
|
|
@@ -171,15 +192,8 @@ class StaticOAuthProvider extends KvOAuthProvider {
|
|
|
171
192
|
* never written to storage, sealed or otherwise: it is deployment config,
|
|
172
193
|
* not grant state, and an operator disconnect cannot delete it.
|
|
173
194
|
*/
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
};
|
|
195
|
+
clientInformation() {
|
|
196
|
+
return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "clientInformation", []));
|
|
183
197
|
}
|
|
184
198
|
configuredAuthorizationBinding() {
|
|
185
199
|
return {
|
|
@@ -188,8 +202,8 @@ class StaticOAuthProvider extends KvOAuthProvider {
|
|
|
188
202
|
tokenEndpoint: this.settings.tokenEndpoint.href,
|
|
189
203
|
};
|
|
190
204
|
}
|
|
191
|
-
|
|
192
|
-
|
|
205
|
+
saveClientInformation() {
|
|
206
|
+
return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "saveClientInformation", []));
|
|
193
207
|
}
|
|
194
208
|
/**
|
|
195
209
|
* Discovery as a constant. Supplying both halves keeps the SDK from
|
|
@@ -198,28 +212,15 @@ class StaticOAuthProvider extends KvOAuthProvider {
|
|
|
198
212
|
* The metadata names no `issuer`, so the SDK binds grants to the configured
|
|
199
213
|
* token endpoint and does not demand an RFC 9207 `iss` it cannot know.
|
|
200
214
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
};
|
|
215
|
+
discoveryState() {
|
|
216
|
+
return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "discoveryState", []));
|
|
216
217
|
}
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
saveDiscoveryState() {
|
|
219
|
+
return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "saveDiscoveryState", []));
|
|
219
220
|
}
|
|
220
221
|
/** No RFC 8707 `resource` parameter: a plain REST API names no resource. */
|
|
221
|
-
|
|
222
|
-
return
|
|
222
|
+
validateResourceURL() {
|
|
223
|
+
return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "validateResourceURL", []));
|
|
223
224
|
}
|
|
224
225
|
/**
|
|
225
226
|
* The code exchange without `code_verifier`, for a flow whose authorization
|
|
@@ -239,18 +240,122 @@ class StaticOAuthProvider extends KvOAuthProvider {
|
|
|
239
240
|
exchanging(code) {
|
|
240
241
|
this.exchangeCode = code;
|
|
241
242
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
243
|
+
redirectToAuthorization(authorizationUrl) {
|
|
244
|
+
return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "redirectToAuthorization", [authorizationUrl]));
|
|
245
|
+
}
|
|
246
|
+
static {
|
|
247
|
+
registerAuthMethod(StaticOAuthProvider.prototype, "clientInformation", function () {
|
|
248
|
+
return authValue(() => {
|
|
249
|
+
const { settings } = this;
|
|
250
|
+
return {
|
|
251
|
+
client_id: settings.clientId,
|
|
252
|
+
...(settings.clientSecret !== undefined ? { client_secret: settings.clientSecret } : {}),
|
|
253
|
+
redirect_uris: [this.redirectUrl],
|
|
254
|
+
token_endpoint_auth_method: settings.clientAuthentication,
|
|
255
|
+
issuer: settings.identity,
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}, StaticOAuthProvider.prototype.clientInformation);
|
|
259
|
+
registerAuthMethod(StaticOAuthProvider.prototype, "saveClientInformation", function () {
|
|
260
|
+
// Nothing to save: the SDK only saves a client it registered or stamped.
|
|
261
|
+
return Effect.void;
|
|
262
|
+
}, StaticOAuthProvider.prototype.saveClientInformation);
|
|
263
|
+
registerAuthMethod(StaticOAuthProvider.prototype, "discoveryState", function () {
|
|
264
|
+
return authValue(() => {
|
|
265
|
+
const { settings } = this;
|
|
266
|
+
const metadata = {
|
|
267
|
+
authorization_endpoint: settings.authorizationEndpoint.href,
|
|
268
|
+
token_endpoint: settings.tokenEndpoint.href,
|
|
269
|
+
response_types_supported: ["code"],
|
|
270
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
271
|
+
code_challenge_methods_supported: ["S256"],
|
|
272
|
+
token_endpoint_auth_methods_supported: [settings.clientAuthentication],
|
|
273
|
+
};
|
|
274
|
+
return {
|
|
275
|
+
authorizationServerUrl: settings.identity,
|
|
276
|
+
authorizationServerMetadata: metadata,
|
|
277
|
+
resourceMetadata: { resource: settings.serverUrl.origin },
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
}, StaticOAuthProvider.prototype.discoveryState);
|
|
281
|
+
registerAuthMethod(StaticOAuthProvider.prototype, "saveDiscoveryState", function () {
|
|
282
|
+
// Configuration is the discovery; there is nothing to remember.
|
|
283
|
+
return Effect.void;
|
|
284
|
+
}, StaticOAuthProvider.prototype.saveDiscoveryState);
|
|
285
|
+
registerAuthMethod(StaticOAuthProvider.prototype, "validateResourceURL", function () {
|
|
286
|
+
return authValue(() => {
|
|
287
|
+
return undefined;
|
|
288
|
+
});
|
|
289
|
+
}, StaticOAuthProvider.prototype.validateResourceURL);
|
|
290
|
+
registerAuthMethod(StaticOAuthProvider.prototype, "redirectToAuthorization", function (authorizationUrl) {
|
|
291
|
+
return authFlow(function* () {
|
|
292
|
+
for (const [name, value] of this.settings.authorizationParams) {
|
|
293
|
+
authorizationUrl.searchParams.set(name, value);
|
|
294
|
+
}
|
|
295
|
+
if (!this.settings.pkce) {
|
|
296
|
+
authorizationUrl.searchParams.delete("code_challenge");
|
|
297
|
+
authorizationUrl.searchParams.delete("code_challenge_method");
|
|
298
|
+
}
|
|
299
|
+
(yield authCall(KvOAuthProvider.prototype.redirectToAuthorization, [authorizationUrl], this));
|
|
300
|
+
}.bind(this));
|
|
301
|
+
}, StaticOAuthProvider.prototype.redirectToAuthorization);
|
|
251
302
|
}
|
|
252
303
|
}
|
|
253
304
|
const AUTH_REQUIRED_MESSAGE = "Authorization required — open the URL to connect.";
|
|
305
|
+
/** Release the resource body before renewal, bounding even the facade's discarded challenge. */
|
|
306
|
+
function readChallenge(response, signal, method) {
|
|
307
|
+
return runEdge(authProgram(readChallengeNative, [response, signal, method]));
|
|
308
|
+
}
|
|
309
|
+
const readChallengeNative = /* @__PURE__ */ registerAuth(readChallenge, function (response, signal, method) {
|
|
310
|
+
return authFlow(function* () {
|
|
311
|
+
if (method?.toUpperCase() === "HEAD") {
|
|
312
|
+
void response.body?.cancel().catch(() => { });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const oversized = () => new ConnectorCallError("connector_call_failed", "The OAuth challenge exceeded its response ceiling.", {
|
|
316
|
+
retryable: false,
|
|
317
|
+
});
|
|
318
|
+
if (Number(response.headers.get("content-length")) > OAUTH_CHALLENGE_MAX_BYTES) {
|
|
319
|
+
void response.body?.cancel().catch(() => { });
|
|
320
|
+
throw oversized();
|
|
321
|
+
}
|
|
322
|
+
if (!response.body)
|
|
323
|
+
return;
|
|
324
|
+
const reader = response.body.getReader();
|
|
325
|
+
const cancel = () => {
|
|
326
|
+
void reader.cancel().catch(() => { });
|
|
327
|
+
};
|
|
328
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
329
|
+
if (signal.aborted)
|
|
330
|
+
cancel();
|
|
331
|
+
let total = 0;
|
|
332
|
+
try {
|
|
333
|
+
while (true) {
|
|
334
|
+
let chunk;
|
|
335
|
+
try {
|
|
336
|
+
chunk = (yield authInvoke(reader, "read", []));
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Like the guarded client, a broken challenge body cannot replace its 401.
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
if (signal.aborted)
|
|
343
|
+
throw signal.reason;
|
|
344
|
+
if (chunk.done)
|
|
345
|
+
break;
|
|
346
|
+
total += chunk.value.byteLength;
|
|
347
|
+
if (total > OAUTH_CHALLENGE_MAX_BYTES) {
|
|
348
|
+
cancel();
|
|
349
|
+
throw oversized();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
signal.removeEventListener("abort", cancel);
|
|
355
|
+
reader.releaseLock();
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
});
|
|
254
359
|
/** The grant behind one `api()` connector's `oauth`, as the hooks it exposes. */
|
|
255
360
|
export function staticOAuth(id, config) {
|
|
256
361
|
const settings = settingsFor(id, config);
|
|
@@ -261,266 +366,553 @@ export function staticOAuth(id, config) {
|
|
|
261
366
|
const rejectedScopes = new WeakSet();
|
|
262
367
|
const scopeOf = (ctx) => ctx.requestScope ?? ctx;
|
|
263
368
|
const warnShortSecret = shortSecretWarning();
|
|
264
|
-
const providerFor = (ctx) => {
|
|
369
|
+
const providerFor = (ctx, requestOwned = false) => {
|
|
265
370
|
warnShortSecret(settings.clientSecret, ctx.logger);
|
|
266
|
-
return new StaticOAuthProvider(id, ctx, coordinatorFor(ctx), settings);
|
|
371
|
+
return new StaticOAuthProvider(id, ctx, coordinatorFor(ctx), settings, requestOwned);
|
|
267
372
|
};
|
|
268
373
|
/**
|
|
269
|
-
* The
|
|
374
|
+
* The owned credential transport, with the configured token-request
|
|
270
375
|
* headers laid over the SDK's own. With discovery and registration answered
|
|
271
376
|
* from configuration the token endpoint is the only URL the SDK fetches here;
|
|
272
377
|
* the check keeps the headers on it even if that ever changes.
|
|
273
378
|
*/
|
|
274
|
-
const tokenEndpointFetch =
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
379
|
+
const tokenEndpointFetch = authMethod((secrets, input, init = {}) => {
|
|
380
|
+
return authFlow(function* () {
|
|
381
|
+
const url = new URL(input);
|
|
382
|
+
const request = { ...init, redirect: "manual" };
|
|
383
|
+
if (settings.tokenRequestHeaders.length && `${url.origin}${url.pathname}` === settings.identity) {
|
|
384
|
+
const headers = new Headers(init.headers);
|
|
385
|
+
for (const [name, value] of settings.tokenRequestHeaders)
|
|
386
|
+
headers.set(name, value);
|
|
387
|
+
for (const [name, value] of settings.tokenRequestHeaders)
|
|
388
|
+
if (!PUBLIC_TOKEN_REQUEST_HEADERS.get(name.toLowerCase())?.has(value))
|
|
389
|
+
secrets.header(value);
|
|
390
|
+
request.headers = headers;
|
|
391
|
+
}
|
|
392
|
+
trackRemoteClientRequest(secrets, input, request);
|
|
393
|
+
// The coordinator or code-exchange wrapper authorizes this send. Refresh
|
|
394
|
+
// claim submission may outlive the caller; this transport uses no
|
|
395
|
+
// request-owned capability and changes no closed resource-call ledger.
|
|
396
|
+
const dispatch = {
|
|
397
|
+
dispatched: false,
|
|
398
|
+
auxiliary: true,
|
|
399
|
+
provider: id,
|
|
400
|
+
method: request.method ?? "GET",
|
|
401
|
+
};
|
|
402
|
+
secrets.request(input, request);
|
|
403
|
+
const response = (yield authCall(sendCredentialPost, [
|
|
404
|
+
input,
|
|
405
|
+
request,
|
|
406
|
+
() => dispatchProviderRequest(undefined, dispatch, request.headers),
|
|
407
|
+
]));
|
|
408
|
+
recordProviderResponse(dispatch, response.status);
|
|
409
|
+
return byteReadResponse(response);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
288
412
|
/**
|
|
289
413
|
* The SDK's `auth()` over this provider. The token endpoint is fetched
|
|
290
414
|
* through the refresh coordinator, with permanent spent fingerprints,
|
|
291
415
|
* rotation commits, and re-consent verdicts. Credential-bearing requests
|
|
292
416
|
* never follow redirects in either OAuth adapter.
|
|
293
417
|
*/
|
|
294
|
-
const runAuth = (provider, ctx, exchange) =>
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
418
|
+
const runAuth = authMethod((provider, ctx, exchange, requestOwned = false) => {
|
|
419
|
+
return authFlow(function* () {
|
|
420
|
+
const secrets = sentSecretsFor(ctx);
|
|
421
|
+
return (yield authCall(auth, [
|
|
422
|
+
provider,
|
|
423
|
+
{
|
|
424
|
+
serverUrl: settings.serverUrl,
|
|
425
|
+
...exchange,
|
|
426
|
+
fetchFn: coordinatorFor(ctx).coordinatedFetch(provider, authMethod((input, init) => {
|
|
427
|
+
return authFlow(function* () {
|
|
428
|
+
if (exchange) {
|
|
429
|
+
// A code exchange has no submitted refresh claim transferring
|
|
430
|
+
// ownership. Only explicit scope revocation stops a callback:
|
|
431
|
+
// the production callback route remains uninterruptible and
|
|
432
|
+
// does not revoke its own scope while exchanging a code.
|
|
433
|
+
return (yield authView(ctx, authMethod((signal) => {
|
|
434
|
+
return authFlow(function* () {
|
|
435
|
+
assertProviderCallOpen(ctx, id, true);
|
|
436
|
+
const response = (yield authCall(tokenEndpointFetch, [
|
|
437
|
+
secrets,
|
|
438
|
+
input,
|
|
439
|
+
{
|
|
440
|
+
...init,
|
|
441
|
+
signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal,
|
|
442
|
+
},
|
|
443
|
+
]));
|
|
444
|
+
// An override can ignore abort. Never hand its late tokens
|
|
445
|
+
// to the SDK, which would otherwise persist both credentials.
|
|
446
|
+
if (signal.aborted) {
|
|
447
|
+
void response.body?.cancel().catch(() => { });
|
|
448
|
+
throw signal.reason;
|
|
449
|
+
}
|
|
450
|
+
// The SDK clones and reads this body after fetch resolves.
|
|
451
|
+
// Revocation must cancel that read even for a custom fetch.
|
|
452
|
+
return codeExchangeResponse(response, signal);
|
|
453
|
+
});
|
|
454
|
+
}), true));
|
|
455
|
+
}
|
|
456
|
+
return (yield authCall(tokenEndpointFetch, [secrets, input, init]));
|
|
457
|
+
});
|
|
458
|
+
}), viewSignal(ctx, requestOwned || exchange !== undefined), ctx.defer, undefined, () => {
|
|
459
|
+
guardedTransport(true);
|
|
460
|
+
}),
|
|
461
|
+
},
|
|
462
|
+
]));
|
|
463
|
+
});
|
|
298
464
|
});
|
|
299
465
|
// No cause, as in remoteMcp(): the `UnauthorizedError` class is the whole
|
|
300
466
|
// verdict, and a logger rendering the chain renders whatever it says.
|
|
301
467
|
const authRequiredError = () => new ConnectorCallError("downstream_oauth_required", `Connector "${id}" requires authorization — call authorize_connector({ connector: "${id}" }) and open the returned URL.`);
|
|
302
468
|
const disconnectedMessage = `Connector "${id}" was disconnected by an operator — explicitly start authorization to reconnect it.`;
|
|
303
469
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
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.
|
|
470
|
+
* The token to send after `spent` drew a 401: one another request has
|
|
471
|
+
* already rotated in, or else one coordinated refresh.
|
|
308
472
|
*/
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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 } : {}),
|
|
473
|
+
const renewedToken = authMethod((provider, ctx, spent, requestOwned = false) => {
|
|
474
|
+
return authFlow(function* () {
|
|
475
|
+
// Only renewal whose claim CAS was submitted may survive this call.
|
|
476
|
+
// Race each preparation read too, and never resume SDK work or consume a
|
|
477
|
+
// rotated token after closure, even if a storage promise completes late.
|
|
478
|
+
const live = authMethod((work) => {
|
|
479
|
+
return authFlow(function* () {
|
|
480
|
+
if (ctx.signal?.aborted)
|
|
481
|
+
throw ctx.signal.reason;
|
|
482
|
+
const value = (yield authView(ctx, work, requestOwned));
|
|
483
|
+
if (ctx.signal?.aborted)
|
|
484
|
+
throw ctx.signal.reason;
|
|
485
|
+
return value;
|
|
486
|
+
});
|
|
339
487
|
});
|
|
340
|
-
|
|
488
|
+
const current = (yield authCall(live, [
|
|
489
|
+
authMethod(() => {
|
|
490
|
+
return authInvoke(provider, "tokens", [issuerContext]);
|
|
491
|
+
}),
|
|
492
|
+
]));
|
|
493
|
+
if (current && current.access_token !== spent)
|
|
494
|
+
return current.access_token;
|
|
495
|
+
if ((yield authCall(live, [
|
|
496
|
+
authMethod(() => {
|
|
497
|
+
return authCall(runAuth, [provider, ctx, undefined, requestOwned]);
|
|
498
|
+
}),
|
|
499
|
+
])) !== "AUTHORIZED")
|
|
500
|
+
throw authRequiredError();
|
|
501
|
+
const refreshed = (yield authCall(live, [
|
|
502
|
+
authMethod(() => {
|
|
503
|
+
return authInvoke(provider, "tokens", [issuerContext]);
|
|
504
|
+
}),
|
|
505
|
+
]));
|
|
506
|
+
if (!refreshed)
|
|
507
|
+
throw authRequiredError();
|
|
508
|
+
return refreshed.access_token;
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
/** One call's grant use; submitted refresh work keeps the coordinator's owner. */
|
|
512
|
+
const grantFor = (ctx, requestOwned = false) => {
|
|
513
|
+
let provider;
|
|
514
|
+
const use = authMethod((work) => {
|
|
515
|
+
return authView(ctx, authMethod((signal) => {
|
|
516
|
+
return authFlow(function* () {
|
|
517
|
+
assertProviderCallOpen(ctx, id, requestOwned);
|
|
518
|
+
if (rejectedScopes.has(scopeOf(ctx)))
|
|
519
|
+
throw authRequiredError();
|
|
520
|
+
const live = deriveContext(ctx, { signal });
|
|
521
|
+
const release = retainOAuthPartition(oauthPartitionFor(live));
|
|
522
|
+
try {
|
|
523
|
+
provider ??= new SharedRead(() => authView(live, authMethod(() => {
|
|
524
|
+
return authFlow(function* () {
|
|
525
|
+
const value = providerFor(live, requestOwned);
|
|
526
|
+
const epoch = (yield authInvoke(value, "beginFlow", []));
|
|
527
|
+
if (value.isOperatorDisconnectedEpoch(epoch)) {
|
|
528
|
+
throw new ConnectorCallError("downstream_oauth_required", disconnectedMessage);
|
|
529
|
+
}
|
|
530
|
+
return value;
|
|
531
|
+
});
|
|
532
|
+
}), requestOwned), undefined, ctx.requestScope ?? ctx);
|
|
533
|
+
return (yield authCall(work, [(yield provider.join(signal)), live]));
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
if (signal.aborted)
|
|
537
|
+
throw signal.reason;
|
|
538
|
+
if (error instanceof UnauthorizedError) {
|
|
539
|
+
rejectedScopes.add(scopeOf(ctx));
|
|
540
|
+
throw authRequiredError();
|
|
541
|
+
}
|
|
542
|
+
throw error;
|
|
543
|
+
}
|
|
544
|
+
finally {
|
|
545
|
+
release();
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}), requestOwned);
|
|
549
|
+
});
|
|
550
|
+
return {
|
|
551
|
+
token: authMethod(() => {
|
|
552
|
+
return authCall(use, [
|
|
553
|
+
authMethod((provider) => {
|
|
554
|
+
return authFlow(function* () {
|
|
555
|
+
const tokens = (yield authInvoke(provider, "tokens", [issuerContext]));
|
|
556
|
+
if (!tokens)
|
|
557
|
+
throw authRequiredError();
|
|
558
|
+
return tokens.access_token;
|
|
559
|
+
});
|
|
560
|
+
}),
|
|
561
|
+
]);
|
|
562
|
+
}),
|
|
563
|
+
refresh: authMethod((stale) => {
|
|
564
|
+
return authCall(use, [
|
|
565
|
+
authMethod((provider, live) => {
|
|
566
|
+
return authCall(renewedToken, [provider, live, stale, requestOwned]);
|
|
567
|
+
}),
|
|
568
|
+
]);
|
|
569
|
+
}),
|
|
570
|
+
rejected: authMethod(() => {
|
|
571
|
+
return authView(ctx, authMethod(() => {
|
|
572
|
+
return authValue(() => {
|
|
573
|
+
rejectedScopes.add(scopeOf(ctx));
|
|
574
|
+
throw authRequiredError();
|
|
575
|
+
});
|
|
576
|
+
}), requestOwned);
|
|
577
|
+
}),
|
|
341
578
|
};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
579
|
+
};
|
|
580
|
+
/**
|
|
581
|
+
* One request through the owner's grant. A 401 earns exactly one recovery
|
|
582
|
+
* ({@link renewedToken}). A read is then sent once more; a write never is
|
|
583
|
+
* (INV-9), since a server may act on a request before it rejects the
|
|
584
|
+
* token. Whatever recovery meets is never reported as retryable: an
|
|
585
|
+
* authorization verdict keeps its code, and anything else (a disconnect or
|
|
586
|
+
* reset racing the refresh) leaves the outcome unknown. A second 401 is
|
|
587
|
+
* `auth_required`, latched for the request scope so a program's later
|
|
588
|
+
* calls do not each spend a refresh on it.
|
|
589
|
+
*/
|
|
590
|
+
const authorizedFetch = authMethod((ctx, input, init = {}) => {
|
|
591
|
+
return authFlow(function* () {
|
|
592
|
+
let prepared = takeProviderHandoff(init);
|
|
593
|
+
const auxiliary = prepared?.auxiliary === true;
|
|
594
|
+
// Own the complete token preparation and fetch helper. The coordinator
|
|
595
|
+
// still owns a submitted refresh claim independently of this wait.
|
|
596
|
+
return (yield authView(ctx, authMethod((owned) => {
|
|
597
|
+
return authFlow(function* () {
|
|
598
|
+
// A call that already ended reads no token and sends nothing more.
|
|
599
|
+
assertProviderCallOpen(ctx, id, auxiliary);
|
|
600
|
+
const owner = prepared;
|
|
601
|
+
const url = new URL(input);
|
|
602
|
+
if (!settings.apiOrigins.has(url.origin)) {
|
|
603
|
+
throw new ConnectorCallError("connector_call_failed", `Connector "${id}" sends its OAuth access token only to its declared ` +
|
|
604
|
+
`apiOrigins, and ${url.origin} is not one of them.`, { retryable: false });
|
|
605
|
+
}
|
|
606
|
+
if (init.body instanceof ReadableStream) {
|
|
607
|
+
throw new TypeError(`Connector "${id}": ctx.oauth.fetch may replay a request once after a ` +
|
|
608
|
+
"refresh, so it cannot send a stream body. Buffer the body first.");
|
|
609
|
+
}
|
|
610
|
+
const headers = new Headers(init.headers);
|
|
611
|
+
if (headers.has("authorization")) {
|
|
612
|
+
throw new ConnectorCallError("connector_call_failed", `Connector "${id}": a request through ctx.oauth.fetch may not set ` +
|
|
613
|
+
"Authorization; the connector's grant owns it.", { retryable: false });
|
|
614
|
+
}
|
|
615
|
+
const signal = init.signal ? AbortSignal.any([init.signal, owned]) : owned;
|
|
616
|
+
ctx = deriveContext(ctx, { signal });
|
|
617
|
+
const detachAnswers = [];
|
|
618
|
+
const send = authMethod((accessToken) => {
|
|
619
|
+
return authFlow(function* () {
|
|
620
|
+
if (signal?.aborted)
|
|
621
|
+
throw signal.reason;
|
|
622
|
+
sentSecretsFor(ctx).add(accessToken);
|
|
623
|
+
const sent = new Headers(headers);
|
|
624
|
+
sent.set("Authorization", `Bearer ${accessToken}`);
|
|
625
|
+
// The token rides to a declared origin and no further: a redirect is
|
|
626
|
+
// handed back to the handler unfollowed rather than re-sent anywhere.
|
|
627
|
+
// The handler reads the answer with `.json()` or `.text()`, which read
|
|
628
|
+
// bytes here: workerd quotes a text read's non-text Content-Type in
|
|
629
|
+
// its own log, out of the handler's reach.
|
|
630
|
+
const request = {
|
|
631
|
+
...init,
|
|
632
|
+
headers: sent,
|
|
633
|
+
redirect: "manual",
|
|
634
|
+
...(signal ? { signal } : {}),
|
|
635
|
+
};
|
|
636
|
+
const dispatch = prepared ?? providerRequest(ctx, auxiliary, id);
|
|
637
|
+
prepared = undefined;
|
|
638
|
+
dispatchProviderRequest(ctx, dispatch, sent, request.method ?? "GET");
|
|
639
|
+
// A native waiter can close before fetch settles. This leaf
|
|
640
|
+
// observer records only immutable response facts, and cancels
|
|
641
|
+
// a late body; it never resumes request-owned preparation.
|
|
642
|
+
const pending = sentSecretsFetch(ctx)(url, request).then((response) => {
|
|
643
|
+
recordProviderResponse(dispatch, response.status);
|
|
644
|
+
if (response.status === 401) {
|
|
645
|
+
// Closure can settle the handler before renewal's catch maps
|
|
646
|
+
// this answer. Preserve its OAuth verdict in the ledger first;
|
|
647
|
+
// only the final write decider reports it. A live renewal keeps
|
|
648
|
+
// its own precise verdict and never replays a write.
|
|
649
|
+
const abandoned = () => {
|
|
650
|
+
vendorRefusal(unsentAfterUnauthorized(`Connector "${id}"'s API`, signal.reason, "downstream_oauth_required"), dispatch);
|
|
651
|
+
};
|
|
652
|
+
if (signal.aborted)
|
|
653
|
+
abandoned();
|
|
654
|
+
else {
|
|
655
|
+
signal.addEventListener("abort", abandoned, { once: true });
|
|
656
|
+
detachAnswers.push(() => signal.removeEventListener("abort", abandoned));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// A custom fetch may answer after revocation. Its physical status
|
|
660
|
+
// remains a fact, but its body belongs to no live helper or handler.
|
|
661
|
+
if (signal.aborted)
|
|
662
|
+
void response.body?.cancel().catch(() => { });
|
|
663
|
+
return response;
|
|
664
|
+
});
|
|
665
|
+
const response = (yield authValue(() => pending));
|
|
666
|
+
if (signal.aborted)
|
|
667
|
+
throw signal.reason;
|
|
668
|
+
const answer = byteReadResponse(response);
|
|
669
|
+
bindProviderResponse(answer, dispatch, owner);
|
|
670
|
+
return { response: answer, dispatch };
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
const release = retainOAuthPartition(oauthPartitionFor(ctx));
|
|
674
|
+
try {
|
|
675
|
+
if (signal?.aborted)
|
|
676
|
+
throw signal.reason;
|
|
677
|
+
const grant = grantFor(ctx, auxiliary);
|
|
678
|
+
const stale = (yield authInvoke(grant, "token", []));
|
|
679
|
+
const { response: first, dispatch } = (yield authCall(send, [stale]));
|
|
680
|
+
if (first.status !== 401)
|
|
681
|
+
return first;
|
|
682
|
+
try {
|
|
683
|
+
(yield authCall(readChallenge, [first, signal, init.method]));
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
if (signal.aborted)
|
|
687
|
+
throw signal.reason;
|
|
688
|
+
if (!readRequest(dispatch)) {
|
|
689
|
+
throw vendorRefusal(unsentAfterUnauthorized(`Connector "${id}"'s API`, error, "downstream_oauth_required"), dispatch);
|
|
690
|
+
}
|
|
691
|
+
throw error;
|
|
692
|
+
}
|
|
693
|
+
if (!readRequest(dispatch)) {
|
|
694
|
+
let renewal;
|
|
695
|
+
try {
|
|
696
|
+
const fresh = (yield authInvoke(grant, "refresh", [stale]));
|
|
697
|
+
if (signal?.aborted)
|
|
698
|
+
throw signal.reason;
|
|
699
|
+
// The call's ledger keeps this 401's verdict, or that renewal
|
|
700
|
+
// replaced the token this request sent (Q8). The same token back
|
|
701
|
+
// renewed nothing.
|
|
702
|
+
renewedAfterUnauthorized(dispatch, stale, fresh);
|
|
703
|
+
if (fresh === stale)
|
|
704
|
+
renewal = authRequiredError();
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
if (error instanceof UnauthorizedError)
|
|
708
|
+
rejectedScopes.add(scopeOf(ctx));
|
|
709
|
+
renewal = error instanceof UnauthorizedError ? authRequiredError() : error;
|
|
710
|
+
}
|
|
711
|
+
throw vendorRefusal(unsentAfterUnauthorized(`Connector "${id}"'s API`, renewal, "downstream_oauth_required"), dispatch);
|
|
712
|
+
}
|
|
713
|
+
const accessToken = (yield authInvoke(grant, "refresh", [stale]));
|
|
714
|
+
if (signal?.aborted)
|
|
715
|
+
throw signal.reason;
|
|
716
|
+
renewedAfterUnauthorized(dispatch, stale, accessToken);
|
|
717
|
+
const { response: second } = (yield authCall(send, [accessToken]));
|
|
718
|
+
if (second.status !== 401)
|
|
719
|
+
return second;
|
|
720
|
+
void second.body?.cancel().catch(() => { });
|
|
721
|
+
return (yield authInvoke(grant, "rejected", []));
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
// The SDK can replace cancellation with its passive-consent refusal,
|
|
725
|
+
// and write recovery normally replaces errors with a 401 verdict. A
|
|
726
|
+
// closed capability must still fail promptly with its own close reason;
|
|
727
|
+
// the invocation's ledger retains the write's uncertainty and handoff.
|
|
728
|
+
if (signal?.aborted)
|
|
729
|
+
throw signal.reason;
|
|
730
|
+
if (error instanceof UnauthorizedError) {
|
|
731
|
+
rejectedScopes.add(scopeOf(ctx));
|
|
732
|
+
throw authRequiredError();
|
|
733
|
+
}
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
finally {
|
|
737
|
+
for (const detach of detachAnswers)
|
|
738
|
+
detach();
|
|
739
|
+
release();
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
}), auxiliary));
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
const status = authMethod((ctx) => {
|
|
746
|
+
return authFlow(function* () {
|
|
747
|
+
if (rejectedScopes.has(scopeOf(ctx))) {
|
|
748
|
+
return {
|
|
749
|
+
state: "auth_required",
|
|
750
|
+
message: "Authorization required \u2014 the downstream rejected this connector's OAuth grant.",
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
// A status read never starts authorization: this provider cannot, and
|
|
754
|
+
// nothing here calls the SDK. A stored grant for the configured token
|
|
755
|
+
// endpoint is healthy until a call says otherwise — the downstream is
|
|
756
|
+
// asked nothing.
|
|
348
757
|
const provider = providerFor(ctx);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const epoch = await provider.beginFlow();
|
|
352
|
-
if (provider.isOperatorDisconnectedEpoch(epoch)) {
|
|
353
|
-
throw new ConnectorCallError("downstream_oauth_required", disconnectedMessage);
|
|
758
|
+
if ((yield authInvoke(provider, "operatorDisconnected", []))) {
|
|
759
|
+
return { state: "auth_required", message: disconnectedMessage };
|
|
354
760
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
if (
|
|
365
|
-
|
|
761
|
+
return (yield authInvoke(provider, "tokens", [issuerContext]))
|
|
762
|
+
? { state: "ok" }
|
|
763
|
+
: { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
const startAuth = authMethod((original, opts) => {
|
|
767
|
+
return authFlow(function* () {
|
|
768
|
+
const ctx = authorizingContext(original);
|
|
769
|
+
const provider = providerFor(ctx);
|
|
770
|
+
if (ctx.signal?.aborted)
|
|
771
|
+
throw ctx.signal.reason;
|
|
772
|
+
const disconnected = opts?.force
|
|
773
|
+
? false
|
|
774
|
+
: (yield authInvoke(provider, "operatorDisconnected", []));
|
|
775
|
+
if (ctx.signal?.aborted)
|
|
776
|
+
throw ctx.signal.reason;
|
|
777
|
+
if (opts?.force || disconnected) {
|
|
778
|
+
(yield authInvoke(provider, "resetAuthorization", []));
|
|
779
|
+
rejectedScopes.delete(scopeOf(original));
|
|
366
780
|
}
|
|
367
781
|
else {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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();
|
|
782
|
+
// Hand back a recent consent URL rather than overwrite the verifier
|
|
783
|
+
// the operator may be mid-consent on.
|
|
784
|
+
const pending = (yield authInvoke(provider, "reusablePendingAuthorizationUrl", []));
|
|
785
|
+
if (pending) {
|
|
786
|
+
return {
|
|
787
|
+
state: "auth_required",
|
|
788
|
+
authorizationUrl: pending,
|
|
789
|
+
authorizationReused: true,
|
|
790
|
+
message: AUTH_REQUIRED_MESSAGE,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
386
793
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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) {
|
|
794
|
+
if (ctx.signal?.aborted)
|
|
795
|
+
throw ctx.signal.reason;
|
|
796
|
+
try {
|
|
797
|
+
// A grant from a since-repointed token endpoint reads as absent here,
|
|
798
|
+
// and the consent this start begins replaces it.
|
|
799
|
+
(yield authInvoke(provider, "beginFlow", []));
|
|
800
|
+
if ((yield authInvoke(provider, "tokens", [issuerContext]))) {
|
|
801
|
+
return { state: "ok", message: "Already authorized \u2014 connection is healthy." };
|
|
802
|
+
}
|
|
803
|
+
if ((yield authCall(runAuth, [provider, ctx])) === "AUTHORIZED") {
|
|
804
|
+
return { state: "ok", message: "Already authorized \u2014 connection is healthy." };
|
|
805
|
+
}
|
|
806
|
+
const authorizationUrl = (yield authInvoke(provider, "consentUrl", []));
|
|
429
807
|
return {
|
|
430
808
|
state: "auth_required",
|
|
431
|
-
authorizationUrl:
|
|
432
|
-
authorizationReused: true,
|
|
809
|
+
...(authorizationUrl !== undefined ? { authorizationUrl } : {}),
|
|
433
810
|
message: AUTH_REQUIRED_MESSAGE,
|
|
434
811
|
};
|
|
435
812
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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 };
|
|
813
|
+
catch (error) {
|
|
814
|
+
if (error instanceof UnauthorizedError) {
|
|
815
|
+
return { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
|
|
816
|
+
}
|
|
817
|
+
// Reaches the agent through authorize_connector. Text already in
|
|
818
|
+
// connecta's words passes with its advice to try again, since this is
|
|
819
|
+
// no write's failure (token responses are rebuilt before the SDK reads
|
|
820
|
+
// them); anything else is told from its record.
|
|
821
|
+
return {
|
|
822
|
+
state: "error",
|
|
823
|
+
message: error instanceof ConnectorCallError || error instanceof OAuthError
|
|
824
|
+
? withResendAdvice(msg(error), resendAdviceOf(error))
|
|
825
|
+
: describeFailure(id, error),
|
|
826
|
+
};
|
|
459
827
|
}
|
|
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
828
|
});
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
829
|
+
});
|
|
830
|
+
const disconnectAuth = authMethod((ctx) => {
|
|
831
|
+
return authFlow(function* () {
|
|
832
|
+
rejectedScopes.delete(scopeOf(ctx));
|
|
833
|
+
(yield authInvoke(providerFor(ctx), "resetAuthorization", [true]));
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
const verifyState = authMethod((state, ctx) => {
|
|
837
|
+
return authFlow(function* () {
|
|
838
|
+
// The provider that verified the state captured its flow's epoch, and the
|
|
839
|
+
// exchange must write under exactly that one.
|
|
840
|
+
const provider = providerFor(ctx);
|
|
841
|
+
callbackProviders.set(scopeOf(ctx), provider);
|
|
842
|
+
return (yield authInvoke(provider, "verifyState", [state]));
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
const finishAuth = authMethod((code, ctx, callbackParams) => {
|
|
846
|
+
return authView(ctx, authMethod(() => {
|
|
847
|
+
return authFlow(function* () {
|
|
848
|
+
const provider = callbackProviders.get(scopeOf(ctx)) ?? providerFor(ctx);
|
|
849
|
+
callbackProviders.delete(scopeOf(ctx));
|
|
850
|
+
const authorizationCode = callbackParams?.get("code") ?? code;
|
|
851
|
+
const iss = callbackParams?.get("iss") ?? undefined;
|
|
852
|
+
// Every exchange names and claims its own consent, including without PKCE.
|
|
853
|
+
const consent = callbackParams?.get("state") ?? null;
|
|
854
|
+
if (!consent || !(yield authInvoke(provider, "verifyCallbackState", [consent]))) {
|
|
855
|
+
throw new ConnectorCallError("connector_call_failed", `Connector "${id}" authorization callback matches no pending consent; nothing was exchanged.`);
|
|
856
|
+
}
|
|
857
|
+
provider.validateCallbackIssuer(callbackParams?.get("iss") ?? null);
|
|
858
|
+
provider.exchanging(authorizationCode);
|
|
859
|
+
(yield authInvoke(provider, "bindFlow", []));
|
|
860
|
+
const result = (yield authCall(runAuth, [
|
|
861
|
+
provider,
|
|
862
|
+
ctx,
|
|
863
|
+
{
|
|
864
|
+
authorizationCode,
|
|
865
|
+
...(iss !== undefined ? { iss } : {}),
|
|
866
|
+
},
|
|
867
|
+
]));
|
|
868
|
+
if (result !== "AUTHORIZED") {
|
|
869
|
+
throw new UnauthorizedError("Failed to authorize");
|
|
870
|
+
}
|
|
871
|
+
rejectedScopes.delete(scopeOf(ctx));
|
|
872
|
+
});
|
|
873
|
+
}), true);
|
|
874
|
+
});
|
|
502
875
|
return {
|
|
503
|
-
status:
|
|
504
|
-
startAuth:
|
|
505
|
-
disconnectAuth:
|
|
506
|
-
verifyState:
|
|
507
|
-
verifyCallbackIssuer:
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}),
|
|
876
|
+
status: retainAuthPartition(status, 0),
|
|
877
|
+
startAuth: retainAuthPartition(startAuth, 0),
|
|
878
|
+
disconnectAuth: retainAuthPartition(disconnectAuth, 0),
|
|
879
|
+
verifyState: retainAuthPartition(verifyState, 1),
|
|
880
|
+
verifyCallbackIssuer: retainAuthPartition(authMethod((issuer, ctx) => {
|
|
881
|
+
return authValue(() => {
|
|
882
|
+
const provider = callbackProviders.get(scopeOf(ctx));
|
|
883
|
+
if (!provider)
|
|
884
|
+
return false;
|
|
885
|
+
provider.validateCallbackIssuer(issuer);
|
|
886
|
+
return true;
|
|
887
|
+
});
|
|
888
|
+
}), 1),
|
|
889
|
+
consumeAuthError: retainAuthPartition(authMethod((ctx) => {
|
|
890
|
+
return authFlow(function* () {
|
|
891
|
+
const provider = callbackProviders.get(scopeOf(ctx));
|
|
892
|
+
callbackProviders.delete(scopeOf(ctx));
|
|
893
|
+
if (!provider)
|
|
894
|
+
throw new ConnectorCallError("connector_call_failed", "OAuth error callback matches no pending consent.");
|
|
895
|
+
(yield authInvoke(provider, "consumeAuthError", []));
|
|
896
|
+
});
|
|
897
|
+
}), 0),
|
|
898
|
+
finishAuth: retainAuthPartition(finishAuth, 1),
|
|
899
|
+
access: (ctx) => bindRenewableGrant({
|
|
900
|
+
fetch: recordingFetch(authMethod((input, init) => {
|
|
901
|
+
return authCall(authorizedFetch, [ctx, input, init]);
|
|
902
|
+
})),
|
|
903
|
+
}, grantFor(ctx)),
|
|
525
904
|
};
|
|
526
905
|
}
|
|
906
|
+
// Hook calls keep the existing owner partition pinned while their native
|
|
907
|
+
// program settles. Each Promise shell enters its own registered program.
|
|
908
|
+
function retainAuthPartition(operation, contextIndex = 0) {
|
|
909
|
+
return authMethod((...args) => authFlow(function* () {
|
|
910
|
+
const release = retainOAuthPartition(oauthPartitionFor(args[contextIndex]));
|
|
911
|
+
try {
|
|
912
|
+
return (yield authCall(operation, args));
|
|
913
|
+
}
|
|
914
|
+
finally {
|
|
915
|
+
release();
|
|
916
|
+
}
|
|
917
|
+
}));
|
|
918
|
+
}
|