@capybara-hq/kernel 0.0.0-stage → 0.1.0-rc.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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +20 -2
- package/bootstrap.json +22 -0
- package/dist/admin/admin.controller.d.ts +139 -0
- package/dist/admin/admin.controller.js +238 -0
- package/dist/admin/extensions.controller.d.ts +100 -0
- package/dist/admin/extensions.controller.js +226 -0
- package/dist/admin/setup.controller.d.ts +36 -0
- package/dist/admin/setup.controller.js +106 -0
- package/dist/admin/setup.service.d.ts +47 -0
- package/dist/admin/setup.service.js +90 -0
- package/dist/agents/agents.controller.d.ts +205 -0
- package/dist/agents/agents.controller.js +144 -0
- package/dist/agents/agents.service.d.ts +177 -0
- package/dist/agents/agents.service.js +677 -0
- package/dist/agents/definition.d.ts +198 -0
- package/dist/agents/definition.js +152 -0
- package/dist/agents/models.controller.d.ts +13 -0
- package/dist/agents/models.controller.js +31 -0
- package/dist/agents/templates.controller.d.ts +15 -0
- package/dist/agents/templates.controller.js +37 -0
- package/dist/agents/templates.d.ts +83 -0
- package/dist/agents/templates.js +173 -0
- package/dist/catalog/admin-catalog.controller.d.ts +143 -0
- package/dist/catalog/admin-catalog.controller.js +245 -0
- package/dist/catalog/catalog.service.d.ts +430 -0
- package/dist/catalog/catalog.service.js +882 -0
- package/dist/catalog/claude/agents.d.ts +27 -0
- package/dist/catalog/claude/agents.js +270 -0
- package/dist/catalog/claude/commands.d.ts +50 -0
- package/dist/catalog/claude/commands.js +179 -0
- package/dist/catalog/claude/manifest.d.ts +69 -0
- package/dist/catalog/claude/manifest.js +134 -0
- package/dist/catalog/claude/marketplace.d.ts +35 -0
- package/dist/catalog/claude/marketplace.js +194 -0
- package/dist/catalog/claude/mcp.d.ts +33 -0
- package/dist/catalog/claude/mcp.js +226 -0
- package/dist/catalog/claude/report.d.ts +86 -0
- package/dist/catalog/claude/report.js +121 -0
- package/dist/catalog/claude/translate.d.ts +56 -0
- package/dist/catalog/claude/translate.js +857 -0
- package/dist/catalog/cli.d.ts +46 -0
- package/dist/catalog/cli.js +211 -0
- package/dist/catalog/git-source.d.ts +127 -0
- package/dist/catalog/git-source.js +520 -0
- package/dist/catalog/marketplace-fetch.d.ts +99 -0
- package/dist/catalog/marketplace-fetch.js +203 -0
- package/dist/catalog/marketplaces.controller.d.ts +97 -0
- package/dist/catalog/marketplaces.controller.js +197 -0
- package/dist/catalog/marketplaces.service.d.ts +340 -0
- package/dist/catalog/marketplaces.service.js +1217 -0
- package/dist/catalog/org-catalog.controller.d.ts +128 -0
- package/dist/catalog/org-catalog.controller.js +277 -0
- package/dist/catalog/org-catalog.service.d.ts +246 -0
- package/dist/catalog/org-catalog.service.js +625 -0
- package/dist/catalog/plugin-import.d.ts +436 -0
- package/dist/catalog/plugin-import.js +1069 -0
- package/dist/catalog/plugin-install.d.ts +447 -0
- package/dist/catalog/plugin-install.js +1333 -0
- package/dist/catalog/plugin-package.d.ts +143 -0
- package/dist/catalog/plugin-package.js +382 -0
- package/dist/catalog/plugin-upgrade.d.ts +222 -0
- package/dist/catalog/plugin-upgrade.js +1219 -0
- package/dist/catalog/requests.service.d.ts +162 -0
- package/dist/catalog/requests.service.js +504 -0
- package/dist/catalog/schema-path.d.ts +7 -0
- package/dist/catalog/schema-path.js +63 -0
- package/dist/catalog/upgrade-targets.d.ts +59 -0
- package/dist/catalog/upgrade-targets.js +90 -0
- package/dist/catalog/versions.d.ts +31 -0
- package/dist/catalog/versions.js +44 -0
- package/dist/channels/actions.d.ts +37 -0
- package/dist/channels/actions.js +121 -0
- package/dist/channels/cards.d.ts +44 -0
- package/dist/channels/cards.js +156 -0
- package/dist/channels/connections.controller.d.ts +59 -0
- package/dist/channels/connections.controller.js +115 -0
- package/dist/channels/connections.service.d.ts +187 -0
- package/dist/channels/connections.service.js +534 -0
- package/dist/channels/features.d.ts +12 -0
- package/dist/channels/features.js +22 -0
- package/dist/channels/inbound.d.ts +120 -0
- package/dist/channels/inbound.js +332 -0
- package/dist/channels/outbound.d.ts +140 -0
- package/dist/channels/outbound.js +1125 -0
- package/dist/channels/phrases.d.ts +100 -0
- package/dist/channels/phrases.js +81 -0
- package/dist/channels/supervisor.d.ts +182 -0
- package/dist/channels/supervisor.js +625 -0
- package/dist/channels/threads.d.ts +29 -0
- package/dist/channels/threads.js +104 -0
- package/dist/channels/webhook-url.d.ts +16 -0
- package/dist/channels/webhook-url.js +34 -0
- package/dist/channels/webhook.controller.d.ts +19 -0
- package/dist/channels/webhook.controller.js +72 -0
- package/dist/channels/webhook.d.ts +73 -0
- package/dist/channels/webhook.js +274 -0
- package/dist/config/docs.d.ts +9 -0
- package/dist/config/docs.js +85 -0
- package/dist/config/env.d.ts +6 -0
- package/dist/config/env.js +73 -0
- package/dist/config/fields.d.ts +42 -0
- package/dist/config/fields.js +214 -0
- package/dist/config/file-source.d.ts +3 -0
- package/dist/config/file-source.js +31 -0
- package/dist/config/load.d.ts +20 -0
- package/dist/config/load.js +98 -0
- package/dist/config/nacos-source.d.ts +31 -0
- package/dist/config/nacos-source.js +229 -0
- package/dist/config/schema.d.ts +223 -0
- package/dist/config/schema.js +1151 -0
- package/dist/config/sources.d.ts +18 -0
- package/dist/config/sources.js +59 -0
- package/dist/config/types.d.ts +9 -0
- package/dist/config/types.js +7 -0
- package/dist/conversations/artifacts.d.ts +52 -0
- package/dist/conversations/artifacts.js +137 -0
- package/dist/conversations/attachments.controller.d.ts +28 -0
- package/dist/conversations/attachments.controller.js +96 -0
- package/dist/conversations/attachments.service.d.ts +180 -0
- package/dist/conversations/attachments.service.js +578 -0
- package/dist/conversations/conversations.controller.d.ts +288 -0
- package/dist/conversations/conversations.controller.js +607 -0
- package/dist/conversations/conversations.service.d.ts +321 -0
- package/dist/conversations/conversations.service.js +754 -0
- package/dist/conversations/event-log.d.ts +122 -0
- package/dist/conversations/event-log.js +259 -0
- package/dist/conversations/events.d.ts +856 -0
- package/dist/conversations/events.js +639 -0
- package/dist/conversations/files.d.ts +92 -0
- package/dist/conversations/files.js +213 -0
- package/dist/conversations/outputs-read.d.ts +34 -0
- package/dist/conversations/outputs-read.js +37 -0
- package/dist/conversations/queue.d.ts +169 -0
- package/dist/conversations/queue.js +527 -0
- package/dist/conversations/sandbox-status.d.ts +21 -0
- package/dist/conversations/sandbox-status.js +24 -0
- package/dist/conversations/selection.d.ts +46 -0
- package/dist/conversations/selection.js +75 -0
- package/dist/conversations/titles.d.ts +93 -0
- package/dist/conversations/titles.js +422 -0
- package/dist/ctx/bus.d.ts +16 -0
- package/dist/ctx/bus.js +101 -0
- package/dist/ctx/create-ctx.d.ts +109 -0
- package/dist/ctx/create-ctx.js +592 -0
- package/dist/ctx/kv.d.ts +4 -0
- package/dist/ctx/kv.js +31 -0
- package/dist/ctx/lock.d.ts +4 -0
- package/dist/ctx/lock.js +39 -0
- package/dist/ctx/namespace.d.ts +6 -0
- package/dist/ctx/namespace.js +12 -0
- package/dist/ctx/queue.d.ts +25 -0
- package/dist/ctx/queue.js +150 -0
- package/dist/ctx/secrets.d.ts +45 -0
- package/dist/ctx/secrets.js +175 -0
- package/dist/db/kernel-schema.d.ts +1122 -0
- package/dist/db/kernel-schema.js +5 -0
- package/dist/db/migrator.d.ts +28 -0
- package/dist/db/migrator.js +140 -0
- package/dist/db/pool.d.ts +21 -0
- package/dist/db/pool.js +28 -0
- package/dist/db/sql-statements.d.ts +34 -0
- package/dist/db/sql-statements.js +168 -0
- package/dist/errors.d.ts +210 -0
- package/dist/errors.js +212 -0
- package/dist/evals/budget.d.ts +61 -0
- package/dist/evals/budget.js +181 -0
- package/dist/evals/diff.d.ts +55 -0
- package/dist/evals/diff.js +133 -0
- package/dist/evals/judge.d.ts +53 -0
- package/dist/evals/judge.js +149 -0
- package/dist/evals/plan.d.ts +47 -0
- package/dist/evals/plan.js +77 -0
- package/dist/evals/reconstruct.d.ts +137 -0
- package/dist/evals/reconstruct.js +322 -0
- package/dist/evals/recorded-tools.d.ts +54 -0
- package/dist/evals/recorded-tools.js +167 -0
- package/dist/evals/run-replay.d.ts +86 -0
- package/dist/evals/run-replay.js +502 -0
- package/dist/evals/runs.controller.d.ts +100 -0
- package/dist/evals/runs.controller.js +199 -0
- package/dist/evals/runs.service.d.ts +275 -0
- package/dist/evals/runs.service.js +836 -0
- package/dist/evals/sets.controller.d.ts +61 -0
- package/dist/evals/sets.controller.js +183 -0
- package/dist/evals/sets.service.d.ts +128 -0
- package/dist/evals/sets.service.js +558 -0
- package/dist/evals/snapshot.d.ts +76 -0
- package/dist/evals/snapshot.js +207 -0
- package/dist/evals/step-replay.d.ts +113 -0
- package/dist/evals/step-replay.js +181 -0
- package/dist/evals/trajectory.controller.d.ts +41 -0
- package/dist/evals/trajectory.controller.js +88 -0
- package/dist/evals/trajectory.d.ts +144 -0
- package/dist/evals/trajectory.js +338 -0
- package/dist/evals/trajectory.service.d.ts +108 -0
- package/dist/evals/trajectory.service.js +323 -0
- package/dist/evals/worker.d.ts +101 -0
- package/dist/evals/worker.js +789 -0
- package/dist/extensions/admin-ops.d.ts +105 -0
- package/dist/extensions/admin-ops.js +353 -0
- package/dist/extensions/admin-view.d.ts +79 -0
- package/dist/extensions/admin-view.js +327 -0
- package/dist/extensions/approvals.d.ts +119 -0
- package/dist/extensions/approvals.js +205 -0
- package/dist/extensions/blobs.d.ts +45 -0
- package/dist/extensions/blobs.js +160 -0
- package/dist/extensions/bootstrap.d.ts +149 -0
- package/dist/extensions/bootstrap.js +441 -0
- package/dist/extensions/cli.d.ts +61 -0
- package/dist/extensions/cli.js +165 -0
- package/dist/extensions/config-json-schema.d.ts +9 -0
- package/dist/extensions/config-json-schema.js +73 -0
- package/dist/extensions/deployment-config.d.ts +138 -0
- package/dist/extensions/deployment-config.js +394 -0
- package/dist/extensions/enabled.d.ts +31 -0
- package/dist/extensions/enabled.js +55 -0
- package/dist/extensions/hooks.d.ts +161 -0
- package/dist/extensions/hooks.js +425 -0
- package/dist/extensions/host.d.ts +577 -0
- package/dist/extensions/host.js +2071 -0
- package/dist/extensions/inflight.d.ts +69 -0
- package/dist/extensions/inflight.js +277 -0
- package/dist/extensions/installs.d.ts +166 -0
- package/dist/extensions/installs.js +253 -0
- package/dist/extensions/instance.d.ts +167 -0
- package/dist/extensions/instance.js +329 -0
- package/dist/extensions/ledger.d.ts +30 -0
- package/dist/extensions/ledger.js +50 -0
- package/dist/extensions/lifecycle.d.ts +80 -0
- package/dist/extensions/lifecycle.js +152 -0
- package/dist/extensions/migrate-lock.d.ts +51 -0
- package/dist/extensions/migrate-lock.js +100 -0
- package/dist/extensions/org-config.controller.d.ts +24 -0
- package/dist/extensions/org-config.controller.js +59 -0
- package/dist/extensions/org-config.d.ts +169 -0
- package/dist/extensions/org-config.js +520 -0
- package/dist/extensions/package.d.ts +115 -0
- package/dist/extensions/package.js +246 -0
- package/dist/extensions/permission-set.d.ts +117 -0
- package/dist/extensions/permission-set.js +184 -0
- package/dist/extensions/permission-view.d.ts +194 -0
- package/dist/extensions/permission-view.js +284 -0
- package/dist/extensions/registry.d.ts +24 -0
- package/dist/extensions/registry.js +56 -0
- package/dist/extensions/resolve.d.ts +12 -0
- package/dist/extensions/resolve.js +71 -0
- package/dist/extensions/sources.d.ts +69 -0
- package/dist/extensions/sources.js +120 -0
- package/dist/extensions/sync.d.ts +40 -0
- package/dist/extensions/sync.js +242 -0
- package/dist/files/previewers.controller.d.ts +46 -0
- package/dist/files/previewers.controller.js +105 -0
- package/dist/files/previewers.d.ts +100 -0
- package/dist/files/previewers.js +250 -0
- package/dist/http/api.module.d.ts +114 -0
- package/dist/http/api.module.js +448 -0
- package/dist/http/app.d.ts +77 -0
- package/dist/http/app.js +170 -0
- package/dist/http/error.filter.d.ts +21 -0
- package/dist/http/error.filter.js +85 -0
- package/dist/http/gateway.module.d.ts +15 -0
- package/dist/http/gateway.module.js +13 -0
- package/dist/http/health.controller.d.ts +26 -0
- package/dist/http/health.controller.js +79 -0
- package/dist/http/nest-logger.d.ts +20 -0
- package/dist/http/nest-logger.js +28 -0
- package/dist/http/plugin-dispatch.d.ts +68 -0
- package/dist/http/plugin-dispatch.js +200 -0
- package/dist/http/tokens.d.ts +131 -0
- package/dist/http/tokens.js +131 -0
- package/dist/http/zod.pipe.d.ts +8 -0
- package/dist/http/zod.pipe.js +14 -0
- package/dist/identity/accounts.d.ts +93 -0
- package/dist/identity/accounts.js +367 -0
- package/dist/identity/api-tokens.d.ts +73 -0
- package/dist/identity/api-tokens.js +197 -0
- package/dist/identity/audit.d.ts +23 -0
- package/dist/identity/audit.js +41 -0
- package/dist/identity/auth.controller.d.ts +40 -0
- package/dist/identity/auth.controller.js +130 -0
- package/dist/identity/auth.guard.d.ts +41 -0
- package/dist/identity/auth.guard.js +162 -0
- package/dist/identity/binding.d.ts +47 -0
- package/dist/identity/binding.js +84 -0
- package/dist/identity/cookies.d.ts +16 -0
- package/dist/identity/cookies.js +32 -0
- package/dist/identity/credential-check.d.ts +17 -0
- package/dist/identity/credential-check.js +24 -0
- package/dist/identity/enterprise-admin.d.ts +14 -0
- package/dist/identity/enterprise-admin.js +27 -0
- package/dist/identity/external.d.ts +106 -0
- package/dist/identity/external.js +337 -0
- package/dist/identity/hash.d.ts +2 -0
- package/dist/identity/hash.js +3 -0
- package/dist/identity/login.d.ts +6 -0
- package/dist/identity/login.js +20 -0
- package/dist/identity/me.controller.d.ts +61 -0
- package/dist/identity/me.controller.js +159 -0
- package/dist/identity/passwords.d.ts +6 -0
- package/dist/identity/passwords.js +37 -0
- package/dist/identity/principal.d.ts +56 -0
- package/dist/identity/principal.js +38 -0
- package/dist/identity/rate-limit.d.ts +55 -0
- package/dist/identity/rate-limit.js +121 -0
- package/dist/identity/sessions.d.ts +84 -0
- package/dist/identity/sessions.js +306 -0
- package/dist/identity/settings.d.ts +121 -0
- package/dist/identity/settings.js +248 -0
- package/dist/identity/sso.controller.d.ts +59 -0
- package/dist/identity/sso.controller.js +192 -0
- package/dist/identity/sso.d.ts +83 -0
- package/dist/identity/sso.js +202 -0
- package/dist/inbox/inbox.service.d.ts +43 -0
- package/dist/inbox/inbox.service.js +57 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +65 -0
- package/dist/kernel.d.ts +256 -0
- package/dist/kernel.js +1326 -0
- package/dist/log.d.ts +48 -0
- package/dist/log.js +229 -0
- package/dist/models/prices.d.ts +53 -0
- package/dist/models/prices.js +121 -0
- package/dist/models/purposes.controller.d.ts +19 -0
- package/dist/models/purposes.controller.js +56 -0
- package/dist/models/purposes.d.ts +140 -0
- package/dist/models/purposes.js +267 -0
- package/dist/net/proxy.d.ts +44 -0
- package/dist/net/proxy.js +162 -0
- package/dist/net/public-address.d.ts +60 -0
- package/dist/net/public-address.js +271 -0
- package/dist/net/safe-fetch.d.ts +38 -0
- package/dist/net/safe-fetch.js +140 -0
- package/dist/orgs/directory-sync.controller.d.ts +14 -0
- package/dist/orgs/directory-sync.controller.js +40 -0
- package/dist/orgs/directory-sync.d.ts +166 -0
- package/dist/orgs/directory-sync.js +558 -0
- package/dist/orgs/invitations.controller.d.ts +58 -0
- package/dist/orgs/invitations.controller.js +137 -0
- package/dist/orgs/invitations.service.d.ts +80 -0
- package/dist/orgs/invitations.service.js +217 -0
- package/dist/orgs/members.service.d.ts +63 -0
- package/dist/orgs/members.service.js +234 -0
- package/dist/orgs/offboarding.d.ts +141 -0
- package/dist/orgs/offboarding.js +398 -0
- package/dist/orgs/org-lock.d.ts +32 -0
- package/dist/orgs/org-lock.js +46 -0
- package/dist/orgs/org.guard.d.ts +35 -0
- package/dist/orgs/org.guard.js +95 -0
- package/dist/orgs/orgs.controller.d.ts +78 -0
- package/dist/orgs/orgs.controller.js +192 -0
- package/dist/orgs/orgs.service.d.ts +61 -0
- package/dist/orgs/orgs.service.js +209 -0
- package/dist/orgs/roles.d.ts +13 -0
- package/dist/orgs/roles.js +58 -0
- package/dist/orgs/settings.controller.d.ts +20 -0
- package/dist/orgs/settings.controller.js +53 -0
- package/dist/orgs/settings.service.d.ts +120 -0
- package/dist/orgs/settings.service.js +178 -0
- package/dist/packages/npm.d.ts +50 -0
- package/dist/packages/npm.js +218 -0
- package/dist/packages/provenance.d.ts +47 -0
- package/dist/packages/provenance.js +147 -0
- package/dist/packages/tgz.d.ts +76 -0
- package/dist/packages/tgz.js +225 -0
- package/dist/runtime/abort.d.ts +32 -0
- package/dist/runtime/abort.js +71 -0
- package/dist/runtime/checkpointer.d.ts +32 -0
- package/dist/runtime/checkpointer.js +243 -0
- package/dist/runtime/context/build.d.ts +254 -0
- package/dist/runtime/context/build.js +693 -0
- package/dist/runtime/context/claude-tools.d.ts +11 -0
- package/dist/runtime/context/claude-tools.js +24 -0
- package/dist/runtime/context/compaction.d.ts +26 -0
- package/dist/runtime/context/compaction.js +195 -0
- package/dist/runtime/context/pairing.d.ts +11 -0
- package/dist/runtime/context/pairing.js +82 -0
- package/dist/runtime/context/reminders.d.ts +5 -0
- package/dist/runtime/context/reminders.js +11 -0
- package/dist/runtime/context/skills.d.ts +105 -0
- package/dist/runtime/context/skills.js +283 -0
- package/dist/runtime/context/tokens.d.ts +19 -0
- package/dist/runtime/context/tokens.js +104 -0
- package/dist/runtime/conversation-lock.d.ts +40 -0
- package/dist/runtime/conversation-lock.js +97 -0
- package/dist/runtime/hook-calls.d.ts +84 -0
- package/dist/runtime/hook-calls.js +136 -0
- package/dist/runtime/langgraph-loop.d.ts +52 -0
- package/dist/runtime/langgraph-loop.js +247 -0
- package/dist/runtime/loop.d.ts +93 -0
- package/dist/runtime/loop.js +7 -0
- package/dist/runtime/maintenance.d.ts +80 -0
- package/dist/runtime/maintenance.js +147 -0
- package/dist/runtime/media-cache.d.ts +21 -0
- package/dist/runtime/media-cache.js +51 -0
- package/dist/runtime/model-call.d.ts +176 -0
- package/dist/runtime/model-call.js +1049 -0
- package/dist/runtime/providers.d.ts +86 -0
- package/dist/runtime/providers.js +224 -0
- package/dist/runtime/quota.d.ts +29 -0
- package/dist/runtime/quota.js +70 -0
- package/dist/runtime/run-finish.d.ts +45 -0
- package/dist/runtime/run-finish.js +69 -0
- package/dist/runtime/run-queue.d.ts +121 -0
- package/dist/runtime/run-queue.js +493 -0
- package/dist/runtime/run-worker.d.ts +138 -0
- package/dist/runtime/run-worker.js +805 -0
- package/dist/runtime/stream.controller.d.ts +34 -0
- package/dist/runtime/stream.controller.js +122 -0
- package/dist/runtime/stream.d.ts +63 -0
- package/dist/runtime/stream.js +262 -0
- package/dist/runtime/subagents/child.d.ts +80 -0
- package/dist/runtime/subagents/child.js +225 -0
- package/dist/runtime/subagents/definition.d.ts +103 -0
- package/dist/runtime/subagents/definition.js +392 -0
- package/dist/runtime/subagents/dispatch.d.ts +69 -0
- package/dist/runtime/subagents/dispatch.js +502 -0
- package/dist/runtime/subagents/limits.d.ts +23 -0
- package/dist/runtime/subagents/limits.js +34 -0
- package/dist/runtime/subagents/org-agents.d.ts +50 -0
- package/dist/runtime/subagents/org-agents.js +70 -0
- package/dist/runtime/subagents/preamble.d.ts +7 -0
- package/dist/runtime/subagents/preamble.js +11 -0
- package/dist/runtime/subagents/progress.d.ts +26 -0
- package/dist/runtime/subagents/progress.js +36 -0
- package/dist/runtime/subagents/roster.d.ts +55 -0
- package/dist/runtime/subagents/roster.js +111 -0
- package/dist/runtime/subagents/skill-fork.d.ts +123 -0
- package/dist/runtime/subagents/skill-fork.js +259 -0
- package/dist/runtime/usage-report.d.ts +163 -0
- package/dist/runtime/usage-report.js +550 -0
- package/dist/runtime/waits.d.ts +233 -0
- package/dist/runtime/waits.js +883 -0
- package/dist/sandbox/leases.d.ts +169 -0
- package/dist/sandbox/leases.js +515 -0
- package/dist/sandbox/lifecycle.d.ts +21 -0
- package/dist/sandbox/lifecycle.js +68 -0
- package/dist/sandbox/sandbox.service.d.ts +249 -0
- package/dist/sandbox/sandbox.service.js +908 -0
- package/dist/skills/catalog.d.ts +149 -0
- package/dist/skills/catalog.js +405 -0
- package/dist/skills/changes.controller.d.ts +56 -0
- package/dist/skills/changes.controller.js +136 -0
- package/dist/skills/changes.service.d.ts +175 -0
- package/dist/skills/changes.service.js +805 -0
- package/dist/skills/invocable.controller.d.ts +23 -0
- package/dist/skills/invocable.controller.js +50 -0
- package/dist/skills/invocation-keys.d.ts +23 -0
- package/dist/skills/invocation-keys.js +44 -0
- package/dist/skills/invoke.d.ts +135 -0
- package/dist/skills/invoke.js +286 -0
- package/dist/skills/package.d.ts +87 -0
- package/dist/skills/package.js +227 -0
- package/dist/skills/skills.controller.d.ts +40 -0
- package/dist/skills/skills.controller.js +110 -0
- package/dist/skills/skills.service.d.ts +192 -0
- package/dist/skills/skills.service.js +567 -0
- package/dist/storage/prefix.d.ts +36 -0
- package/dist/storage/prefix.js +104 -0
- package/dist/storage/purge.d.ts +27 -0
- package/dist/storage/purge.js +75 -0
- package/dist/storage/storage.service.d.ts +44 -0
- package/dist/storage/storage.service.js +145 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.js +13 -0
- package/dist/tools/approvals.controller.d.ts +53 -0
- package/dist/tools/approvals.controller.js +128 -0
- package/dist/tools/approvals.service.d.ts +172 -0
- package/dist/tools/approvals.service.js +568 -0
- package/dist/tools/breaker.d.ts +28 -0
- package/dist/tools/breaker.js +72 -0
- package/dist/tools/builtins/agent.d.ts +81 -0
- package/dist/tools/builtins/agent.js +223 -0
- package/dist/tools/builtins/ask-user.d.ts +28 -0
- package/dist/tools/builtins/ask-user.js +139 -0
- package/dist/tools/builtins/read-tool-output.d.ts +18 -0
- package/dist/tools/builtins/read-tool-output.js +108 -0
- package/dist/tools/builtins/sandbox/bash.d.ts +16 -0
- package/dist/tools/builtins/sandbox/bash.js +137 -0
- package/dist/tools/builtins/sandbox/common.d.ts +114 -0
- package/dist/tools/builtins/sandbox/common.js +304 -0
- package/dist/tools/builtins/sandbox/deliver.d.ts +5 -0
- package/dist/tools/builtins/sandbox/deliver.js +180 -0
- package/dist/tools/builtins/sandbox/edit.d.ts +4 -0
- package/dist/tools/builtins/sandbox/edit.js +98 -0
- package/dist/tools/builtins/sandbox/glob-match.d.ts +25 -0
- package/dist/tools/builtins/sandbox/glob-match.js +240 -0
- package/dist/tools/builtins/sandbox/glob.d.ts +4 -0
- package/dist/tools/builtins/sandbox/glob.js +104 -0
- package/dist/tools/builtins/sandbox/grep.d.ts +9 -0
- package/dist/tools/builtins/sandbox/grep.js +133 -0
- package/dist/tools/builtins/sandbox/index.d.ts +12 -0
- package/dist/tools/builtins/sandbox/index.js +27 -0
- package/dist/tools/builtins/sandbox/names.d.ts +7 -0
- package/dist/tools/builtins/sandbox/names.js +20 -0
- package/dist/tools/builtins/sandbox/read.d.ts +8 -0
- package/dist/tools/builtins/sandbox/read.js +121 -0
- package/dist/tools/builtins/sandbox/write.d.ts +4 -0
- package/dist/tools/builtins/sandbox/write.js +88 -0
- package/dist/tools/builtins/skill-load.d.ts +53 -0
- package/dist/tools/builtins/skill-load.js +446 -0
- package/dist/tools/builtins/skill-write.d.ts +59 -0
- package/dist/tools/builtins/skill-write.js +244 -0
- package/dist/tools/builtins/tool-search.d.ts +31 -0
- package/dist/tools/builtins/tool-search.js +171 -0
- package/dist/tools/connector-content.controller.d.ts +34 -0
- package/dist/tools/connector-content.controller.js +78 -0
- package/dist/tools/connector-content.d.ts +104 -0
- package/dist/tools/connector-content.js +267 -0
- package/dist/tools/connectors.controller.d.ts +210 -0
- package/dist/tools/connectors.controller.js +234 -0
- package/dist/tools/connectors.service.d.ts +303 -0
- package/dist/tools/connectors.service.js +1102 -0
- package/dist/tools/credentials.d.ts +139 -0
- package/dist/tools/credentials.js +351 -0
- package/dist/tools/drift.d.ts +141 -0
- package/dist/tools/drift.js +539 -0
- package/dist/tools/elicitation.d.ts +81 -0
- package/dist/tools/elicitation.js +266 -0
- package/dist/tools/endpoint.d.ts +18 -0
- package/dist/tools/endpoint.js +115 -0
- package/dist/tools/info.d.ts +67 -0
- package/dist/tools/info.js +113 -0
- package/dist/tools/naming.d.ts +27 -0
- package/dist/tools/naming.js +47 -0
- package/dist/tools/oauth/discovery.d.ts +65 -0
- package/dist/tools/oauth/discovery.js +307 -0
- package/dist/tools/oauth/flow.d.ts +63 -0
- package/dist/tools/oauth/flow.js +237 -0
- package/dist/tools/oauth/oauth.controller.d.ts +31 -0
- package/dist/tools/oauth/oauth.controller.js +95 -0
- package/dist/tools/oauth/tokens.d.ts +111 -0
- package/dist/tools/oauth/tokens.js +258 -0
- package/dist/tools/outputs.d.ts +61 -0
- package/dist/tools/outputs.js +156 -0
- package/dist/tools/pipeline.d.ts +322 -0
- package/dist/tools/pipeline.js +1236 -0
- package/dist/tools/policy.d.ts +60 -0
- package/dist/tools/policy.js +236 -0
- package/dist/tools/questions.controller.d.ts +24 -0
- package/dist/tools/questions.controller.js +58 -0
- package/dist/tools/questions.service.d.ts +103 -0
- package/dist/tools/questions.service.js +295 -0
- package/dist/tools/redact.d.ts +20 -0
- package/dist/tools/redact.js +60 -0
- package/dist/tools/registry.d.ts +246 -0
- package/dist/tools/registry.js +481 -0
- package/dist/tools/resources.d.ts +16 -0
- package/dist/tools/resources.js +76 -0
- package/dist/tools/specs.d.ts +14 -0
- package/dist/tools/specs.js +31 -0
- package/dist/tools/validate.d.ts +20 -0
- package/dist/tools/validate.js +183 -0
- package/dist/tools/view.d.ts +43 -0
- package/dist/tools/view.js +225 -0
- package/dist/ui/embed-token.d.ts +31 -0
- package/dist/ui/embed-token.js +132 -0
- package/dist/ui/registry.d.ts +58 -0
- package/dist/ui/registry.js +90 -0
- package/dist/ui/ui-stream.d.ts +37 -0
- package/dist/ui/ui-stream.js +162 -0
- package/dist/ui/views.controller.d.ts +81 -0
- package/dist/ui/views.controller.js +261 -0
- package/dist/ui/views.d.ts +155 -0
- package/dist/ui/views.js +306 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +6 -0
- package/package.json +67 -3
- package/src/migrations/0001_init.sql +8 -0
- package/src/migrations/0002_secrets.sql +8 -0
- package/src/migrations/0003_identity.sql +117 -0
- package/src/migrations/0004_plugin_org_config_nullable.sql +3 -0
- package/src/migrations/0005_agents.sql +27 -0
- package/src/migrations/0006_conversations.sql +37 -0
- package/src/migrations/0007_checkpoints.sql +26 -0
- package/src/migrations/0008_runs_trigger_seq.sql +3 -0
- package/src/migrations/0009_runs_active_unique.sql +4 -0
- package/src/migrations/0010_tools.sql +125 -0
- package/src/migrations/0011_approvals_spec.sql +10 -0
- package/src/migrations/0012_run_waits.sql +24 -0
- package/src/migrations/0013_app_tools_source.sql +4 -0
- package/src/migrations/0014_tool_specs.sql +8 -0
- package/src/migrations/0015_tool_drift.sql +20 -0
- package/src/migrations/0016_plugin_permissions.sql +11 -0
- package/src/migrations/0017_identity_sources.sql +6 -0
- package/src/migrations/0018_channels.sql +74 -0
- package/src/migrations/0019_identity_trust.sql +17 -0
- package/src/migrations/0020_channel_card_lookup.sql +10 -0
- package/src/migrations/0021_org_storage_purge.sql +9 -0
- package/src/migrations/0022_attachments.sql +29 -0
- package/src/migrations/0023_sandboxes.sql +37 -0
- package/src/migrations/0024_skills.sql +46 -0
- package/src/migrations/0025_skills_catalog_idx.sql +6 -0
- package/src/migrations/0026_skill_changes.sql +34 -0
- package/src/migrations/0027_model_prices.sql +18 -0
- package/src/migrations/0028_evals.sql +105 -0
- package/src/migrations/0029_model_purposes.sql +18 -0
- package/src/migrations/0030_plugin_installs.sql +73 -0
- package/src/migrations/0031_plugin_snapshot_source.sql +6 -0
- package/src/migrations/0032_plugin_deployment_configs.sql +10 -0
- package/src/migrations/0033_rename_apps_to_connectors.sql +25 -0
- package/src/migrations/0034_directory.sql +49 -0
- package/src/migrations/0035_catalogs.sql +40 -0
- package/src/migrations/0036_app_import_staging.sql +17 -0
- package/src/migrations/0037_directory_installs.sql +92 -0
- package/src/migrations/0038_channel_inbound_mode.sql +6 -0
- package/src/migrations/0039_channel_card_pending.sql +8 -0
- package/src/migrations/0040_usage_report_sweep.sql +10 -0
- package/src/migrations/0041_rename_plugins_to_extensions.sql +38 -0
- package/src/migrations/0042_rename_directory_to_catalog.sql +80 -0
- package/src/migrations/0043_rename_app_installs_to_plugin_installs.sql +20 -0
- package/src/migrations/0044_catalog_one_kind.sql +90 -0
- package/src/migrations/0045_plugin_dependencies.sql +9 -0
- package/src/migrations/0046_git_sources.sql +25 -0
- package/src/migrations/0047_marketplace_entry_heads.sql +7 -0
- package/src/migrations/0048_marketplace_entry_trees.sql +8 -0
- package/src/migrations/0049_plugin_upgrades.sql +37 -0
- package/src/migrations/0050_bootstrap.sql +53 -0
- package/src/migrations/0051_claude_compat.sql +26 -0
- package/src/migrations/0052_conversations_account_agent_idx.sql +5 -0
- package/src/migrations/0053_subagents.sql +73 -0
- package/src/migrations/0054_subagent_wait_expiry.sql +7 -0
- package/src/migrations/0055_subagent_parent_indexes.sql +10 -0
- package/src/migrations/0056_approval_reason.sql +3 -0
- package/src/migrations/0057_conversation_titles.sql +11 -0
- package/src/migrations/0058_conversation_selection.sql +4 -0
- package/src/migrations/0059_conversation_queue.sql +22 -0
- package/src/migrations/0060_file_previewers.sql +10 -0
- package/src/migrations/0061_queue_selection.sql +5 -0
- package/src/migrations/0062_queued_skill.sql +8 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { KernelConfig } from "../../config/schema.js";
|
|
2
|
+
import type { OAuthClientRecord, TokenAuthMethod } from "./tokens.js";
|
|
3
|
+
/**
|
|
4
|
+
* MCP OAuth 发现与动态注册(MCP 授权规范):
|
|
5
|
+
* RFC 9728 受保护资源元数据(401 的 `WWW-Authenticate: Bearer resource_metadata=…`,或
|
|
6
|
+
* `/.well-known/oauth-protected-resource[/<path>]`)→ RFC 8414 授权服务器元数据(OpenID 发现文档作回退)
|
|
7
|
+
* → RFC 7591 动态客户端注册。
|
|
8
|
+
*
|
|
9
|
+
* 自己实现而不引库:内核只需要这几次 HTTP 往返,且要统一「不跟随重定向、限时、限响应大小、只允许 https」
|
|
10
|
+
* 与错误映射(错误只带原因码与英文说明,不带对方响应正文)。`oauth4webapi` 默认禁止 http、自带错误模型,
|
|
11
|
+
* 换来的代码量并不少;MCP SDK 的 client auth 帮助函数围绕交互式 `OAuthClientProvider`,且会把 MCP SDK
|
|
12
|
+
* 拉进与协议无关的内核。
|
|
13
|
+
*/
|
|
14
|
+
export interface OAuthHttpOptions {
|
|
15
|
+
/** 允许本机(127.0.0.1 / localhost / [::1])的 http,仅开发与测试。 */
|
|
16
|
+
allowInsecureLoopback: boolean;
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
}
|
|
19
|
+
/** OAuth 往返的限时:工具超时与 30 秒取小。 */
|
|
20
|
+
export declare function oauthHttpOptions(cfg: KernelConfig): OAuthHttpOptions;
|
|
21
|
+
export type OAuthSetupReason = "insecure_url" | "discovery_failed" | "dcr_unsupported" | "dcr_failed" | "token_failed";
|
|
22
|
+
/** 发现 / 注册 / 安装时取 token 失败:原因码供向导分支(如 `dcr_unsupported` 改为手填 client),说明为英文。 */
|
|
23
|
+
export declare class OAuthSetupError extends Error {
|
|
24
|
+
readonly reason: OAuthSetupReason;
|
|
25
|
+
constructor(reason: OAuthSetupReason, message: string);
|
|
26
|
+
}
|
|
27
|
+
/** 只接受 https(放行时本机 http 也可),不允许 URL 里带用户信息。 */
|
|
28
|
+
export declare function checkOAuthUrl(raw: string, http: OAuthHttpOptions): URL;
|
|
29
|
+
export interface OAuthResponse {
|
|
30
|
+
status: number;
|
|
31
|
+
headers: Headers;
|
|
32
|
+
text: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* OAuth 往返:校验 URL、不跟随重定向(3xx 原样返回,调用方按失败处理)、限时、正文超过 1 MiB 即中止。
|
|
36
|
+
* 网络错误 / 超时原样抛出。
|
|
37
|
+
*/
|
|
38
|
+
export declare function oauthFetch(url: string, init: RequestInit, http: OAuthHttpOptions): Promise<OAuthResponse>;
|
|
39
|
+
/** 发现结果:授权服务器端点 + 资源标识(RFC 8707 `resource` 参数)。 */
|
|
40
|
+
export interface McpAuthMetadata {
|
|
41
|
+
resource: string;
|
|
42
|
+
issuer: string;
|
|
43
|
+
authorizationEndpoint: string;
|
|
44
|
+
tokenEndpoint: string;
|
|
45
|
+
registrationEndpoint?: string;
|
|
46
|
+
/** 401 的 `scope` 参数,或受保护资源元数据的 `scopes_supported`。 */
|
|
47
|
+
scopes?: string[];
|
|
48
|
+
/** 授权服务器的 `token_endpoint_auth_methods_supported`。 */
|
|
49
|
+
authMethods?: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 对 MCP 端点做 OAuth 发现:先以不带凭据的 `initialize` 探测拿 401 的 `WWW-Authenticate`,再取受保护资源元数据,
|
|
53
|
+
* 再取授权服务器元数据(RFC 8414 §3.1 的路径插入式,OpenID 发现作回退)。校验资源标识与端点匹配、
|
|
54
|
+
* 元数据 `issuer` 与授权服务器标识一致、声明了 PKCE 方法时必须含 S256。
|
|
55
|
+
*/
|
|
56
|
+
export declare function discoverMcp(endpointUrl: string, http: OAuthHttpOptions): Promise<McpAuthMetadata>;
|
|
57
|
+
/** 按授权服务器声明的方法选客户端认证方式(未声明时 RFC 8414 默认 client_secret_basic)。 */
|
|
58
|
+
export declare function pickAuthMethod(supported: string[] | undefined, hasSecret: boolean): TokenAuthMethod;
|
|
59
|
+
/**
|
|
60
|
+
* 动态客户端注册(RFC 7591),每个连接器注册一次。授权服务器没有注册端点 → `dcr_unsupported`(向导据此改为手填
|
|
61
|
+
* client)。注册失败只报 HTTP 状态,不带对方正文。
|
|
62
|
+
*/
|
|
63
|
+
export declare function registerClient(meta: McpAuthMetadata, redirectUri: string, http: OAuthHttpOptions,
|
|
64
|
+
/** 管理员指定的客户端认证方式(`tokenAuthMethod`);不给则按授权服务器声明选择。 */
|
|
65
|
+
preferred?: TokenAuthMethod): Promise<OAuthClientRecord>;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/** OAuth 往返的限时:工具超时与 30 秒取小。 */
|
|
2
|
+
export function oauthHttpOptions(cfg) {
|
|
3
|
+
return {
|
|
4
|
+
allowInsecureLoopback: cfg.tools.oauthAllowInsecureLoopback,
|
|
5
|
+
timeoutMs: Math.min(cfg.tools.timeoutMs, 30_000),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/** 发现 / 注册 / 安装时取 token 失败:原因码供向导分支(如 `dcr_unsupported` 改为手填 client),说明为英文。 */
|
|
9
|
+
export class OAuthSetupError extends Error {
|
|
10
|
+
reason;
|
|
11
|
+
constructor(reason, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.reason = reason;
|
|
14
|
+
this.name = "OAuthSetupError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const LOOPBACK = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
18
|
+
/** 元数据 / 令牌响应的大小上限。 */
|
|
19
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
20
|
+
/** 只接受 https(放行时本机 http 也可),不允许 URL 里带用户信息。 */
|
|
21
|
+
export function checkOAuthUrl(raw, http) {
|
|
22
|
+
let u;
|
|
23
|
+
try {
|
|
24
|
+
u = new URL(raw);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new OAuthSetupError("discovery_failed", "An OAuth URL is not a valid absolute URL");
|
|
28
|
+
}
|
|
29
|
+
const ok = u.protocol === "https:" ||
|
|
30
|
+
(u.protocol === "http:" && http.allowInsecureLoopback && LOOPBACK.has(u.hostname));
|
|
31
|
+
if (!ok) {
|
|
32
|
+
throw new OAuthSetupError("insecure_url", `OAuth URLs must use https (got ${u.protocol}//${u.host})`);
|
|
33
|
+
}
|
|
34
|
+
if (u.username !== "" || u.password !== "") {
|
|
35
|
+
throw new OAuthSetupError("insecure_url", "OAuth URLs must not contain user information");
|
|
36
|
+
}
|
|
37
|
+
return u;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* OAuth 往返:校验 URL、不跟随重定向(3xx 原样返回,调用方按失败处理)、限时、正文超过 1 MiB 即中止。
|
|
41
|
+
* 网络错误 / 超时原样抛出。
|
|
42
|
+
*/
|
|
43
|
+
export async function oauthFetch(url, init, http) {
|
|
44
|
+
checkOAuthUrl(url, http);
|
|
45
|
+
const res = await fetch(url, {
|
|
46
|
+
...init,
|
|
47
|
+
redirect: "manual",
|
|
48
|
+
signal: AbortSignal.timeout(http.timeoutMs),
|
|
49
|
+
});
|
|
50
|
+
const declared = Number(res.headers.get("content-length"));
|
|
51
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
52
|
+
await res.body?.cancel();
|
|
53
|
+
throw new Error("OAuth response is too large");
|
|
54
|
+
}
|
|
55
|
+
const chunks = [];
|
|
56
|
+
let size = 0;
|
|
57
|
+
if (res.body) {
|
|
58
|
+
const reader = res.body.getReader();
|
|
59
|
+
for (;;) {
|
|
60
|
+
const { done, value } = await reader.read();
|
|
61
|
+
if (done)
|
|
62
|
+
break;
|
|
63
|
+
size += value.byteLength;
|
|
64
|
+
if (size > MAX_BODY_BYTES) {
|
|
65
|
+
await reader.cancel().catch(() => { });
|
|
66
|
+
throw new Error("OAuth response is too large");
|
|
67
|
+
}
|
|
68
|
+
chunks.push(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { status: res.status, headers: res.headers, text: Buffer.concat(chunks).toString("utf8") };
|
|
72
|
+
}
|
|
73
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
74
|
+
const parseJson = (text) => {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : undefined;
|
|
83
|
+
/** `WWW-Authenticate: Bearer k="v", …` 中某个参数(带引号或不带)。 */
|
|
84
|
+
function challengeParam(header, name) {
|
|
85
|
+
if (!header)
|
|
86
|
+
return undefined;
|
|
87
|
+
const m = new RegExp(`(?:^|[\\s,])${name}\\s*=\\s*(?:"([^"]*)"|([^\\s,]+))`, "i").exec(header);
|
|
88
|
+
return m ? (m[1] ?? m[2]) : undefined;
|
|
89
|
+
}
|
|
90
|
+
const failed = (message) => new OAuthSetupError("discovery_failed", message);
|
|
91
|
+
/** 依次 GET 候选地址,返回第一个 200 且为 JSON 对象的正文;网络错误按「这个位置没有」处理。 */
|
|
92
|
+
async function firstJson(urls, http) {
|
|
93
|
+
for (const url of urls) {
|
|
94
|
+
let res;
|
|
95
|
+
try {
|
|
96
|
+
res = await oauthFetch(url, { headers: { accept: "application/json" } }, http);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
if (e instanceof OAuthSetupError)
|
|
100
|
+
throw e;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (res.status !== 200)
|
|
104
|
+
continue;
|
|
105
|
+
const body = parseJson(res.text);
|
|
106
|
+
if (isObj(body))
|
|
107
|
+
return body;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
/** 资源标识与端点:同源,且端点路径以资源路径为前缀(MCP 规范的资源匹配)。 */
|
|
112
|
+
function resourceMatches(resource, endpoint) {
|
|
113
|
+
let r;
|
|
114
|
+
try {
|
|
115
|
+
r = new URL(resource);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (r.origin !== endpoint.origin)
|
|
121
|
+
return false;
|
|
122
|
+
const rp = r.pathname.endsWith("/") ? r.pathname : `${r.pathname}/`;
|
|
123
|
+
const ep = endpoint.pathname.endsWith("/") ? endpoint.pathname : `${endpoint.pathname}/`;
|
|
124
|
+
return ep.startsWith(rp);
|
|
125
|
+
}
|
|
126
|
+
const trimSlash = (s) => s.replace(/\/+$/, "");
|
|
127
|
+
/**
|
|
128
|
+
* 对 MCP 端点做 OAuth 发现:先以不带凭据的 `initialize` 探测拿 401 的 `WWW-Authenticate`,再取受保护资源元数据,
|
|
129
|
+
* 再取授权服务器元数据(RFC 8414 §3.1 的路径插入式,OpenID 发现作回退)。校验资源标识与端点匹配、
|
|
130
|
+
* 元数据 `issuer` 与授权服务器标识一致、声明了 PKCE 方法时必须含 S256。
|
|
131
|
+
*/
|
|
132
|
+
export async function discoverMcp(endpointUrl, http) {
|
|
133
|
+
const endpoint = checkOAuthUrl(endpointUrl, http);
|
|
134
|
+
let challenge = null;
|
|
135
|
+
try {
|
|
136
|
+
const probe = await oauthFetch(endpoint.toString(), {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: {
|
|
139
|
+
"content-type": "application/json",
|
|
140
|
+
accept: "application/json, text/event-stream",
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify({
|
|
143
|
+
jsonrpc: "2.0",
|
|
144
|
+
id: 1,
|
|
145
|
+
method: "initialize",
|
|
146
|
+
params: {
|
|
147
|
+
protocolVersion: "2025-06-18",
|
|
148
|
+
capabilities: {},
|
|
149
|
+
clientInfo: { name: "capybara", version: "0.1.0" },
|
|
150
|
+
},
|
|
151
|
+
}),
|
|
152
|
+
}, http);
|
|
153
|
+
if (probe.status === 401)
|
|
154
|
+
challenge = probe.headers.get("www-authenticate");
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
if (e instanceof OAuthSetupError)
|
|
158
|
+
throw e;
|
|
159
|
+
throw failed("Could not reach the connector's server for OAuth discovery");
|
|
160
|
+
}
|
|
161
|
+
const metadataUrl = challengeParam(challenge, "resource_metadata");
|
|
162
|
+
const prmCandidates = [];
|
|
163
|
+
if (metadataUrl !== undefined) {
|
|
164
|
+
prmCandidates.push(checkOAuthUrl(metadataUrl, http).toString());
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const path = trimSlash(endpoint.pathname);
|
|
168
|
+
if (path !== "")
|
|
169
|
+
prmCandidates.push(`${endpoint.origin}/.well-known/oauth-protected-resource${path}`);
|
|
170
|
+
prmCandidates.push(`${endpoint.origin}/.well-known/oauth-protected-resource`);
|
|
171
|
+
}
|
|
172
|
+
const prm = await firstJson(prmCandidates, http);
|
|
173
|
+
if (!prm) {
|
|
174
|
+
throw failed("The connector's server did not publish OAuth protected resource metadata (RFC 9728)");
|
|
175
|
+
}
|
|
176
|
+
const resource = prm.resource;
|
|
177
|
+
const servers = strings(prm.authorization_servers);
|
|
178
|
+
if (typeof resource !== "string" || !servers || servers.length === 0) {
|
|
179
|
+
throw failed("The protected resource metadata lacks resource or authorization_servers");
|
|
180
|
+
}
|
|
181
|
+
if (!resourceMatches(resource, endpoint)) {
|
|
182
|
+
throw failed("The protected resource metadata names a different resource than the connector's URL");
|
|
183
|
+
}
|
|
184
|
+
const issuer = servers[0];
|
|
185
|
+
const as = checkOAuthUrl(issuer, http);
|
|
186
|
+
const asPath = trimSlash(as.pathname);
|
|
187
|
+
const asCandidates = asPath === ""
|
|
188
|
+
? [
|
|
189
|
+
`${as.origin}/.well-known/oauth-authorization-server`,
|
|
190
|
+
`${as.origin}/.well-known/openid-configuration`,
|
|
191
|
+
]
|
|
192
|
+
: [
|
|
193
|
+
`${as.origin}/.well-known/oauth-authorization-server${asPath}`,
|
|
194
|
+
`${as.origin}/.well-known/openid-configuration${asPath}`,
|
|
195
|
+
`${as.origin}${asPath}/.well-known/openid-configuration`,
|
|
196
|
+
];
|
|
197
|
+
const meta = await firstJson(asCandidates, http);
|
|
198
|
+
if (!meta)
|
|
199
|
+
throw failed("The authorization server did not publish its metadata (RFC 8414)");
|
|
200
|
+
// RFC 8414 §3.3:元数据的 issuer 必须与授权服务器标识完全相同
|
|
201
|
+
if (meta.issuer !== issuer) {
|
|
202
|
+
throw failed("The authorization server metadata issuer does not match the server");
|
|
203
|
+
}
|
|
204
|
+
const authz = meta.authorization_endpoint;
|
|
205
|
+
const token = meta.token_endpoint;
|
|
206
|
+
if (typeof authz !== "string" || typeof token !== "string") {
|
|
207
|
+
throw failed("The authorization server metadata lacks authorization or token endpoints");
|
|
208
|
+
}
|
|
209
|
+
checkOAuthUrl(authz, http);
|
|
210
|
+
checkOAuthUrl(token, http);
|
|
211
|
+
const pkce = strings(meta.code_challenge_methods_supported);
|
|
212
|
+
// MCP 授权规范:授权服务器必须声明支持 S256,未声明即拒绝
|
|
213
|
+
if (!pkce?.includes("S256")) {
|
|
214
|
+
throw failed("The authorization server does not advertise PKCE S256 support (code_challenge_methods_supported)");
|
|
215
|
+
}
|
|
216
|
+
const reg = meta.registration_endpoint;
|
|
217
|
+
if (reg !== undefined && typeof reg !== "string") {
|
|
218
|
+
throw failed("The authorization server metadata has an invalid registration_endpoint");
|
|
219
|
+
}
|
|
220
|
+
const scopeParam = challengeParam(challenge, "scope");
|
|
221
|
+
const scopes = scopeParam !== undefined
|
|
222
|
+
? scopeParam.split(/\s+/).filter(Boolean)
|
|
223
|
+
: strings(prm.scopes_supported);
|
|
224
|
+
const authMethods = strings(meta.token_endpoint_auth_methods_supported);
|
|
225
|
+
return {
|
|
226
|
+
resource,
|
|
227
|
+
issuer,
|
|
228
|
+
authorizationEndpoint: authz,
|
|
229
|
+
tokenEndpoint: token,
|
|
230
|
+
...(reg !== undefined && { registrationEndpoint: reg }),
|
|
231
|
+
...(scopes !== undefined && scopes.length > 0 && { scopes }),
|
|
232
|
+
...(authMethods !== undefined && { authMethods }),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** 按授权服务器声明的方法选客户端认证方式(未声明时 RFC 8414 默认 client_secret_basic)。 */
|
|
236
|
+
export function pickAuthMethod(supported, hasSecret) {
|
|
237
|
+
if (!hasSecret)
|
|
238
|
+
return "none";
|
|
239
|
+
if (!supported || supported.includes("client_secret_basic"))
|
|
240
|
+
return "client_secret_basic";
|
|
241
|
+
if (supported.includes("client_secret_post"))
|
|
242
|
+
return "client_secret_post";
|
|
243
|
+
return supported.includes("none") ? "none" : "client_secret_basic";
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 动态客户端注册(RFC 7591),每个连接器注册一次。授权服务器没有注册端点 → `dcr_unsupported`(向导据此改为手填
|
|
247
|
+
* client)。注册失败只报 HTTP 状态,不带对方正文。
|
|
248
|
+
*/
|
|
249
|
+
export async function registerClient(meta, redirectUri, http,
|
|
250
|
+
/** 管理员指定的客户端认证方式(`tokenAuthMethod`);不给则按授权服务器声明选择。 */
|
|
251
|
+
preferred) {
|
|
252
|
+
if (!meta.registrationEndpoint) {
|
|
253
|
+
throw new OAuthSetupError("dcr_unsupported", "The authorization server does not support dynamic client registration; provide a client ID (and secret) registered with it");
|
|
254
|
+
}
|
|
255
|
+
// 优先机密客户端(client_secret_basic / post);授权服务器两者都不支持时注册为公开客户端(只靠 PKCE)
|
|
256
|
+
const wanted = preferred ?? pickAuthMethod(meta.authMethods, true);
|
|
257
|
+
let res;
|
|
258
|
+
try {
|
|
259
|
+
res = await oauthFetch(meta.registrationEndpoint, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
262
|
+
body: JSON.stringify({
|
|
263
|
+
client_name: "Capybara",
|
|
264
|
+
redirect_uris: [redirectUri],
|
|
265
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
266
|
+
response_types: ["code"],
|
|
267
|
+
token_endpoint_auth_method: wanted,
|
|
268
|
+
...(meta.scopes && { scope: meta.scopes.join(" ") }),
|
|
269
|
+
}),
|
|
270
|
+
}, http);
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
if (e instanceof OAuthSetupError)
|
|
274
|
+
throw e;
|
|
275
|
+
throw new OAuthSetupError("dcr_failed", "Could not reach the client registration endpoint");
|
|
276
|
+
}
|
|
277
|
+
const body = parseJson(res.text);
|
|
278
|
+
if ((res.status !== 201 && res.status !== 200) ||
|
|
279
|
+
!isObj(body) ||
|
|
280
|
+
typeof body.client_id !== "string") {
|
|
281
|
+
throw new OAuthSetupError("dcr_failed", `Dynamic client registration failed (HTTP ${res.status})`);
|
|
282
|
+
}
|
|
283
|
+
const secret = typeof body.client_secret === "string" && body.client_secret !== ""
|
|
284
|
+
? body.client_secret
|
|
285
|
+
: undefined;
|
|
286
|
+
const method = body.token_endpoint_auth_method;
|
|
287
|
+
const authMethod = method === "client_secret_basic" || method === "client_secret_post" || method === "none"
|
|
288
|
+
? method
|
|
289
|
+
: pickAuthMethod(meta.authMethods, secret !== undefined);
|
|
290
|
+
const regToken = body.registration_access_token;
|
|
291
|
+
const regUri = body.registration_client_uri;
|
|
292
|
+
return {
|
|
293
|
+
clientId: body.client_id,
|
|
294
|
+
...(secret !== undefined && { clientSecret: secret }),
|
|
295
|
+
authMethod,
|
|
296
|
+
authorizationEndpoint: meta.authorizationEndpoint,
|
|
297
|
+
tokenEndpoint: meta.tokenEndpoint,
|
|
298
|
+
resource: meta.resource,
|
|
299
|
+
issuer: meta.issuer,
|
|
300
|
+
...(meta.scopes && { scopes: meta.scopes }),
|
|
301
|
+
registration: {
|
|
302
|
+
...(typeof regToken === "string" && { accessToken: regToken }),
|
|
303
|
+
...(typeof regUri === "string" && { clientUri: regUri }),
|
|
304
|
+
...(typeof body.client_id_issued_at === "number" && { issuedAt: body.client_id_issued_at }),
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Logger } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { Redis } from "ioredis";
|
|
3
|
+
import type { KernelConfig } from "../../config/schema.js";
|
|
4
|
+
import type { KernelDb } from "../../db/kernel-schema.js";
|
|
5
|
+
import { ApiError } from "../../errors.js";
|
|
6
|
+
import type { Credentials } from "../credentials.js";
|
|
7
|
+
/** 授权回调的路径(`http.publicUrl` + 它 = 注册给授权服务器的 redirect_uri)。 */
|
|
8
|
+
export declare const OAUTH_CALLBACK_PATH = "/api/v1/oauth/callback";
|
|
9
|
+
/** state 的有效期(秒)。 */
|
|
10
|
+
export declare const OAUTH_STATE_TTL_SECONDS = 600;
|
|
11
|
+
/** 浏览器绑定 Cookie:回调必须来自发起连接的同一个浏览器。 */
|
|
12
|
+
export declare const OAUTH_BINDING_COOKIE = "cap_oauth";
|
|
13
|
+
/** 回调失败、或连接时没给回跳页面时回到的网页(next-intl 会补语言前缀)。 */
|
|
14
|
+
export declare const DEFAULT_RETURN_PATH = "/settings/connections";
|
|
15
|
+
/** OAuth 连接器需要 `http.publicUrl`(回调地址);未配置 → 这条校验错误。 */
|
|
16
|
+
export declare const publicUrlRequired: () => ApiError;
|
|
17
|
+
export declare function callbackUrl(publicUrl: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* 回跳页面只接受站内相对路径(防开放重定向):以单个 `/` 开头,不含 `\` 与控制字符,解析后仍在同一个源。
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSafeReturnPath(p: string): boolean;
|
|
22
|
+
/** 一个有效的浏览器绑定值(复用请求里已有的,否则新生成)。 */
|
|
23
|
+
export declare function browserBinding(existing: string | undefined): string;
|
|
24
|
+
/** 回调失败的原因码(放进回跳 URL 的 `oauth_error`;不回显授权服务器的任何文本)。 */
|
|
25
|
+
export type OAuthCallbackError = "state_invalid" | "session_mismatch" | "access_denied" | "provider_error" | "token_exchange_failed" | "issuer_mismatch" | "connector_unavailable";
|
|
26
|
+
/** 首次连接后刷新工具清单最多等这么久再 302(超时则在后台继续,结果照常写入)。 */
|
|
27
|
+
export declare const FIRST_CONNECT_REFRESH_WAIT_MS = 15000;
|
|
28
|
+
export interface OAuthFlowDeps {
|
|
29
|
+
db: KernelDb;
|
|
30
|
+
redis: Redis;
|
|
31
|
+
credentials: Credentials;
|
|
32
|
+
config: () => KernelConfig;
|
|
33
|
+
log: Logger;
|
|
34
|
+
/** 某成员首次成功连接、而连接器还没有工具快照时调用(A16:用该成员的 token 刷新工具清单)。 */
|
|
35
|
+
onFirstConnect: (orgId: string, connectorId: string, accountId: string) => Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 授权码 + PKCE(S256)连接流程。state 是 32 字节随机串,存 Redis `capk:oauth-state:<state>` 10 分钟、单次有效
|
|
39
|
+
* (GETDEL),内含组织、连接器、发起人、PKCE verifier、redirect_uri、回跳页面与浏览器绑定值的哈希。
|
|
40
|
+
*/
|
|
41
|
+
export declare class OAuthFlow {
|
|
42
|
+
private readonly deps;
|
|
43
|
+
constructor(deps: OAuthFlowDeps);
|
|
44
|
+
private app;
|
|
45
|
+
/** 生成授权 URL。`binding` 为本浏览器的绑定值(由 controller 写入 Cookie)。 */
|
|
46
|
+
start(orgId: string, connectorId: string, accountId: string, returnTo: string | undefined, binding: string): Promise<{
|
|
47
|
+
authorizationUrl: string;
|
|
48
|
+
}>;
|
|
49
|
+
/** 回跳地址:`http.publicUrl` + 站内路径 + 结果参数(未配置 publicUrl 时为相对地址)。 */
|
|
50
|
+
private redirect;
|
|
51
|
+
private fail;
|
|
52
|
+
/**
|
|
53
|
+
* 处理授权服务器的回跳,返回 302 的目标。state 先取后删(无论后续成败都不能再用);浏览器绑定不符 →
|
|
54
|
+
* `session_mismatch`(把回跳链接转给别人不能把对方的授权连到发起人账号上);授权服务器报错只映射为原因码。
|
|
55
|
+
* 成功则在组织行锁内复核发起人仍是成员(`connectors.use`),写个人凭据与审计;连接器还没有工具快照时用这个 token 刷新。
|
|
56
|
+
*/
|
|
57
|
+
callback(query: {
|
|
58
|
+
code?: string | undefined;
|
|
59
|
+
state?: string | undefined;
|
|
60
|
+
error?: string | undefined;
|
|
61
|
+
iss?: string | undefined;
|
|
62
|
+
}, binding: string | undefined): Promise<string>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { ApiError } from "../../errors.js";
|
|
3
|
+
import { audit } from "../../identity/audit.js";
|
|
4
|
+
import { lockOrgAndAuthorize } from "../../orgs/org-lock.js";
|
|
5
|
+
import { oauthHttpOptions } from "./discovery.js";
|
|
6
|
+
import { tokenRequest } from "./tokens.js";
|
|
7
|
+
/** 授权回调的路径(`http.publicUrl` + 它 = 注册给授权服务器的 redirect_uri)。 */
|
|
8
|
+
export const OAUTH_CALLBACK_PATH = "/api/v1/oauth/callback";
|
|
9
|
+
/** state 的有效期(秒)。 */
|
|
10
|
+
export const OAUTH_STATE_TTL_SECONDS = 600;
|
|
11
|
+
/** 浏览器绑定 Cookie:回调必须来自发起连接的同一个浏览器。 */
|
|
12
|
+
export const OAUTH_BINDING_COOKIE = "cap_oauth";
|
|
13
|
+
/** 回调失败、或连接时没给回跳页面时回到的网页(next-intl 会补语言前缀)。 */
|
|
14
|
+
export const DEFAULT_RETURN_PATH = "/settings/connections";
|
|
15
|
+
const stateKey = (state) => `capk:oauth-state:${state}`;
|
|
16
|
+
const STATE_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
17
|
+
const BINDING_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
18
|
+
const b64url = (n = 32) => randomBytes(n).toString("base64url");
|
|
19
|
+
const sha256 = (s) => createHash("sha256").update(s).digest("base64url");
|
|
20
|
+
/** OAuth 连接器需要 `http.publicUrl`(回调地址);未配置 → 这条校验错误。 */
|
|
21
|
+
export const publicUrlRequired = () => new ApiError("validation_failed", "Request validation failed", [
|
|
22
|
+
{
|
|
23
|
+
path: "auth",
|
|
24
|
+
message: "OAuth connectors require http.publicUrl to be configured (it is used to build the OAuth callback URL)",
|
|
25
|
+
},
|
|
26
|
+
]);
|
|
27
|
+
export function callbackUrl(publicUrl) {
|
|
28
|
+
return `${publicUrl.replace(/\/+$/, "")}${OAUTH_CALLBACK_PATH}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 回跳页面只接受站内相对路径(防开放重定向):以单个 `/` 开头,不含 `\` 与控制字符,解析后仍在同一个源。
|
|
32
|
+
*/
|
|
33
|
+
export function isSafeReturnPath(p) {
|
|
34
|
+
if (p.length > 500 || !p.startsWith("/") || p.startsWith("//"))
|
|
35
|
+
return false;
|
|
36
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: 正是要拒绝控制字符
|
|
37
|
+
if (/[\\\u0000-\u001f\u007f]/.test(p))
|
|
38
|
+
return false;
|
|
39
|
+
const base = "https://return.invalid";
|
|
40
|
+
try {
|
|
41
|
+
return new URL(p, base).origin === base;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** 一个有效的浏览器绑定值(复用请求里已有的,否则新生成)。 */
|
|
48
|
+
export function browserBinding(existing) {
|
|
49
|
+
return existing !== undefined && BINDING_RE.test(existing) ? existing : b64url();
|
|
50
|
+
}
|
|
51
|
+
/** 首次连接后刷新工具清单最多等这么久再 302(超时则在后台继续,结果照常写入)。 */
|
|
52
|
+
export const FIRST_CONNECT_REFRESH_WAIT_MS = 15_000;
|
|
53
|
+
/**
|
|
54
|
+
* 授权码 + PKCE(S256)连接流程。state 是 32 字节随机串,存 Redis `capk:oauth-state:<state>` 10 分钟、单次有效
|
|
55
|
+
* (GETDEL),内含组织、连接器、发起人、PKCE verifier、redirect_uri、回跳页面与浏览器绑定值的哈希。
|
|
56
|
+
*/
|
|
57
|
+
export class OAuthFlow {
|
|
58
|
+
deps;
|
|
59
|
+
constructor(deps) {
|
|
60
|
+
this.deps = deps;
|
|
61
|
+
}
|
|
62
|
+
async app(orgId, connectorId) {
|
|
63
|
+
return this.deps.db
|
|
64
|
+
.selectFrom("connectors")
|
|
65
|
+
.select(["id", "auth", "status"])
|
|
66
|
+
.where("id", "=", connectorId)
|
|
67
|
+
.where("org_id", "=", orgId)
|
|
68
|
+
.where("deleted_at", "is", null)
|
|
69
|
+
.executeTakeFirst();
|
|
70
|
+
}
|
|
71
|
+
/** 生成授权 URL。`binding` 为本浏览器的绑定值(由 controller 写入 Cookie)。 */
|
|
72
|
+
async start(orgId, connectorId, accountId, returnTo, binding) {
|
|
73
|
+
const publicUrl = this.deps.config().http.publicUrl;
|
|
74
|
+
if (!publicUrl)
|
|
75
|
+
throw publicUrlRequired();
|
|
76
|
+
const app = await this.app(orgId, connectorId);
|
|
77
|
+
if (!app)
|
|
78
|
+
throw new ApiError("connector.not_found", "Connector not found");
|
|
79
|
+
if (app.auth.type !== "oauth_code") {
|
|
80
|
+
throw new ApiError("validation_failed", "Request validation failed", [
|
|
81
|
+
{
|
|
82
|
+
path: "auth",
|
|
83
|
+
message: "Only OAuth authorization-code connectors are connected this way",
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
if (app.status !== "active") {
|
|
88
|
+
throw new ApiError("validation_failed", "Request validation failed", [
|
|
89
|
+
{ path: "status", message: "The connector is disabled; ask an administrator to enable it" },
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
const client = await this.deps.credentials.oauthClient(orgId, connectorId);
|
|
93
|
+
if (!client?.authorizationEndpoint) {
|
|
94
|
+
throw new ApiError("connector.connect_failed", "The connector has no registered OAuth client; ask an administrator to reinstall it");
|
|
95
|
+
}
|
|
96
|
+
const state = b64url();
|
|
97
|
+
const verifier = b64url();
|
|
98
|
+
const redirectUri = callbackUrl(publicUrl);
|
|
99
|
+
const rec = {
|
|
100
|
+
orgId,
|
|
101
|
+
connectorId,
|
|
102
|
+
accountId,
|
|
103
|
+
verifier,
|
|
104
|
+
redirectUri,
|
|
105
|
+
returnTo: returnTo ?? DEFAULT_RETURN_PATH,
|
|
106
|
+
binding: sha256(binding),
|
|
107
|
+
...(client.issuer !== undefined && { issuer: client.issuer }),
|
|
108
|
+
};
|
|
109
|
+
await this.deps.redis.set(stateKey(state), JSON.stringify(rec), "EX", OAUTH_STATE_TTL_SECONDS);
|
|
110
|
+
const url = new URL(client.authorizationEndpoint);
|
|
111
|
+
url.searchParams.set("response_type", "code");
|
|
112
|
+
url.searchParams.set("client_id", client.clientId);
|
|
113
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
114
|
+
url.searchParams.set("state", state);
|
|
115
|
+
url.searchParams.set("code_challenge", sha256(verifier));
|
|
116
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
117
|
+
if (client.scopes && client.scopes.length > 0)
|
|
118
|
+
url.searchParams.set("scope", client.scopes.join(" "));
|
|
119
|
+
if (client.resource)
|
|
120
|
+
url.searchParams.set("resource", client.resource);
|
|
121
|
+
return { authorizationUrl: url.toString() };
|
|
122
|
+
}
|
|
123
|
+
/** 回跳地址:`http.publicUrl` + 站内路径 + 结果参数(未配置 publicUrl 时为相对地址)。 */
|
|
124
|
+
redirect(path, param) {
|
|
125
|
+
const base = (this.deps.config().http.publicUrl ?? "").replace(/\/+$/, "");
|
|
126
|
+
const u = new URL(path, "https://return.invalid");
|
|
127
|
+
u.searchParams.set(param[0], param[1]);
|
|
128
|
+
return `${base}${u.pathname}${u.search}${u.hash}`;
|
|
129
|
+
}
|
|
130
|
+
fail(code, path = DEFAULT_RETURN_PATH) {
|
|
131
|
+
return this.redirect(path, ["oauth_error", code]);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 处理授权服务器的回跳,返回 302 的目标。state 先取后删(无论后续成败都不能再用);浏览器绑定不符 →
|
|
135
|
+
* `session_mismatch`(把回跳链接转给别人不能把对方的授权连到发起人账号上);授权服务器报错只映射为原因码。
|
|
136
|
+
* 成功则在组织行锁内复核发起人仍是成员(`connectors.use`),写个人凭据与审计;连接器还没有工具快照时用这个 token 刷新。
|
|
137
|
+
*/
|
|
138
|
+
async callback(query, binding) {
|
|
139
|
+
const state = query.state;
|
|
140
|
+
if (state === undefined || !STATE_RE.test(state))
|
|
141
|
+
return this.fail("state_invalid");
|
|
142
|
+
const raw = await this.deps.redis.getdel(stateKey(state));
|
|
143
|
+
if (raw === null)
|
|
144
|
+
return this.fail("state_invalid");
|
|
145
|
+
const rec = JSON.parse(raw);
|
|
146
|
+
const back = rec.returnTo;
|
|
147
|
+
if (binding === undefined || sha256(binding) !== rec.binding) {
|
|
148
|
+
return this.fail("session_mismatch", back);
|
|
149
|
+
}
|
|
150
|
+
// RFC 9207:回跳带 iss 时必须是发起时的授权服务器(防混淆攻击)
|
|
151
|
+
if (query.iss !== undefined && rec.issuer !== undefined && query.iss !== rec.issuer) {
|
|
152
|
+
return this.fail("issuer_mismatch", back);
|
|
153
|
+
}
|
|
154
|
+
if (query.error !== undefined) {
|
|
155
|
+
return this.fail(query.error === "access_denied" ? "access_denied" : "provider_error", back);
|
|
156
|
+
}
|
|
157
|
+
if (!query.code)
|
|
158
|
+
return this.fail("provider_error", back);
|
|
159
|
+
const { orgId, connectorId, accountId } = rec;
|
|
160
|
+
const app = await this.app(orgId, connectorId);
|
|
161
|
+
const client = app ? await this.deps.credentials.oauthClient(orgId, connectorId) : null;
|
|
162
|
+
if (!app || !client || app.auth.type !== "oauth_code") {
|
|
163
|
+
return this.fail("connector_unavailable", back);
|
|
164
|
+
}
|
|
165
|
+
let tokens;
|
|
166
|
+
try {
|
|
167
|
+
tokens = await tokenRequest(client, {
|
|
168
|
+
grant_type: "authorization_code",
|
|
169
|
+
code: query.code,
|
|
170
|
+
redirect_uri: rec.redirectUri,
|
|
171
|
+
code_verifier: rec.verifier,
|
|
172
|
+
resource: client.resource,
|
|
173
|
+
}, oauthHttpOptions(this.deps.config()));
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
this.deps.log.warn("oauth code exchange failed", { orgId, connectorId, err: e });
|
|
177
|
+
return this.fail("token_exchange_failed", back);
|
|
178
|
+
}
|
|
179
|
+
let first;
|
|
180
|
+
try {
|
|
181
|
+
first = await this.deps.db.transaction().execute(async (t) => {
|
|
182
|
+
await lockOrgAndAuthorize(t, orgId, accountId, "connectors.use");
|
|
183
|
+
const still = await t
|
|
184
|
+
.selectFrom("connectors")
|
|
185
|
+
.select("id")
|
|
186
|
+
.where("id", "=", connectorId)
|
|
187
|
+
.where("deleted_at", "is", null)
|
|
188
|
+
.forUpdate()
|
|
189
|
+
.executeTakeFirst();
|
|
190
|
+
if (!still)
|
|
191
|
+
throw new ApiError("connector.not_found", "Connector not found");
|
|
192
|
+
await this.deps.credentials.setOAuthTokens(orgId, connectorId, { kind: "user", accountId }, tokens, t);
|
|
193
|
+
await audit(t, {
|
|
194
|
+
actorAccountId: accountId,
|
|
195
|
+
orgId,
|
|
196
|
+
action: "connector.credential.set",
|
|
197
|
+
target: connectorId,
|
|
198
|
+
detail: { type: "oauth_code", ownership: "personal" },
|
|
199
|
+
});
|
|
200
|
+
const tool = await t
|
|
201
|
+
.selectFrom("connector_tools")
|
|
202
|
+
.select("name")
|
|
203
|
+
.where("connector_id", "=", connectorId)
|
|
204
|
+
.limit(1)
|
|
205
|
+
.executeTakeFirst();
|
|
206
|
+
return tool === undefined;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
if (e instanceof ApiError)
|
|
211
|
+
return this.fail("connector_unavailable", back);
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
if (first) {
|
|
215
|
+
// 首次连接(A16):刷新失败不影响连接本身,凭据已存好;管理员之后可手动刷新
|
|
216
|
+
// 最多等 FIRST_CONNECT_REFRESH_WAIT_MS 再回跳(本请求自己的计时器,结束即清除);超时的刷新在后台继续
|
|
217
|
+
const refresh = this.deps
|
|
218
|
+
.onFirstConnect(orgId, connectorId, accountId)
|
|
219
|
+
.catch((err) => {
|
|
220
|
+
this.deps.log.warn("oauth first-connect tool refresh failed", {
|
|
221
|
+
orgId,
|
|
222
|
+
connectorId,
|
|
223
|
+
err,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
let timer;
|
|
227
|
+
await Promise.race([
|
|
228
|
+
refresh,
|
|
229
|
+
new Promise((resolve) => {
|
|
230
|
+
timer = setTimeout(resolve, FIRST_CONNECT_REFRESH_WAIT_MS);
|
|
231
|
+
}),
|
|
232
|
+
]);
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
}
|
|
235
|
+
return this.redirect(back, ["connected", connectorId]);
|
|
236
|
+
}
|
|
237
|
+
}
|