@zackbart/connecta 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +961 -5
- package/README.md +62 -39
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1632 -1509
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +526 -0
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +220 -0
- package/dist/connectors/api-connector.js +214 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +13 -99
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +108 -25
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1780 -351
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +25 -257
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-partition.js +17 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/basecamp/index.d.ts +43 -0
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/breeze/index.d.ts +34 -0
- package/dist/providers/breeze/index.js +1446 -0
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/ccb/index.d.ts +103 -0
- package/dist/providers/ccb/index.js +1725 -0
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/overflow/index.d.ts +60 -0
- package/dist/providers/overflow/index.js +1535 -0
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/planning-center/index.d.ts +59 -0
- package/dist/providers/planning-center/index.js +2199 -0
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/tithely/index.d.ts +49 -0
- package/dist/providers/tithely/index.js +1003 -0
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +136 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +665 -230
- package/documentation/auth.md +962 -458
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +125 -35
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { selfHostedClientDocument } from "../auth/downstream-client-metadata.js";
|
|
2
|
+
/** Public OAuth client metadata, without auth, storage, discovery, or request-host input. */
|
|
3
|
+
export function routeOAuthClientMetadata(context) {
|
|
4
|
+
if (!context.path.startsWith("/oauth/client-metadata/"))
|
|
5
|
+
return null;
|
|
6
|
+
const id = context.path.slice("/oauth/client-metadata/".length);
|
|
7
|
+
const connector = context.opts.registry.getConnector(id);
|
|
8
|
+
const document = connector &&
|
|
9
|
+
selfHostedClientDocument(connector, context.opts.config.publicUrl, context.opts.config.serverInfo.name);
|
|
10
|
+
if (!document)
|
|
11
|
+
return new Response(null, { status: 404 });
|
|
12
|
+
if (context.request.method !== "GET")
|
|
13
|
+
return new Response(null, { status: 405, headers: { Allow: "GET" } });
|
|
14
|
+
return Response.json(document, { headers: { "Cache-Control": "public, max-age=300" } });
|
|
15
|
+
}
|
|
@@ -1,28 +1,17 @@
|
|
|
1
|
+
import { OAuthRevocationError } from "../auth/downstream-revocation.js";
|
|
1
2
|
import { Effect, Result } from "effect";
|
|
2
|
-
import { drainOAuthStartResets } from "../auth/oauth-start-reset.js";
|
|
3
3
|
import { closeScope } from "../runtime/connector-scope.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { mayManageConnector, isSameOrigin,
|
|
8
|
-
/**
|
|
9
|
-
const OAUTH_START_TIMEOUT_MS = 30_000;
|
|
10
|
-
/**
|
|
11
|
-
* How a POST starts authorization, from its `mode` query parameter:
|
|
12
|
-
* `restart` (the default) resets the connector to a fresh epoch — wiping the
|
|
13
|
-
* grant, the client registration, and discovery — before starting over;
|
|
14
|
-
* `continue` hands back a recent pending authorization URL when one exists
|
|
15
|
-
* and otherwise starts a flow in the current epoch, keeping the stored
|
|
16
|
-
* registration. `null` is a malformed request.
|
|
17
|
-
*/
|
|
4
|
+
import { oauthConnectUrl, oauthConnectUnavailable } from "../oauth-handoff.js";
|
|
5
|
+
import { authorizedPerson, refuse, serveOperator, visibleRegistry, Answer } from "./operator.js";
|
|
6
|
+
import { failureRecord, logFailure } from "../operator-record.js";
|
|
7
|
+
import { mayManageConnector, isSameOrigin, privateJson } from "./shared.js";
|
|
8
|
+
/** A UI action signs its requested mode; the verified /connect visit applies it. */
|
|
18
9
|
function startMode(url) {
|
|
19
10
|
const modes = url.searchParams.getAll("mode");
|
|
20
11
|
if (modes.length === 0)
|
|
21
12
|
return "restart";
|
|
22
13
|
const [mode] = modes;
|
|
23
|
-
return modes.length === 1 && (mode === "continue" || mode === "restart")
|
|
24
|
-
? mode
|
|
25
|
-
: null;
|
|
14
|
+
return modes.length === 1 && (mode === "continue" || mode === "restart") ? mode : null;
|
|
26
15
|
}
|
|
27
16
|
function oauthManagementRequest(context, connectorId) {
|
|
28
17
|
const { request, baseUrl, defer, opts } = context;
|
|
@@ -52,101 +41,50 @@ function oauthManagementRequest(context, connectorId) {
|
|
|
52
41
|
if (mode === null) {
|
|
53
42
|
return yield* refuse('mode must be "continue" or "restart"', 400);
|
|
54
43
|
}
|
|
44
|
+
if (!disconnecting) {
|
|
45
|
+
const unavailable = oauthConnectUnavailable(opts);
|
|
46
|
+
if (unavailable)
|
|
47
|
+
return yield* refuse(unavailable, 403);
|
|
48
|
+
const authorizationUrl = yield* Effect.tryPromise({
|
|
49
|
+
try: () => oauthConnectUrl(opts, baseUrl, connectorId, authz.principalKey, mode === "restart"),
|
|
50
|
+
catch: () => new Answer(privateJson({ error: "OAuth connection link could not be created" }, { status: 502 })),
|
|
51
|
+
});
|
|
52
|
+
// This POST is already an explicit connect action; the browser visit
|
|
53
|
+
// verifies the signed initiator and applies it without another UI hand-off.
|
|
54
|
+
const target = new URL(authorizationUrl);
|
|
55
|
+
if (opts.config.ui)
|
|
56
|
+
target.searchParams.set("start", "1");
|
|
57
|
+
return privateJson({ state: "auth_required", authorizationUrl: target.href });
|
|
58
|
+
}
|
|
55
59
|
let ctx;
|
|
56
60
|
// The connector scope this request opened ends with it, however it ends.
|
|
57
|
-
yield* Effect.addFinalizer(() => ctx ? closeScope(connector, ctx, defer) : Effect.void);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
await disconnectAuth(ctx);
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
const started = await startAuth(ctx, { force: mode === "restart" });
|
|
67
|
-
if (signal.aborted)
|
|
68
|
-
throw signal.reason;
|
|
69
|
-
if (started.authorizationUrl) {
|
|
70
|
-
await registry.bindOAuthHandoff(connectorId, started.authorizationUrl);
|
|
71
|
-
}
|
|
72
|
-
return started;
|
|
73
|
-
},
|
|
61
|
+
yield* Effect.addFinalizer(() => (ctx ? closeScope(connector, ctx, defer) : Effect.void));
|
|
62
|
+
// A disconnect commits through invalidation even if its hook rejects or
|
|
63
|
+
// the browser leaves. It has no start deadline or request signal.
|
|
64
|
+
ctx = registry.contextFor(connectorId, baseUrl);
|
|
65
|
+
const operation = yield* Effect.result(Effect.tryPromise({
|
|
66
|
+
try: () => disconnectAuth(ctx),
|
|
74
67
|
catch: (error) => error,
|
|
75
|
-
}), {
|
|
76
|
-
// A disconnect deliberately ignores both browser cancellation and
|
|
77
|
-
// this start deadline so its reset reaches invalidation.
|
|
78
|
-
...(disconnecting ? {} : { timeoutMs: OAUTH_START_TIMEOUT_MS }),
|
|
79
|
-
...(disconnecting ? {} : { signal: request.signal }),
|
|
80
|
-
timeoutError,
|
|
81
68
|
}));
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const started = Result.isSuccess(operation) ? operation.success : undefined;
|
|
96
|
-
const reused = mode === "continue" && started?.authorizationReused === true;
|
|
97
|
-
const unchanged = mode === "continue" && (reused || started?.state === "ok");
|
|
98
|
-
const invalidated = unchanged
|
|
99
|
-
? Result.succeed(undefined)
|
|
100
|
-
: yield* Effect.result(Effect.tryPromise({
|
|
101
|
-
try: () => registry.invalidateStored(connectorId),
|
|
102
|
-
catch: (error) => error,
|
|
103
|
-
}));
|
|
104
|
-
// Every failure below answers in the route's own fixed words. A hook's
|
|
105
|
-
// rejection and a status message can quote a token endpoint's error body
|
|
106
|
-
// or a provider's refusal, either of which can quote the secret it was
|
|
107
|
-
// sent, so that text goes to the deployment's log and nowhere else.
|
|
108
|
-
const failed = (detail, fixed, status) => {
|
|
109
|
-
opts.logger.warn(`[connecta] connector "${connectorId}" OAuth ${disconnecting ? "disconnect" : mode} failed: ${detail}`);
|
|
110
|
-
return refuse(fixed, status);
|
|
111
|
-
};
|
|
112
|
-
const couldNot = disconnecting
|
|
113
|
-
? "OAuth disconnect failed"
|
|
114
|
-
: "OAuth authorization could not start";
|
|
115
|
-
// A Result, not a caught value, decides whether it failed: a hook that
|
|
116
|
-
// rejects with no reason at all still failed.
|
|
117
|
-
if (Result.isFailure(operation)) {
|
|
118
|
-
if (operation.failure === timeoutError) {
|
|
119
|
-
return yield* failed(`start exceeded ${OAUTH_START_TIMEOUT_MS} ms`, "OAuth authorization start timed out", 504);
|
|
69
|
+
const invalidated = yield* Effect.result(Effect.tryPromise({
|
|
70
|
+
try: () => registry.invalidateStored(connectorId),
|
|
71
|
+
catch: (error) => error,
|
|
72
|
+
}));
|
|
73
|
+
if (Result.isFailure(operation) || Result.isFailure(invalidated)) {
|
|
74
|
+
const error = Result.isFailure(operation)
|
|
75
|
+
? operation.failure
|
|
76
|
+
: Result.isFailure(invalidated)
|
|
77
|
+
? invalidated.failure
|
|
78
|
+
: undefined;
|
|
79
|
+
logFailure(opts.config.logger, "OAuth disconnect failed", failureRecord({ connector: connectorId }, error));
|
|
80
|
+
if (error instanceof OAuthRevocationError && !Result.isFailure(invalidated)) {
|
|
81
|
+
return privateJson({ state: "auth_required", code: "oauth_revocation_failed" });
|
|
120
82
|
}
|
|
121
|
-
return yield*
|
|
122
|
-
}
|
|
123
|
-
if (Result.isFailure(invalidated)) {
|
|
124
|
-
return yield* failed(`stored-state invalidation: ${msg(invalidated.failure)}`, couldNot, 400);
|
|
125
|
-
}
|
|
126
|
-
const result = operation.success;
|
|
127
|
-
if (!result) {
|
|
128
|
-
return new Response(null, {
|
|
129
|
-
status: 204,
|
|
130
|
-
headers: {
|
|
131
|
-
"Cache-Control": "no-store",
|
|
132
|
-
"Referrer-Policy": "no-referrer",
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
const authorizationUrl = isSafeHttpUrl(result.authorizationUrl)
|
|
137
|
-
? result.authorizationUrl
|
|
138
|
-
: undefined;
|
|
139
|
-
if (result.state === "error") {
|
|
140
|
-
return yield* failed(result.message || "the connector reported an error state", couldNot, 502);
|
|
141
|
-
}
|
|
142
|
-
if (result.state === "auth_required" && !authorizationUrl) {
|
|
143
|
-
return yield* failed(result.message || "consent is required but no safe authorization URL was returned", "OAuth authorization requires consent but no safe URL is available", 502);
|
|
83
|
+
return yield* refuse("OAuth disconnect failed", 400);
|
|
144
84
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
state: result.state,
|
|
149
|
-
...(authorizationUrl ? { authorizationUrl, reused } : {}),
|
|
85
|
+
return new Response(null, {
|
|
86
|
+
status: 204,
|
|
87
|
+
headers: { "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" },
|
|
150
88
|
});
|
|
151
89
|
}).pipe(Effect.scoped);
|
|
152
90
|
}
|
package/dist/routes/oauth.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
+
import { htmlSecurityHeaders } from "../html-security.js";
|
|
3
|
+
import { authorizationCredential } from "../inbound-credential.js";
|
|
2
4
|
import { closeConnectorScope } from "../connector-scope.js";
|
|
3
|
-
import {
|
|
5
|
+
import { OAuthCallbackClaimedError, oauthStateDigest } from "../auth/downstream-oauth.js";
|
|
6
|
+
import { failureRecord, logFailure } from "../operator-record.js";
|
|
7
|
+
import { oauthFlowKeys } from "../storage/keys.js";
|
|
4
8
|
import { escapeHtml, renderPage, resolveBranding, STATUS_ICONS } from "../branding.js";
|
|
5
|
-
import { oauthCallbackOutcome, providerErrorReason
|
|
6
|
-
import { authorizeUiIdentity, mayManageConnector, validateAuthPermissions, loggableValue,
|
|
9
|
+
import { oauthCallbackOutcome, providerErrorReason } from "../oauth-callback-outcome.js";
|
|
10
|
+
import { authorizeUiIdentity, withSessionCookies, mayManageConnector, validateAuthPermissions, loggableValue, } from "./shared.js";
|
|
7
11
|
const TONE_MARKS = {
|
|
8
12
|
ok: { className: " ok", icon: STATUS_ICONS.ok, label: "Connected" },
|
|
9
13
|
declined: { className: "", icon: STATUS_ICONS.declined, label: "Not connected" },
|
|
@@ -24,10 +28,10 @@ const TONE_MARKS = {
|
|
|
24
28
|
* under a quiet disclosure rather than in the message.
|
|
25
29
|
*/
|
|
26
30
|
function html(reason, opts, connector) {
|
|
27
|
-
const brand = resolveBranding(opts.branding);
|
|
31
|
+
const brand = resolveBranding(opts.config.ui?.branding);
|
|
28
32
|
const outcome = oauthCallbackOutcome(reason, connector, brand.productName);
|
|
29
33
|
const mark = TONE_MARKS[outcome.tone];
|
|
30
|
-
const uiMounted = Boolean(opts.ui);
|
|
34
|
+
const uiMounted = Boolean(opts.config.ui);
|
|
31
35
|
const home = uiMounted
|
|
32
36
|
? `<div class="status-actions"><a class="btn${outcome.tone === "ok" ? "" : " primary"}" href="/">Return to ${escapeHtml(brand.productName)}</a></div>`
|
|
33
37
|
: "";
|
|
@@ -50,23 +54,23 @@ function html(reason, opts, connector) {
|
|
|
50
54
|
${details}
|
|
51
55
|
</section>
|
|
52
56
|
</main>`;
|
|
53
|
-
return new Response(renderPage(opts.branding, {
|
|
57
|
+
return new Response(renderPage(opts.config.ui?.branding, {
|
|
54
58
|
title: `${outcome.heading} — ${brand.pageTitle}`,
|
|
55
59
|
uiMounted,
|
|
56
60
|
body,
|
|
57
|
-
}), { status: outcome.status, headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
61
|
+
}), { status: outcome.status, headers: htmlSecurityHeaders({ "Content-Type": "text/html; charset=utf-8" }) });
|
|
58
62
|
}
|
|
59
63
|
/**
|
|
60
64
|
* Pay the storage read a real downstream-OAuth refusal pays, on the refusal
|
|
61
65
|
* paths that would otherwise pay nothing.
|
|
62
66
|
*
|
|
63
67
|
* Identical bodies do not hide a connector id if the clock still sorts them.
|
|
64
|
-
* `KvOAuthProvider.verifyState` reads
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* `KvOAuthProvider.verifyState` reads the consent the state names before it
|
|
69
|
+
* can reject it, so a configured id costs a storage round trip on the
|
|
70
|
+
* ordinary path while an id naming nothing used to touch no I/O. That gap is
|
|
71
|
+
* an oracle: sample the two and a wordlist recovers the connector list the
|
|
72
|
+
* flat 400 was meant to withhold. So zero-I/O refusals read the same key in
|
|
73
|
+
* the same `conn:<id>:` namespace, where an unconfigured id gets a miss.
|
|
70
74
|
*
|
|
71
75
|
* This is deliberately *not* a constant-time claim: a hit and a miss are not
|
|
72
76
|
* identical in a KV store, and a connector shipping its own `verifyState` may
|
|
@@ -78,10 +82,9 @@ function html(reason, opts, connector) {
|
|
|
78
82
|
* turning it into a 500 would hand back exactly the distinguishable response
|
|
79
83
|
* this whole path exists to deny.
|
|
80
84
|
*/
|
|
81
|
-
async function equalizeRefusalCost(context) {
|
|
85
|
+
async function equalizeRefusalCost(context, state) {
|
|
82
86
|
try {
|
|
83
|
-
|
|
84
|
-
await context.storage.get(oauthValueStorageKey("oauth:state", generation));
|
|
87
|
+
await context.storage.get(oauthFlowKeys.flow(await oauthStateDigest(state ?? "")));
|
|
85
88
|
}
|
|
86
89
|
catch {
|
|
87
90
|
// Deliberately ignored — see above.
|
|
@@ -100,16 +103,50 @@ async function equalizeRefusalCost(context) {
|
|
|
100
103
|
export function routeOAuthCallback(context) {
|
|
101
104
|
if (!context.path.startsWith("/oauth/callback/"))
|
|
102
105
|
return Effect.succeed(null);
|
|
106
|
+
// Downstream OAuth uses query-mode redirects; form_post is not supported.
|
|
107
|
+
if (context.request.method !== "GET")
|
|
108
|
+
return Effect.succeed(new Response(null, {
|
|
109
|
+
status: 405,
|
|
110
|
+
headers: { Allow: "GET" },
|
|
111
|
+
}));
|
|
103
112
|
return Effect.uninterruptible(Effect.promise(() => finishOAuthCallback(context)));
|
|
104
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* RFC 6749's error codes (sections 4.1.2.1 and 5.2) and RFC 8707's
|
|
116
|
+
* `invalid_target`. Only a code on this list is logged: the `code` an OAuth
|
|
117
|
+
* error carries is the provider's own text.
|
|
118
|
+
*/
|
|
119
|
+
const TOKEN_ERROR_CODES = new Set([
|
|
120
|
+
"invalid_request",
|
|
121
|
+
"invalid_client",
|
|
122
|
+
"invalid_grant",
|
|
123
|
+
"unauthorized_client",
|
|
124
|
+
"unsupported_grant_type",
|
|
125
|
+
"invalid_scope",
|
|
126
|
+
"invalid_target",
|
|
127
|
+
"access_denied",
|
|
128
|
+
"server_error",
|
|
129
|
+
"temporarily_unavailable",
|
|
130
|
+
]);
|
|
131
|
+
/** ` with OAuth error <code>` for a known code; nothing for anything else. */
|
|
132
|
+
function exchangeErrorCode(err) {
|
|
133
|
+
const code = err?.code;
|
|
134
|
+
return typeof code === "string" && TOKEN_ERROR_CODES.has(code) ? ` with OAuth error ${code}` : "";
|
|
135
|
+
}
|
|
136
|
+
/** The provider's own refusal of a duplicate callback, wherever it is wrapped. */
|
|
137
|
+
function claimedByAnotherCallback(err) {
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
for (let current = err; current instanceof Error && !seen.has(current); current = current.cause) {
|
|
140
|
+
if (current instanceof OAuthCallbackClaimedError)
|
|
141
|
+
return true;
|
|
142
|
+
seen.add(current);
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
105
146
|
async function finishOAuthCallback(context) {
|
|
106
147
|
const { path, url, baseUrl, opts } = context;
|
|
107
148
|
const error = url.searchParams.get("error");
|
|
108
|
-
if (error)
|
|
109
|
-
return html(providerErrorReason(error), opts);
|
|
110
149
|
const code = url.searchParams.get("code");
|
|
111
|
-
if (!code)
|
|
112
|
-
return html("invalid_callback", opts);
|
|
113
150
|
const id = path.slice("/oauth/callback/".length);
|
|
114
151
|
const state = url.searchParams.get("state");
|
|
115
152
|
const callbackTarget = await opts.registry.oauthCallbackView(id, state);
|
|
@@ -121,38 +158,40 @@ async function finishOAuthCallback(context) {
|
|
|
121
158
|
// nor touches storage for an unknown id, which is what lets the refusals
|
|
122
159
|
// below borrow it to equalize their cost.
|
|
123
160
|
const connectorContext = callbackRegistry
|
|
124
|
-
? callbackRegistry.contextFor(id, baseUrl)
|
|
125
|
-
: opts.registry.contextFor(id, baseUrl);
|
|
161
|
+
? callbackRegistry.contextFor(id, baseUrl, {}, context.defer ? { defer: context.defer } : {})
|
|
162
|
+
: opts.registry.contextFor(id, baseUrl, {}, context.defer ? { defer: context.defer } : {});
|
|
126
163
|
const refused = () => html("invalid_callback", opts);
|
|
127
164
|
if (!connector || !connector.finishAuth) {
|
|
128
|
-
await equalizeRefusalCost(connectorContext);
|
|
165
|
+
await equalizeRefusalCost(connectorContext, state);
|
|
129
166
|
return refused();
|
|
130
167
|
}
|
|
131
168
|
try {
|
|
132
169
|
const expectedPrincipalKey = callbackTarget?.principalKey;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
170
|
+
const browserIdentity = await authorizeUiIdentity(context.request, baseUrl, opts.config.auth, "OAuth callback", context.runtimeContext, opts.config.identity);
|
|
171
|
+
if (!browserIdentity.ok) {
|
|
172
|
+
if (browserIdentity.response.status === 401 && authorizationCredential(context.request).kind !== "absent")
|
|
173
|
+
return browserIdentity.response;
|
|
174
|
+
// A Clerk browser handshake refreshes its session and returns to this
|
|
175
|
+
// exact callback. It grants no identity and exchanges no code yet.
|
|
176
|
+
if (browserIdentity.response.status === 307 && browserIdentity.response.headers.has("location"))
|
|
177
|
+
return browserIdentity.response;
|
|
178
|
+
return refused();
|
|
179
|
+
}
|
|
180
|
+
if (!expectedPrincipalKey || browserIdentity.principalKey !== expectedPrincipalKey)
|
|
181
|
+
return refused();
|
|
182
|
+
try {
|
|
183
|
+
validateAuthPermissions(browserIdentity, opts.registry);
|
|
147
184
|
}
|
|
148
|
-
|
|
185
|
+
catch {
|
|
149
186
|
return refused();
|
|
150
187
|
}
|
|
188
|
+
if (!mayManageConnector(browserIdentity, connector))
|
|
189
|
+
return refused();
|
|
151
190
|
// CSRF / login-fixation guard: this route is intentionally public, so verify
|
|
152
191
|
// the `state` matches the flow connecta started BEFORE exchanging the code.
|
|
153
192
|
if (!connector.verifyState) {
|
|
154
|
-
await equalizeRefusalCost(connectorContext);
|
|
155
|
-
opts.logger.warn(`[connecta] refused an OAuth callback for connector ` +
|
|
193
|
+
await equalizeRefusalCost(connectorContext, state);
|
|
194
|
+
opts.config.logger.warn(`[connecta] refused an OAuth callback for connector ` +
|
|
156
195
|
`${loggableValue(id)} with 400: it implements finishAuth but no ` +
|
|
157
196
|
"verifyState, so connecta cannot establish that it started this flow. " +
|
|
158
197
|
"No authorization code was exchanged. Implement verifyState before " +
|
|
@@ -164,15 +203,11 @@ async function finishOAuthCallback(context) {
|
|
|
164
203
|
stateMatches = await connector.verifyState(state, connectorContext);
|
|
165
204
|
}
|
|
166
205
|
catch (err) {
|
|
167
|
-
opts.logger
|
|
168
|
-
`${loggableValue(id)} with 400: verifyState threw ` +
|
|
169
|
-
`${loggableValue(msg(err))}. No authorization code was exchanged. ` +
|
|
170
|
-
"Re-run authorization from connecta and check the verifier if it " +
|
|
171
|
-
"fails again.");
|
|
206
|
+
logFailure(opts.config.logger, "OAuth callback verifyState threw; no authorization code was exchanged", failureRecord({ connector: id }, err));
|
|
172
207
|
return refused();
|
|
173
208
|
}
|
|
174
209
|
if (!stateMatches) {
|
|
175
|
-
opts.logger.warn(`[connecta] refused an OAuth callback for connector ` +
|
|
210
|
+
opts.config.logger.warn(`[connecta] refused an OAuth callback for connector ` +
|
|
176
211
|
`${loggableValue(id)} with 400: ` +
|
|
177
212
|
(state === null
|
|
178
213
|
? "the state parameter was missing"
|
|
@@ -181,28 +216,72 @@ async function finishOAuthCallback(context) {
|
|
|
181
216
|
"connecta and try again.");
|
|
182
217
|
return refused();
|
|
183
218
|
}
|
|
184
|
-
|
|
219
|
+
// Error responses are callbacks too: neither their code nor their copy is
|
|
220
|
+
// interpreted until the state and the consent's issuer have been validated.
|
|
221
|
+
if (["state", "iss", "code", "error"].some((name) => url.searchParams.getAll(name).length > 1))
|
|
222
|
+
return refused();
|
|
223
|
+
try {
|
|
224
|
+
const issuer = url.searchParams.get("iss");
|
|
225
|
+
if (connector.verifyCallbackIssuer) {
|
|
226
|
+
if (!(await connector.verifyCallbackIssuer(issuer, connectorContext)))
|
|
227
|
+
return refused();
|
|
228
|
+
}
|
|
229
|
+
else if (issuer !== null) {
|
|
230
|
+
// A custom connector with no issuer validator cannot vouch for this response.
|
|
231
|
+
return refused();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return refused();
|
|
236
|
+
}
|
|
237
|
+
if (error === null && !code)
|
|
238
|
+
return refused();
|
|
239
|
+
if (error !== null && !connector.consumeAuthError)
|
|
240
|
+
return refused();
|
|
241
|
+
{
|
|
185
242
|
try {
|
|
186
|
-
await opts.registry.
|
|
243
|
+
if (!(await opts.registry.consumeOAuthHandoff(id, state, expectedPrincipalKey)))
|
|
244
|
+
return refused();
|
|
187
245
|
}
|
|
188
246
|
catch (err) {
|
|
189
|
-
opts.logger
|
|
190
|
-
`${loggableValue(id)} with 500: its principal handoff could not be ` +
|
|
191
|
-
`consumed (${loggableValue(msg(err))}). No authorization code was exchanged.`);
|
|
247
|
+
logFailure(opts.config.logger, "OAuth callback handoff could not be consumed; no authorization code was exchanged", failureRecord({ connector: id }, err));
|
|
192
248
|
return html("handoff_failed", opts, connector);
|
|
193
249
|
}
|
|
194
250
|
}
|
|
251
|
+
if (error !== null) {
|
|
252
|
+
try {
|
|
253
|
+
await connector.consumeAuthError(connectorContext);
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
if (claimedByAnotherCallback(err))
|
|
257
|
+
return refused();
|
|
258
|
+
logFailure(opts.config.logger, "OAuth error callback consent could not be consumed; no authorization code was exchanged", failureRecord({ connector: id }, err));
|
|
259
|
+
return withSessionCookies(html("handoff_failed", opts, connector), browserIdentity.sessionCookies);
|
|
260
|
+
}
|
|
261
|
+
return withSessionCookies(html(providerErrorReason(error), opts), browserIdentity.sessionCookies);
|
|
262
|
+
}
|
|
195
263
|
try {
|
|
196
264
|
await connector.finishAuth(code, connectorContext, url.searchParams);
|
|
197
265
|
await callbackRegistry.invalidateStored(id);
|
|
198
|
-
return html("connected", opts, connector);
|
|
266
|
+
return withSessionCookies(html("connected", opts, connector), browserIdentity.sessionCookies);
|
|
199
267
|
}
|
|
200
268
|
catch (err) {
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
269
|
+
// A duplicate of a callback that already claimed this consent sent
|
|
270
|
+
// nothing: it is the already-used link the flat refusal describes, not
|
|
271
|
+
// an exchange the provider rejected.
|
|
272
|
+
if (claimedByAnotherCallback(err)) {
|
|
273
|
+
opts.config.logger.warn(`[connecta] refused an OAuth callback for connector ` +
|
|
274
|
+
`${loggableValue(id)} with 400: another callback had already ` +
|
|
275
|
+
"claimed its state. No authorization code was exchanged.");
|
|
276
|
+
return refused();
|
|
277
|
+
}
|
|
278
|
+
// Neither the page nor the log repeats what the exchange threw: the SDK
|
|
279
|
+
// quotes the token endpoint's error_description or raw body, and a
|
|
280
|
+
// provider echoing a client_secret_post request puts the secret there.
|
|
281
|
+
opts.config.logger.warn(`[connecta] OAuth callback for connector ${loggableValue(id)} failed ` +
|
|
282
|
+
`with 500: the authorization code exchange failed${exchangeErrorCode(err)}. ` +
|
|
283
|
+
"Check the connector's client configuration and re-run authorization.");
|
|
284
|
+
return withSessionCookies(html("exchange_failed", opts, connector), browserIdentity.sessionCookies);
|
|
206
285
|
}
|
|
207
286
|
}
|
|
208
287
|
finally {
|
package/dist/routes/operator.js
CHANGED
|
@@ -36,15 +36,15 @@ export function refuse(error, status) {
|
|
|
36
36
|
return Effect.fail(new Answer(privateJson({ error }, { status })));
|
|
37
37
|
}
|
|
38
38
|
function admitted(result) {
|
|
39
|
-
return Effect.flatMap(Effect.promise(() => result), (authz) => authz.ok ? Effect.succeed(authz) : Effect.fail(new Answer(authz.response)));
|
|
39
|
+
return Effect.flatMap(Effect.promise(() => result), (authz) => (authz.ok ? Effect.succeed(authz) : Effect.fail(new Answer(authz.response))));
|
|
40
40
|
}
|
|
41
41
|
/** Any configured caller. The gate's refusal, challenge headers and all, is the answer. */
|
|
42
42
|
export function authorized({ request, baseUrl, opts, runtimeContext }, partitionIdentity = true) {
|
|
43
|
-
return Effect.suspend(() => admitted(authorize(request, baseUrl, opts.auth, runtimeContext, opts.identity, partitionIdentity)));
|
|
43
|
+
return Effect.suspend(() => admitted(authorize(request, baseUrl, opts.config.auth, runtimeContext, opts.config.identity, partitionIdentity)));
|
|
44
44
|
}
|
|
45
45
|
/** A signed-in person, for a route that changes how a connector authenticates. */
|
|
46
46
|
export function authorizedPerson({ request, baseUrl, opts, runtimeContext }, purpose) {
|
|
47
|
-
return Effect.suspend(() => admitted(authorizeUiIdentity(request, baseUrl, opts.auth, purpose, runtimeContext, opts.identity)));
|
|
47
|
+
return Effect.suspend(() => admitted(authorizeUiIdentity(request, baseUrl, opts.config.auth, purpose, runtimeContext, opts.config.identity)));
|
|
48
48
|
}
|
|
49
49
|
/** The partition and tool grants an identity carries into a registry view. */
|
|
50
50
|
export function scopeFor(authz, connectorIds = authz.connectorIds) {
|
package/dist/routes/shared.d.ts
CHANGED
|
@@ -1,66 +1,30 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AccessTokensModule, ActivityModule, ArtifactsModule, OperatorSurface } from "../module-contracts.js";
|
|
3
|
-
import type { ActivityActor, ActivityReadGate, ActivityStore } from "../activity.js";
|
|
4
|
-
import type { CredentialVault } from "../credential-contract.js";
|
|
1
|
+
import type { ActivityActor } from "../activity.js";
|
|
5
2
|
import type { DeferredWork } from "../connector-scope.js";
|
|
6
3
|
import type { AdmissionController } from "../executor-admission.js";
|
|
7
4
|
import type { Registry, ToolAccess } from "../registry.js";
|
|
8
5
|
import type { ResolvedPool } from "../connector-access.js";
|
|
9
|
-
import type { AuthenticatedIdentity,
|
|
10
|
-
import type {
|
|
11
|
-
import type { ConnectorPermission, ConnectaIdentityConfig } from "../index.js";
|
|
6
|
+
import type { AuthenticatedIdentity, Executor, InboundAuth, InboundAuthRuntimeContext } from "../types.js";
|
|
7
|
+
import type { ConnectorPermission, ConnectaIdentityConfig, ResolvedConfig } from "../config.js";
|
|
12
8
|
export { msg } from "../errors.js";
|
|
13
9
|
export interface ServerOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Everything createConnecta resolved from configuration: auth, identity,
|
|
12
|
+
* URLs, modules, logger, and every limit with its default applied. Routes
|
|
13
|
+
* read it here rather than from fields copied out of it one by one.
|
|
14
|
+
*/
|
|
15
|
+
config: ResolvedConfig;
|
|
16
|
+
/** Construction-time allowlisted snapshot, frozen by describeConfig. */
|
|
17
|
+
configDescription: import("../describe-config.js").ConnectaConfigDescription;
|
|
18
|
+
configValueSources?: Readonly<Record<string, "default" | "config">>;
|
|
14
19
|
registry: Registry;
|
|
15
|
-
auth: InboundAuth[];
|
|
16
|
-
identity?: ConnectaIdentityConfig | undefined;
|
|
17
20
|
/** Validated named pools served at `/mcp/<name>`; empty when none declared. */
|
|
18
|
-
pools
|
|
19
|
-
|
|
20
|
-
artifactOrigin?: string | undefined;
|
|
21
|
-
allowedOrigins?: readonly string[] | "*" | undefined;
|
|
22
|
-
serverInfo: Implementation;
|
|
23
|
-
logger: Logger;
|
|
24
|
-
activity?: ActivityStore | undefined;
|
|
25
|
-
activityReadGate?: ActivityReadGate | undefined;
|
|
26
|
-
activityDeploymentId?: string | undefined;
|
|
27
|
-
deploymentInfo?: Record<string, unknown> | undefined;
|
|
28
|
-
/** Deadline for call_tool/call_destructive_tool calls that pass no timeoutMs. Off when unset. */
|
|
29
|
-
defaultToolTimeoutMs?: number | undefined;
|
|
30
|
-
/** Per-connector deadline for the search/describe probe fan-out. Default 30_000. */
|
|
31
|
-
probeTimeoutMs?: number | undefined;
|
|
32
|
-
/** Maximum simultaneous connector discovery operations. Default 4. */
|
|
33
|
-
discoveryConcurrency?: number | undefined;
|
|
34
|
-
/** Aggregate serialized-byte budget for connecta.emit per run. Default 4_000_000. */
|
|
35
|
-
maxEmittedBytes?: number | undefined;
|
|
36
|
-
/** Block-count budget for connecta.emit per run. Default 32. */
|
|
37
|
-
maxEmittedBlocks?: number | undefined;
|
|
38
|
-
/** Host calls one execute_code program may make. Default 20. */
|
|
39
|
-
maxHostCalls?: number | undefined;
|
|
40
|
-
/** Deadline per execute_code host call. Default 15_000. */
|
|
41
|
-
hostCallTimeoutMs?: number | undefined;
|
|
42
|
-
/** Hard ceiling on one execute_code run, outside the sandbox. Default 120_000. */
|
|
43
|
-
watchdogMs?: number | undefined;
|
|
44
|
-
/** Config approval exemptions (`execute.approval`), resolved at construction. */
|
|
45
|
-
approval?: ApprovalPolicy | undefined;
|
|
46
|
-
/** Exempt writes one program may send (`execute.maxWrites`). */
|
|
47
|
-
maxWrites?: number | undefined;
|
|
48
|
-
/** Required sandbox backing the execute_code meta-tool. */
|
|
21
|
+
pools: ReadonlyMap<string, ResolvedPool>;
|
|
22
|
+
/** Required sandbox backing execute_code, wrapped in fallback admission if needed. */
|
|
49
23
|
executor: Executor;
|
|
50
24
|
/** Sanitized identity of the configured sandbox, when it has one. */
|
|
51
25
|
executorName?: string | undefined;
|
|
52
26
|
/** Global FIFO boundary for all non-preflight `/mcp` requests. */
|
|
53
27
|
requestAdmission: AdmissionController;
|
|
54
|
-
/** Encrypted connector-credential storage backing the Credentials page. */
|
|
55
|
-
credentialVault?: CredentialVault | undefined;
|
|
56
|
-
/** Optional browser routes, with no implementation import in core. */
|
|
57
|
-
ui?: OperatorSurface | undefined;
|
|
58
|
-
activityModule?: ActivityModule | undefined;
|
|
59
|
-
/** Optional team pages; their routes mount only beside `ui`. */
|
|
60
|
-
artifactsModule?: ArtifactsModule | undefined;
|
|
61
|
-
accessTokens?: AccessTokensModule | undefined;
|
|
62
|
-
/** Optional browser UI and OAuth result-page labels. */
|
|
63
|
-
branding?: ConnectaBranding | undefined;
|
|
64
28
|
}
|
|
65
29
|
export interface RuntimeExecutionContext extends InboundAuthRuntimeContext {
|
|
66
30
|
waitUntil(promise: Promise<unknown>): void;
|
|
@@ -86,10 +50,16 @@ export declare function privateJson(body: unknown, init?: ResponseInit): Respons
|
|
|
86
50
|
*/
|
|
87
51
|
export declare function loggableValue(requested: string): string;
|
|
88
52
|
export declare function activityActorNamespace(provider: InboundAuth): string | undefined;
|
|
89
|
-
|
|
53
|
+
/** Metadata routing and challenge selection use the same first actual answer. */
|
|
54
|
+
export declare function authMetadata(request: Request, baseUrl: string, auth: readonly InboundAuth[]): Promise<{
|
|
55
|
+
provider: InboundAuth;
|
|
56
|
+
response: Response;
|
|
57
|
+
} | null>;
|
|
58
|
+
export declare function authorize(request: Request, baseUrl: string, auth: readonly InboundAuth[], runtimeContext?: RuntimeExecutionContext, identityConfig?: ConnectaIdentityConfig, partitionIdentity?: boolean, interactiveOnly?: boolean): Promise<{
|
|
90
59
|
ok: true;
|
|
91
60
|
actor: ActivityActor;
|
|
92
61
|
identity: AuthenticatedIdentity;
|
|
62
|
+
sessionCookies?: readonly string[];
|
|
93
63
|
subjectKey?: string;
|
|
94
64
|
principalKey?: string;
|
|
95
65
|
connectorIds: "all" | readonly string[];
|
|
@@ -107,12 +77,8 @@ export declare function authorize(request: Request, baseUrl: string, auth: Inbou
|
|
|
107
77
|
ok: false;
|
|
108
78
|
response: Response;
|
|
109
79
|
}>;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}> | {
|
|
113
|
-
ok: false;
|
|
114
|
-
response: Response;
|
|
115
|
-
}>;
|
|
80
|
+
/** Human routes reject machine credentials before any verification or storage I/O. */
|
|
81
|
+
export declare function authorizeUiIdentity(request: Request, baseUrl: string, auth: readonly InboundAuth[], purpose: string, runtimeContext?: RuntimeExecutionContext, identityConfig?: ConnectaIdentityConfig): Promise<Awaited<ReturnType<typeof authorize>>>;
|
|
116
82
|
export declare function isSameOrigin(request: Request, baseUrl: string): boolean;
|
|
117
83
|
export declare function withSecurityHeaders(response: Response, requestUrl: URL, _path: string): Response;
|
|
118
84
|
type AuthorizedIdentity = Extract<Awaited<ReturnType<typeof authorize>>, {
|
|
@@ -120,14 +86,9 @@ type AuthorizedIdentity = Extract<Awaited<ReturnType<typeof authorize>>, {
|
|
|
120
86
|
}>;
|
|
121
87
|
/** Unknown configured ids refuse the complete view, including management rights. */
|
|
122
88
|
export declare function validateAuthPermissions(authz: AuthorizedIdentity, registry: Registry): void;
|
|
123
|
-
/**
|
|
124
|
-
* Whether this identity may open artifact pages: exactly whoever may read the
|
|
125
|
-
* `artifacts` connector — it is visible to them and `get_artifact` is among
|
|
126
|
-
* its tools they were granted. Viewing grants nothing a program could not
|
|
127
|
-
* already read (ethos, "Human routes manage auth, never capability").
|
|
128
|
-
*/
|
|
129
|
-
export declare function mayViewArtifacts(authz: AuthorizedIdentity, registry: Registry): boolean;
|
|
130
89
|
export declare function mayManageConnector(authz: AuthorizedIdentity, connector: {
|
|
131
90
|
id: string;
|
|
132
91
|
authScope?: "shared" | "personal";
|
|
133
92
|
}): boolean;
|
|
93
|
+
/** Preserve refreshed browser cookies on the redirect or completion page. */
|
|
94
|
+
export declare function withSessionCookies(response: Response, cookies?: readonly string[]): Response;
|