@zackbart/connecta 0.28.1 → 0.30.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 +855 -5
- package/README.md +125 -116
- package/bin/connecta.mjs +121 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/argument-redaction.js +230 -0
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +288 -0
- package/dist/authorization-handoff.js +111 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +409 -225
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +633 -0
- package/dist/config.js +508 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +87 -38
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +128 -0
- package/dist/downstream-input.js +325 -0
- package/dist/errors.d.ts +132 -31
- package/dist/errors.js +175 -87
- package/dist/execute.js +798 -318
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +30 -13
- package/dist/executors/quickjs-child.js +15 -9
- package/dist/executors/quickjs-protocol.js +5 -6
- package/dist/executors/quickjs-runtime.js +152 -59
- package/dist/executors/quickjs.js +49 -39
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +238 -0
- package/dist/guest-types.d.ts +218 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +262 -79
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +61 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +419 -272
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +194 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-result.js +5 -0
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +60 -19
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/_shared/request-token.js +83 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/infisical/index.d.ts +7 -0
- package/dist/providers/infisical/index.js +713 -0
- package/dist/providers/infisical/skill.generated.js +8 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +110 -171
- package/dist/registry.js +588 -927
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +103 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +365 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +93 -0
- package/dist/sent-secrets.js +1013 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +416 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +181 -0
- package/dist/storage/keys.js +443 -0
- package/dist/storage/memory.js +16 -4
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +543 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +336 -56
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +146 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +435 -0
- package/documentation/README.md +63 -0
- package/documentation/architecture.md +696 -231
- package/documentation/auth.md +1069 -546
- package/documentation/code-mode.md +662 -356
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +159 -0
- package/documentation/meta-tools.md +570 -216
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +97 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-migration-0.29.md +65 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +293 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +126 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +140 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
package/bin/connecta.mjs
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
lstat,
|
|
7
|
-
mkdtemp,
|
|
8
|
-
readFile,
|
|
9
|
-
rename,
|
|
10
|
-
rm,
|
|
11
|
-
symlink,
|
|
12
|
-
writeFile,
|
|
13
|
-
} from "node:fs/promises";
|
|
3
|
+
import { copyFile, cp, lstat, mkdtemp, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { CONNECTA_VERSION } from "./version.mjs";
|
|
5
|
+
import { META_TOOL_NAMES } from "./meta-tool-names.mjs";
|
|
14
6
|
import { basename, dirname, join, resolve } from "node:path";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
8
|
|
|
17
9
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
18
10
|
const [command, ...args] = process.argv.slice(2);
|
|
@@ -27,7 +19,8 @@ function shellCd(path) {
|
|
|
27
19
|
function usage() {
|
|
28
20
|
console.log(`Usage:
|
|
29
21
|
connecta init [directory]
|
|
30
|
-
|
|
22
|
+
connecta migrate-state <state.json> <connecta.sqlite>
|
|
23
|
+
CONNECTA_TOKEN=<bearer> connecta doctor [--config] [--url http://localhost:8787]
|
|
31
24
|
CF_ACCESS_CLIENT_ID=<id> CF_ACCESS_CLIENT_SECRET=<secret> connecta doctor --url https://worker.example`);
|
|
32
25
|
}
|
|
33
26
|
|
|
@@ -44,18 +37,13 @@ async function init() {
|
|
|
44
37
|
await lstat(target);
|
|
45
38
|
throw new Error(`Refusing to overwrite existing path: ${target}`);
|
|
46
39
|
} catch (error) {
|
|
47
|
-
const code =
|
|
48
|
-
error && typeof error === "object" && "code" in error
|
|
49
|
-
? String(error.code)
|
|
50
|
-
: "";
|
|
40
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
51
41
|
if (code !== "ENOENT") throw error;
|
|
52
42
|
}
|
|
53
43
|
|
|
54
44
|
// Build beside the destination, then rename once complete. A failed copy or
|
|
55
45
|
// rewrite leaves no partial destination that blocks a clean retry.
|
|
56
|
-
let stage = await mkdtemp(
|
|
57
|
-
join(parent, `.${basename(target)}.connecta-init-`),
|
|
58
|
-
);
|
|
46
|
+
let stage = await mkdtemp(join(parent, `.${basename(target)}.connecta-init-`));
|
|
59
47
|
try {
|
|
60
48
|
await cp(join(packageRoot, "templates", "node"), stage, {
|
|
61
49
|
recursive: true,
|
|
@@ -63,10 +51,7 @@ async function init() {
|
|
|
63
51
|
|
|
64
52
|
// npm excludes .gitignore files and symlinks from packed dependencies.
|
|
65
53
|
// Restore both conventions explicitly in the generated project.
|
|
66
|
-
await writeFile(
|
|
67
|
-
join(stage, ".gitignore"),
|
|
68
|
-
".connecta-state.json\n.connecta-activity.jsonl\n.env\nnode_modules/\n",
|
|
69
|
-
);
|
|
54
|
+
await writeFile(join(stage, ".gitignore"), ".connecta.sqlite*\n.env\nnode_modules/\n");
|
|
70
55
|
await rm(join(stage, "CLAUDE.md"), { force: true });
|
|
71
56
|
try {
|
|
72
57
|
await symlink("AGENTS.md", join(stage, "CLAUDE.md"));
|
|
@@ -76,18 +61,11 @@ async function init() {
|
|
|
76
61
|
await copyFile(join(stage, "AGENTS.md"), join(stage, "CLAUDE.md"));
|
|
77
62
|
}
|
|
78
63
|
|
|
79
|
-
const rootPackage = JSON.parse(
|
|
80
|
-
await readFile(join(packageRoot, "package.json"), "utf8"),
|
|
81
|
-
);
|
|
64
|
+
const rootPackage = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
|
|
82
65
|
const deploymentPath = join(stage, "package.json");
|
|
83
|
-
const deploymentPackage = JSON.parse(
|
|
84
|
-
await readFile(deploymentPath, "utf8"),
|
|
85
|
-
);
|
|
66
|
+
const deploymentPackage = JSON.parse(await readFile(deploymentPath, "utf8"));
|
|
86
67
|
deploymentPackage.dependencies["@zackbart/connecta"] = rootPackage.version;
|
|
87
|
-
await writeFile(
|
|
88
|
-
deploymentPath,
|
|
89
|
-
JSON.stringify(deploymentPackage, null, 2) + "\n",
|
|
90
|
-
);
|
|
68
|
+
await writeFile(deploymentPath, JSON.stringify(deploymentPackage, null, 2) + "\n");
|
|
91
69
|
|
|
92
70
|
await rename(stage, target);
|
|
93
71
|
stage = "";
|
|
@@ -99,7 +77,10 @@ async function init() {
|
|
|
99
77
|
console.log("Next:");
|
|
100
78
|
console.log(` ${shellCd(target)}`);
|
|
101
79
|
console.log(" npm install");
|
|
102
|
-
console.log(
|
|
80
|
+
console.log(' npm run --silent provision-token -- "local-machine"');
|
|
81
|
+
console.log(" # Save the returned cta_ token privately for your MCP client and doctor.");
|
|
82
|
+
console.log(" npm start");
|
|
83
|
+
console.log(" # README.md covers database paths and Docker provisioning.");
|
|
103
84
|
}
|
|
104
85
|
|
|
105
86
|
function option(name, fallback) {
|
|
@@ -135,7 +116,7 @@ async function doctorFetch(url, init = {}) {
|
|
|
135
116
|
const response = await fetch(url, {
|
|
136
117
|
...init,
|
|
137
118
|
redirect: "manual",
|
|
138
|
-
signal: AbortSignal.timeout(DOCTOR_TIMEOUT_MS),
|
|
119
|
+
signal: AbortSignal.any([AbortSignal.timeout(DOCTOR_TIMEOUT_MS), ...(init.signal ? [init.signal] : [])]),
|
|
139
120
|
});
|
|
140
121
|
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
141
122
|
await response.body?.cancel().catch(() => {});
|
|
@@ -145,10 +126,7 @@ async function doctorFetch(url, init = {}) {
|
|
|
145
126
|
}
|
|
146
127
|
return response;
|
|
147
128
|
} catch (error) {
|
|
148
|
-
if (
|
|
149
|
-
error instanceof Error &&
|
|
150
|
-
(error.name === "TimeoutError" || error.name === "AbortError")
|
|
151
|
-
) {
|
|
129
|
+
if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
|
|
152
130
|
throw new Error(`Timed out after ${DOCTOR_TIMEOUT_MS}ms contacting ${url}`);
|
|
153
131
|
}
|
|
154
132
|
throw error;
|
|
@@ -156,9 +134,9 @@ async function doctorFetch(url, init = {}) {
|
|
|
156
134
|
}
|
|
157
135
|
|
|
158
136
|
async function doctor() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
137
|
+
for (let index = 0; index < args.length; index++) {
|
|
138
|
+
if (args[index] === "--config") continue;
|
|
139
|
+
if (args[index] !== "--url" || !args[++index] || args[index].startsWith("--")) {
|
|
162
140
|
usage();
|
|
163
141
|
process.exitCode = 1;
|
|
164
142
|
return;
|
|
@@ -173,22 +151,15 @@ async function doctor() {
|
|
|
173
151
|
throw new Error("Doctor URL must use http or https.");
|
|
174
152
|
}
|
|
175
153
|
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
!loopbackHosts.has(parsedUrl.hostname)
|
|
179
|
-
) {
|
|
180
|
-
throw new Error(
|
|
181
|
-
"Refusing to send authentication credentials over remote plaintext HTTP. Use HTTPS.",
|
|
182
|
-
);
|
|
154
|
+
if (parsedUrl.protocol === "http:" && !loopbackHosts.has(parsedUrl.hostname)) {
|
|
155
|
+
throw new Error("Refusing to send authentication credentials over remote plaintext HTTP. Use HTTPS.");
|
|
183
156
|
}
|
|
184
157
|
const baseUrl = requestedUrl.replace(/\/+$/, "");
|
|
185
158
|
const token = process.env.CONNECTA_TOKEN;
|
|
186
159
|
const accessClientId = process.env.CF_ACCESS_CLIENT_ID;
|
|
187
160
|
const accessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET;
|
|
188
161
|
if (Boolean(accessClientId) !== Boolean(accessClientSecret)) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
"Set both CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET.",
|
|
191
|
-
);
|
|
162
|
+
throw new Error("Set both CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET.");
|
|
192
163
|
}
|
|
193
164
|
if (!token && !accessClientId) {
|
|
194
165
|
throw new Error(
|
|
@@ -205,9 +176,28 @@ async function doctor() {
|
|
|
205
176
|
: {}),
|
|
206
177
|
};
|
|
207
178
|
|
|
208
|
-
|
|
209
|
-
await doctorFetch(`${baseUrl}/
|
|
210
|
-
|
|
179
|
+
if (args.includes("--config")) {
|
|
180
|
+
const response = await doctorFetch(`${baseUrl}/ui/api/config`, { headers: authHeaders });
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
await response.body?.cancel().catch(() => {});
|
|
183
|
+
throw new Error(`HTTP ${response.status}: config snapshot unavailable.`);
|
|
184
|
+
}
|
|
185
|
+
let contract;
|
|
186
|
+
try {
|
|
187
|
+
contract = await response.json();
|
|
188
|
+
} catch {
|
|
189
|
+
throw new Error("Deployment returned an invalid config contract.");
|
|
190
|
+
}
|
|
191
|
+
if (contract?.schemaVersion !== 1 || contract.config?.schemaVersion !== 1) {
|
|
192
|
+
throw new Error("Deployment returned an unsupported config contract.");
|
|
193
|
+
}
|
|
194
|
+
// The server builds this with describeConfig's allowlist; do not print
|
|
195
|
+
// the overlay, identity, health deploymentInfo, or a raw error body.
|
|
196
|
+
console.log(JSON.stringify(contract.config, null, 2));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const health = await jsonResponse(await doctorFetch(`${baseUrl}/health`, { headers: authHeaders }));
|
|
211
201
|
if (health.status !== "ok") {
|
|
212
202
|
throw new Error(`Unexpected health status: ${String(health.status)}`);
|
|
213
203
|
}
|
|
@@ -225,107 +215,99 @@ async function doctor() {
|
|
|
225
215
|
.slice(0, 40)
|
|
226
216
|
.trim()
|
|
227
217
|
: "";
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
Accept: "application/json, text/event-stream",
|
|
237
|
-
},
|
|
238
|
-
body: JSON.stringify({
|
|
239
|
-
jsonrpc: "2.0",
|
|
240
|
-
id: ++requestId,
|
|
241
|
-
method,
|
|
242
|
-
params,
|
|
243
|
-
}),
|
|
244
|
-
}),
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
const listed = await mcp("tools/list", {});
|
|
248
|
-
if (listed.error) {
|
|
249
|
-
throw new Error(`tools/list failed: ${JSON.stringify(listed.error)}`);
|
|
250
|
-
}
|
|
251
|
-
const actual = listed.result?.tools
|
|
252
|
-
?.map((tool) => tool.name)
|
|
253
|
-
.sort();
|
|
254
|
-
const expected = [
|
|
255
|
-
"authorize_connector",
|
|
256
|
-
"call_destructive_tool",
|
|
257
|
-
"call_tool",
|
|
258
|
-
"execute_code",
|
|
259
|
-
"get_result",
|
|
260
|
-
"search_tools",
|
|
261
|
-
"skills",
|
|
262
|
-
];
|
|
263
|
-
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
264
|
-
throw new Error(
|
|
265
|
-
`Unexpected MCP surface. Expected ${expected.join(", ")}; received ` +
|
|
266
|
-
`${Array.isArray(actual) ? actual.join(", ") : "no tool list"}.`,
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const executed = await mcp("tools/call", {
|
|
271
|
-
name: "execute_code",
|
|
272
|
-
arguments: { code: "async () => 42" },
|
|
218
|
+
const { Client, StreamableHTTPClientTransport } = await import("@modelcontextprotocol/client");
|
|
219
|
+
const client = new Client(
|
|
220
|
+
{ name: "connecta-doctor", version: CONNECTA_VERSION },
|
|
221
|
+
{ versionNegotiation: { mode: "auto", probe: { timeoutMs: DOCTOR_TIMEOUT_MS } } },
|
|
222
|
+
);
|
|
223
|
+
const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {
|
|
224
|
+
requestInit: { headers: authHeaders },
|
|
225
|
+
fetch: doctorFetch,
|
|
273
226
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
`
|
|
284
|
-
|
|
285
|
-
|
|
227
|
+
let negotiatedVersion;
|
|
228
|
+
try {
|
|
229
|
+
await client.connect(transport);
|
|
230
|
+
negotiatedVersion = client.getNegotiatedProtocolVersion();
|
|
231
|
+
const listed = await client.listTools({}, { timeout: DOCTOR_TIMEOUT_MS });
|
|
232
|
+
const actual = listed.tools.map((tool) => tool.name).sort();
|
|
233
|
+
const expected = [...META_TOOL_NAMES].sort();
|
|
234
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Unexpected MCP surface. Expected ${expected.join(", ")}; received ` +
|
|
237
|
+
`${Array.isArray(actual) ? actual.join(", ") : "no tool list"}.`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
286
240
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
report.unclassifiedTools ||
|
|
295
|
-
report.unservedTools ||
|
|
296
|
-
report.annotationConflicts ||
|
|
297
|
-
report.schemaChanges,
|
|
298
|
-
);
|
|
299
|
-
for (const [connectorId, report] of drifted) {
|
|
300
|
-
console.warn(
|
|
301
|
-
`[connecta] catalog drift on "${connectorId}" (observed ` +
|
|
302
|
-
`${report.observedAt}): ${report.unclassifiedTools} unclassified, ` +
|
|
303
|
-
`${report.unservedTools} no longer served, ` +
|
|
304
|
-
`${report.annotationConflicts} annotation conflict(s), ` +
|
|
305
|
-
`${report.schemaChanges} schema change(s).`,
|
|
241
|
+
const executed = await client.callTool(
|
|
242
|
+
{
|
|
243
|
+
name: "execute_code",
|
|
244
|
+
arguments: { code: "async () => 42" },
|
|
245
|
+
},
|
|
246
|
+
undefined,
|
|
247
|
+
{ timeout: DOCTOR_TIMEOUT_MS },
|
|
306
248
|
);
|
|
249
|
+
const executionResult = executed.structuredContent ?? JSON.parse(executed.content?.[0]?.text ?? "null");
|
|
250
|
+
if (executed.isError || executionResult?.result !== 42) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`${executorName ? `${executorName} execution` : "execute_code"} check ` + `failed: ${JSON.stringify(executed)}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
} finally {
|
|
256
|
+
await client.close();
|
|
307
257
|
}
|
|
308
258
|
|
|
309
259
|
console.log(
|
|
310
260
|
`Connecta doctor passed: ${health.connectors} connector(s), ` +
|
|
311
261
|
`${executorName ? `${executorName} executed` : "code executed"}, ` +
|
|
312
|
-
|
|
313
|
-
(drifted.length > 0
|
|
314
|
-
? `, catalog drift on ${drifted.length} connector(s).`
|
|
315
|
-
: "."),
|
|
262
|
+
`prescribed ${META_TOOL_NAMES.length}-tool surface, MCP ${negotiatedVersion}, package ${CONNECTA_VERSION}.`,
|
|
316
263
|
);
|
|
317
264
|
}
|
|
318
265
|
|
|
266
|
+
/**
|
|
267
|
+
* One-shot copy of a 0.28 `fileStorage` JSON state file into the SQLite
|
|
268
|
+
* database 0.29 stores everything in. Stop the old deployment first; the file
|
|
269
|
+
* is read, never changed.
|
|
270
|
+
*/
|
|
271
|
+
async function migrateState() {
|
|
272
|
+
if (args.length !== 2 || args.some((arg) => arg.startsWith("--"))) {
|
|
273
|
+
usage();
|
|
274
|
+
process.exitCode = 1;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const [statePath, databasePath] = args.map((arg) => resolve(process.cwd(), arg));
|
|
278
|
+
const { importStateFile, openSqlite } = await import(pathToFileURL(join(packageRoot, "dist", "sqlite.js")).href);
|
|
279
|
+
let database;
|
|
280
|
+
try {
|
|
281
|
+
database = openSqlite(databasePath);
|
|
282
|
+
} catch {
|
|
283
|
+
throw new Error("State migration failed: could not open the SQLite database");
|
|
284
|
+
}
|
|
285
|
+
let closeFailed = false;
|
|
286
|
+
try {
|
|
287
|
+
const result = importStateFile(database, statePath);
|
|
288
|
+
console.log(
|
|
289
|
+
`Imported ${result.imported} entries from ${statePath} into ${databasePath}` +
|
|
290
|
+
` (${result.kept} already present, ${result.expired} expired).`,
|
|
291
|
+
);
|
|
292
|
+
} finally {
|
|
293
|
+
try {
|
|
294
|
+
database.close();
|
|
295
|
+
} catch {
|
|
296
|
+
closeFailed = true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (closeFailed) throw new Error("State migration failed: could not close the SQLite database");
|
|
300
|
+
}
|
|
301
|
+
|
|
319
302
|
try {
|
|
320
303
|
if (command === "init") await init();
|
|
321
304
|
else if (command === "doctor") await doctor();
|
|
305
|
+
else if (command === "migrate-state") await migrateState();
|
|
322
306
|
else {
|
|
323
307
|
usage();
|
|
324
308
|
process.exitCode = command === "--help" || command === "-h" ? 0 : 1;
|
|
325
309
|
}
|
|
326
310
|
} catch (error) {
|
|
327
|
-
console.error(
|
|
328
|
-
`[connecta] ${error instanceof Error ? error.message : String(error)}`,
|
|
329
|
-
);
|
|
311
|
+
console.error(`[connecta] ${error instanceof Error ? error.message : String(error)}`);
|
|
330
312
|
process.exitCode = 1;
|
|
331
313
|
}
|
package/bin/version.mjs
ADDED
package/dist/access-tokens.js
CHANGED
|
@@ -1,35 +1,27 @@
|
|
|
1
|
+
import { authorizationCredential, isMachineCredential } from "./inbound-credential.js";
|
|
2
|
+
import { assertKnownOptions, keys, optionsOf } from "./config-schema.js";
|
|
1
3
|
import { routeAccessTokens } from "./routes/access-tokens.js";
|
|
2
4
|
import { validIdentityReference } from "./identity.js";
|
|
5
|
+
import { accessTokenKeys } from "./storage/keys.js";
|
|
3
6
|
const TOKEN_PREFIX = "cta_";
|
|
4
7
|
const TOKEN_BYTES = 32;
|
|
5
8
|
const TOKEN_VALUE_RE = /^cta_[A-Za-z0-9_-]{43}$/;
|
|
6
|
-
const RECORD_PREFIX = "access-token:v1:record:";
|
|
7
|
-
const LOOKUP_PREFIX = "access-token:v1:lookup:";
|
|
8
9
|
const MAX_NAME_CHARACTERS = 80;
|
|
9
10
|
const DEFAULT_MAX_ACTIVE = 100;
|
|
10
11
|
const MAX_CONFIGURED_ACTIVE = 1_000;
|
|
11
|
-
const ACTIVE_KEY = "access-token:v1:active";
|
|
12
12
|
const ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
|
13
13
|
const encoder = new TextEncoder();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function lookupKey(hash) {
|
|
18
|
-
return `${LOOKUP_PREFIX}${hash}`;
|
|
19
|
-
}
|
|
14
|
+
const recordKey = accessTokenKeys.record;
|
|
15
|
+
const lookupKey = accessTokenKeys.lookup;
|
|
16
|
+
const ACTIVE_KEY = accessTokenKeys.active;
|
|
20
17
|
function bytesToBase64Url(bytes) {
|
|
21
18
|
let binary = "";
|
|
22
19
|
for (const byte of bytes)
|
|
23
20
|
binary += String.fromCharCode(byte);
|
|
24
|
-
return btoa(binary)
|
|
25
|
-
.replaceAll("+", "-")
|
|
26
|
-
.replaceAll("/", "_")
|
|
27
|
-
.replace(/=+$/u, "");
|
|
21
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
28
22
|
}
|
|
29
23
|
function bytesToHex(bytes) {
|
|
30
|
-
return [...bytes]
|
|
31
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
32
|
-
.join("");
|
|
24
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
33
25
|
}
|
|
34
26
|
async function hashToken(token) {
|
|
35
27
|
return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token))));
|
|
@@ -62,12 +54,10 @@ function parseRecord(raw) {
|
|
|
62
54
|
typeof value.createdAt !== "string" ||
|
|
63
55
|
!Number.isFinite(Date.parse(value.createdAt)) ||
|
|
64
56
|
typeof value.createdBy !== "string" ||
|
|
65
|
-
(value.principal !== undefined &&
|
|
66
|
-
!validIdentityReference(value.principal)) ||
|
|
57
|
+
(value.principal !== undefined && !validIdentityReference(value.principal)) ||
|
|
67
58
|
(value.revokedAt !== undefined &&
|
|
68
59
|
(typeof value.revokedAt !== "string" || !value.revokedAt || !Number.isFinite(Date.parse(value.revokedAt)))) ||
|
|
69
|
-
(value.revokedBy !== undefined &&
|
|
70
|
-
typeof value.revokedBy !== "string")) {
|
|
60
|
+
(value.revokedBy !== undefined && typeof value.revokedBy !== "string")) {
|
|
71
61
|
throw new Error("invalid token record");
|
|
72
62
|
}
|
|
73
63
|
return value;
|
|
@@ -119,18 +109,20 @@ export class AccessTokenManager {
|
|
|
119
109
|
maxActive;
|
|
120
110
|
constructor(storage, options = {}) {
|
|
121
111
|
this.storage = storage;
|
|
122
|
-
|
|
123
|
-
|
|
112
|
+
// Issuance claims capacity by compare-and-set and listing enumerates
|
|
113
|
+
// records; refuse a store without either here, not at the first create.
|
|
114
|
+
if (typeof storage?.list !== "function" || typeof storage.compareAndSet !== "function") {
|
|
115
|
+
throw new Error("accessTokens requires storage that implements list and compareAndSet " +
|
|
116
|
+
"(d1Storage, sqliteStorage, or memoryStorage)");
|
|
124
117
|
}
|
|
125
118
|
const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
|
|
126
|
-
if (!Number.isInteger(maxActive) ||
|
|
127
|
-
maxActive < 1 ||
|
|
128
|
-
maxActive > MAX_CONFIGURED_ACTIVE) {
|
|
119
|
+
if (!Number.isInteger(maxActive) || maxActive < 1 || maxActive > MAX_CONFIGURED_ACTIVE) {
|
|
129
120
|
throw new Error(`accessTokens.maxActive must be a whole number from 1 to ${MAX_CONFIGURED_ACTIVE}`);
|
|
130
121
|
}
|
|
131
122
|
this.maxActive = maxActive;
|
|
132
123
|
this.auth = {
|
|
133
124
|
kind: "access_token",
|
|
125
|
+
recognizesCredential: isMachineCredential,
|
|
134
126
|
activityActorNamespace: "connecta:access-tokens:v1",
|
|
135
127
|
activityActorLabel: async (id) => {
|
|
136
128
|
try {
|
|
@@ -153,7 +145,7 @@ export class AccessTokenManager {
|
|
|
153
145
|
return record;
|
|
154
146
|
}
|
|
155
147
|
async list() {
|
|
156
|
-
const keys = await this.storage.list(
|
|
148
|
+
const keys = await this.storage.list(accessTokenKeys.recordPrefix);
|
|
157
149
|
const records = await Promise.all(keys.map(async (key) => {
|
|
158
150
|
const raw = await this.storage.get(key);
|
|
159
151
|
return raw ? parseRecord(raw) : null;
|
|
@@ -165,9 +157,6 @@ export class AccessTokenManager {
|
|
|
165
157
|
}
|
|
166
158
|
async create(name, createdBy) {
|
|
167
159
|
const normalizedName = normalizeName(name);
|
|
168
|
-
if (!this.storage.compareAndSet) {
|
|
169
|
-
throw new Error("Creating access tokens requires atomic compareAndSet storage");
|
|
170
|
-
}
|
|
171
160
|
if (typeof createdBy !== "string" && !validIdentityReference(createdBy)) {
|
|
172
161
|
throw new Error("Invalid token owner");
|
|
173
162
|
}
|
|
@@ -184,12 +173,8 @@ export class AccessTokenManager {
|
|
|
184
173
|
tokenHash: hash,
|
|
185
174
|
tokenPrefix: token.slice(0, 12),
|
|
186
175
|
createdAt: new Date().toISOString(),
|
|
187
|
-
createdBy: typeof createdBy === "string"
|
|
188
|
-
|
|
189
|
-
: `${createdBy.namespace}:${createdBy.id}`,
|
|
190
|
-
...(typeof createdBy === "string"
|
|
191
|
-
? {}
|
|
192
|
-
: { principal: { ...createdBy } }),
|
|
176
|
+
createdBy: typeof createdBy === "string" ? createdBy : `${createdBy.namespace}:${createdBy.id}`,
|
|
177
|
+
...(typeof createdBy === "string" ? {} : { principal: { ...createdBy } }),
|
|
193
178
|
};
|
|
194
179
|
try {
|
|
195
180
|
await this.updateActive(record.id, true);
|
|
@@ -211,7 +196,7 @@ export class AccessTokenManager {
|
|
|
211
196
|
}
|
|
212
197
|
async rename(id, name) {
|
|
213
198
|
const normalized = normalizeName(name);
|
|
214
|
-
const record = await this.updateRecord(id, current => ({ ...current, name: normalized }));
|
|
199
|
+
const record = await this.updateRecord(id, (current) => ({ ...current, name: normalized }));
|
|
215
200
|
return record ? metadata(record) : null;
|
|
216
201
|
}
|
|
217
202
|
async revoke(id, revokedBy) {
|
|
@@ -221,11 +206,14 @@ export class AccessTokenManager {
|
|
|
221
206
|
// Remove admission first. A metadata failure cannot leave a successful
|
|
222
207
|
// revocation's lookup alive. Retry deletion even if already marked revoked.
|
|
223
208
|
await this.storage.delete(lookupKey(record.tokenHash));
|
|
224
|
-
const updated = await this.updateRecord(id, current => current.revokedAt
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
209
|
+
const updated = await this.updateRecord(id, (current) => current.revokedAt
|
|
210
|
+
? current
|
|
211
|
+
: {
|
|
212
|
+
...current,
|
|
213
|
+
revokedAt: new Date().toISOString(),
|
|
214
|
+
revokedBy,
|
|
215
|
+
});
|
|
216
|
+
await this.updateActive(id, false);
|
|
229
217
|
return updated ? metadata(updated) : null;
|
|
230
218
|
}
|
|
231
219
|
async updateRecord(id, update) {
|
|
@@ -239,45 +227,36 @@ export class AccessTokenManager {
|
|
|
239
227
|
if (record.id !== id)
|
|
240
228
|
throw new Error("Stored access token id mismatch");
|
|
241
229
|
const next = update(record);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
// Legacy adapters cannot create, so no late lookup writer exists.
|
|
250
|
-
// Revocation's deleted lookup still refuses a stale metadata write.
|
|
251
|
-
await this.storage.set(recordKey(id), JSON.stringify(next));
|
|
252
|
-
}
|
|
230
|
+
// A rename racing revocation must retain revokedAt, including when
|
|
231
|
+
// an in-flight create has not yet published its lookup.
|
|
232
|
+
if (!(await this.storage.compareAndSet(recordKey(id), raw, JSON.stringify(next))))
|
|
233
|
+
continue;
|
|
253
234
|
return next;
|
|
254
235
|
}
|
|
255
236
|
throw new Error("Access token metadata is busy; retry the operation");
|
|
256
237
|
}
|
|
257
238
|
async updateActive(id, adding) {
|
|
258
|
-
const cas = this.storage.compareAndSet;
|
|
259
|
-
if (!cas)
|
|
260
|
-
throw new Error("Creating access tokens requires atomic compareAndSet storage");
|
|
261
239
|
for (let attempt = 0; attempt < 32; attempt++) {
|
|
262
240
|
const raw = await this.storage.get(ACTIVE_KEY);
|
|
263
241
|
const ids = raw === null
|
|
264
|
-
? (await this.list()).filter(token => !token.revokedAt).map(token => token.id)
|
|
242
|
+
? (await this.list()).filter((token) => !token.revokedAt).map((token) => token.id)
|
|
265
243
|
: JSON.parse(raw);
|
|
266
|
-
if (!Array.isArray(ids) ||
|
|
244
|
+
if (!Array.isArray(ids) ||
|
|
245
|
+
!ids.every((value) => typeof value === "string" && ID_RE.test(value)) ||
|
|
246
|
+
new Set(ids).size !== ids.length) {
|
|
267
247
|
throw new Error("Stored access token capacity is invalid");
|
|
268
248
|
}
|
|
269
249
|
if (adding && ids.length >= this.maxActive)
|
|
270
250
|
throw new Error(`This deployment already has the maximum of ${this.maxActive} active access tokens`);
|
|
271
|
-
const next = adding ? [...ids, id] : ids.filter(value => value !== id);
|
|
272
|
-
if (await
|
|
251
|
+
const next = adding ? [...ids, id] : ids.filter((value) => value !== id);
|
|
252
|
+
if (await this.storage.compareAndSet(ACTIVE_KEY, raw, JSON.stringify(next)))
|
|
273
253
|
return;
|
|
274
254
|
}
|
|
275
255
|
throw new Error("Access token capacity is busy; retry the operation");
|
|
276
256
|
}
|
|
277
257
|
async authorize(request) {
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
const token = match?.[1];
|
|
258
|
+
const credential = authorizationCredential(request);
|
|
259
|
+
const token = credential.kind === "bearer" ? credential.token : undefined;
|
|
281
260
|
if (!token || !TOKEN_VALUE_RE.test(token))
|
|
282
261
|
return unauthorized();
|
|
283
262
|
const hash = await hashToken(token);
|
|
@@ -298,8 +277,19 @@ export class AccessTokenManager {
|
|
|
298
277
|
};
|
|
299
278
|
}
|
|
300
279
|
}
|
|
280
|
+
/** The closed options accessTokens() accepts; see `assertKnownOptions`. */
|
|
281
|
+
const ACCESS_TOKENS_OPTIONS = optionsOf()(keys("maxActive"));
|
|
301
282
|
/** Opt in using the same storage namespace that held the v0.23 records. */
|
|
302
283
|
export function accessTokens(storage, options = {}) {
|
|
284
|
+
options = assertKnownOptions(options, "accessTokens()", ACCESS_TOKENS_OPTIONS);
|
|
303
285
|
const manager = new AccessTokenManager(storage, options);
|
|
304
|
-
|
|
286
|
+
const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
|
|
287
|
+
const optionSources = Object.freeze({
|
|
288
|
+
maxActive: options.maxActive === undefined ? "default" : "config",
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
auth: manager.auth,
|
|
292
|
+
handle: (context) => routeAccessTokens(context, manager),
|
|
293
|
+
describe: () => ({ maxActive, optionSources }),
|
|
294
|
+
};
|
|
305
295
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Weak ownership keeps the grouping context within the request's lifetime.
|
|
2
|
+
const requests = new WeakMap();
|
|
3
|
+
export function bindActivityRequest(scope, context) {
|
|
4
|
+
requests.set(scope, context);
|
|
5
|
+
}
|
|
6
|
+
export function activityRequest(scope) {
|
|
7
|
+
return scope ? requests.get(scope) : undefined;
|
|
8
|
+
}
|