@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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { ConnectorCallError } from "../errors.js";
|
|
2
|
+
import { authorizationCredential } from "../inbound-credential.js";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { escapeHtml, renderPage } from "../branding.js";
|
|
5
|
+
import { htmlSecurityHeaders } from "../html-security.js";
|
|
6
|
+
import { closeConnectorScope } from "../connector-scope.js";
|
|
7
|
+
import { drainOAuthStartResets } from "../auth/oauth-start-reset.js";
|
|
8
|
+
import { finishOAuthConnectStart, consumeOAuthConnectLink, oauthConnectUnavailable, verifyOAuthHandoff, } from "../oauth-handoff.js";
|
|
9
|
+
import { failureRecord, logFailure } from "../operator-record.js";
|
|
10
|
+
import { runEdge, withDeadlineEffect } from "../runtime/run.js";
|
|
11
|
+
import { authorizeUiIdentity, mayManageConnector, privateJson, validateAuthPermissions, withSessionCookies, } from "./shared.js";
|
|
12
|
+
const START_TIMEOUT_MS = 30_000;
|
|
13
|
+
/** Clerk establishes a browser session on this origin before retrying the link. */
|
|
14
|
+
function clerkSignIn(context) {
|
|
15
|
+
const clerk = context.opts.config.auth.find((provider) => provider.uiAuth?.kind === "clerk")?.uiAuth;
|
|
16
|
+
if (clerk?.kind !== "clerk")
|
|
17
|
+
return undefined;
|
|
18
|
+
const returnUrl = new URL(context.path, context.baseUrl);
|
|
19
|
+
returnUrl.search = context.url.search;
|
|
20
|
+
if (clerk.signInUrl) {
|
|
21
|
+
try {
|
|
22
|
+
const target = new URL(clerk.signInUrl);
|
|
23
|
+
if (target.protocol === "https:" && !target.username && !target.password) {
|
|
24
|
+
target.searchParams.set("redirect_url", returnUrl.href);
|
|
25
|
+
return new Response(null, { status: 302, headers: { Location: target.href, "Cache-Control": "no-store" } });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* Use the local sign-in page. */
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let origin;
|
|
33
|
+
try {
|
|
34
|
+
const frontend = new URL(clerk.frontendApiUrl);
|
|
35
|
+
if (frontend.protocol !== "https:" || frontend.username || frontend.password)
|
|
36
|
+
return undefined;
|
|
37
|
+
origin = frontend.origin;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const nonce = crypto.randomUUID();
|
|
43
|
+
const target = JSON.stringify(returnUrl.href).replace(/</g, "\\u003c");
|
|
44
|
+
const body = `<main class="page shell"><h1>Sign in to connect</h1><p>Sign in as the user who requested this connection.</p><div id="signin"></div></main>
|
|
45
|
+
<script nonce="${nonce}" crossorigin="anonymous" data-clerk-publishable-key="${escapeHtml(clerk.publishableKey)}" src="${escapeHtml(origin)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>
|
|
46
|
+
<script nonce="${nonce}">window.Clerk.load().then(() => {
|
|
47
|
+
if (window.Clerk.session) { window.location.replace(${target}); return; }
|
|
48
|
+
window.Clerk.mountSignIn(document.getElementById("signin"), { routing: "hash", forceRedirectUrl: ${target}, signUpForceRedirectUrl: ${target} });
|
|
49
|
+
}).catch(() => { document.getElementById("signin").textContent = "Sign-in could not load. Try again."; });</script>`;
|
|
50
|
+
return new Response(renderPage(context.opts.config.ui?.branding, {
|
|
51
|
+
title: "Sign in to connect",
|
|
52
|
+
uiMounted: Boolean(context.opts.config.ui),
|
|
53
|
+
body,
|
|
54
|
+
}), {
|
|
55
|
+
headers: htmlSecurityHeaders({
|
|
56
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
57
|
+
"Cache-Control": "no-store",
|
|
58
|
+
}, { clerkOrigin: origin, nonce }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function routeConnect(context) {
|
|
62
|
+
if (!context.path.startsWith("/connect/"))
|
|
63
|
+
return Effect.succeed(null);
|
|
64
|
+
return Effect.promise(() => connect(context));
|
|
65
|
+
}
|
|
66
|
+
async function connect(context) {
|
|
67
|
+
const { opts, request, baseUrl, runtimeContext } = context;
|
|
68
|
+
const refuse = (error, status = 403) => privateJson({ error }, { status });
|
|
69
|
+
if (request.method !== "GET")
|
|
70
|
+
return privateJson({ error: "method not allowed" }, { status: 405, headers: { Allow: "GET" } });
|
|
71
|
+
const unavailable = oauthConnectUnavailable(opts);
|
|
72
|
+
if (unavailable)
|
|
73
|
+
return refuse(unavailable);
|
|
74
|
+
const id = context.path.slice("/connect/".length);
|
|
75
|
+
const handoff = await verifyOAuthHandoff(opts, baseUrl, id, context.url.searchParams.get("h"));
|
|
76
|
+
if (!handoff)
|
|
77
|
+
return refuse("Invalid or expired connection link. Request a new link from connecta.", 400);
|
|
78
|
+
const authPage = () => {
|
|
79
|
+
const target = new URL(`/connectors/${id}`, baseUrl);
|
|
80
|
+
target.searchParams.set("h", context.url.searchParams.get("h"));
|
|
81
|
+
target.hash = "auth";
|
|
82
|
+
return new Response(null, {
|
|
83
|
+
status: 302,
|
|
84
|
+
headers: { Location: target.href, "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" },
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const authz = await authorizeUiIdentity(request, baseUrl, opts.config.auth, "OAuth connection", runtimeContext, opts.config.identity);
|
|
88
|
+
if (!authz.ok) {
|
|
89
|
+
// Access sign-in is enforced at the edge. Clerk needs its own sign-in page.
|
|
90
|
+
if (authz.response.status === 401 &&
|
|
91
|
+
authorizationCredential(request).kind === "absent" &&
|
|
92
|
+
!runtimeContext?.access) {
|
|
93
|
+
if (opts.config.ui)
|
|
94
|
+
return authPage();
|
|
95
|
+
const signIn = clerkSignIn(context);
|
|
96
|
+
if (signIn)
|
|
97
|
+
return signIn;
|
|
98
|
+
}
|
|
99
|
+
return authz.response;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
validateAuthPermissions(authz, opts.registry);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return refuse("forbidden");
|
|
106
|
+
}
|
|
107
|
+
const connector = opts.registry.getConnector(id);
|
|
108
|
+
if (authz.principalKey !== handoff.principal || !mayManageConnector(authz, connector)) {
|
|
109
|
+
return refuse("Sign in as the user who requested this connection and has permission to manage it.");
|
|
110
|
+
}
|
|
111
|
+
if (!connector.startAuth) {
|
|
112
|
+
if (!connector.credential || !opts.config.ui || !opts.config.vault)
|
|
113
|
+
return refuse("unknown OAuth connector", 404);
|
|
114
|
+
let target;
|
|
115
|
+
try {
|
|
116
|
+
target = new URL(opts.config.ui.credentialHandoffUrl(baseUrl), baseUrl);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return refuse("Credential management URL is unavailable", 503);
|
|
120
|
+
}
|
|
121
|
+
if (target.origin !== new URL(baseUrl).origin || target.username || target.password)
|
|
122
|
+
return refuse("Credential management URL is unavailable", 503);
|
|
123
|
+
if (!(await consumeOAuthConnectLink(opts, handoff)))
|
|
124
|
+
return refuse("Invalid or expired connection link. Request a new link from connecta.", 400);
|
|
125
|
+
if (!(await finishOAuthConnectStart(opts, handoff, "started")))
|
|
126
|
+
return refuse("Invalid or expired connection link. Request a new link from connecta.", 400);
|
|
127
|
+
// Credential entry stays in the authenticated operator UI on this origin.
|
|
128
|
+
return withSessionCookies(new Response(null, {
|
|
129
|
+
status: 302,
|
|
130
|
+
headers: {
|
|
131
|
+
Location: target.href,
|
|
132
|
+
"Cache-Control": "no-store",
|
|
133
|
+
},
|
|
134
|
+
}), authz.sessionCookies);
|
|
135
|
+
}
|
|
136
|
+
if (opts.config.ui && context.url.searchParams.get("start") !== "1")
|
|
137
|
+
return withSessionCookies(authPage(), authz.sessionCookies);
|
|
138
|
+
const registry = opts.registry.scoped({
|
|
139
|
+
connectorIds: [id],
|
|
140
|
+
principalKey: authz.principalKey,
|
|
141
|
+
...(authz.subjectKey ? { subjectKey: authz.subjectKey } : {}),
|
|
142
|
+
});
|
|
143
|
+
const scope = {};
|
|
144
|
+
let ctx = registry.contextFor(id, baseUrl, scope, context.defer ? { defer: context.defer } : {});
|
|
145
|
+
const timeoutError = new ConnectorCallError("timeout", "OAuth authorization start timed out");
|
|
146
|
+
let claimed = false;
|
|
147
|
+
try {
|
|
148
|
+
if (!(await consumeOAuthConnectLink(opts, handoff)))
|
|
149
|
+
return refuse("Invalid or expired connection link. Request a new link from connecta.", 400);
|
|
150
|
+
claimed = true;
|
|
151
|
+
const status = await runEdge(withDeadlineEffect((signal) => Effect.tryPromise({
|
|
152
|
+
try: async () => {
|
|
153
|
+
ctx = registry.contextFor(id, baseUrl, scope, {
|
|
154
|
+
signal,
|
|
155
|
+
...(context.defer ? { defer: context.defer } : {}),
|
|
156
|
+
});
|
|
157
|
+
const started = await connector.startAuth(ctx, { force: handoff.force });
|
|
158
|
+
if (signal.aborted)
|
|
159
|
+
throw signal.reason;
|
|
160
|
+
if (started.authorizationUrl) {
|
|
161
|
+
let target;
|
|
162
|
+
try {
|
|
163
|
+
target = new URL(started.authorizationUrl);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* Refused below. */
|
|
167
|
+
}
|
|
168
|
+
if (target && ["https:", "http:"].includes(target.protocol) && !target.username && !target.password) {
|
|
169
|
+
await registry.bindOAuthHandoff(id, target.href);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
return { ...started, authorizationUrl: undefined };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return started;
|
|
176
|
+
},
|
|
177
|
+
catch: (error) => error,
|
|
178
|
+
}), { timeoutMs: START_TIMEOUT_MS, signal: request.signal, timeoutError }));
|
|
179
|
+
await drainOAuthStartResets(scope);
|
|
180
|
+
if (handoff.force || (!status.authorizationReused && status.state !== "ok"))
|
|
181
|
+
await registry.invalidateStored(id);
|
|
182
|
+
if (status.state === "ok") {
|
|
183
|
+
if (!(await finishOAuthConnectStart(opts, handoff, "started")))
|
|
184
|
+
return refuse("Invalid or expired connection link. Request a new link from connecta.", 400);
|
|
185
|
+
return withSessionCookies(new Response("This connector is already connected.", { headers: { "Cache-Control": "no-store" } }), authz.sessionCookies);
|
|
186
|
+
}
|
|
187
|
+
if (status.state !== "auth_required" || !status.authorizationUrl) {
|
|
188
|
+
// The start's message can be a downstream's refusal, which an agent
|
|
189
|
+
// may read but a log may not (INV-6): the record keeps the checked
|
|
190
|
+
// state only, since a plugin's `startAuth` returns whatever it likes.
|
|
191
|
+
logFailure(opts.config.logger, "OAuth start failed", failureRecord({
|
|
192
|
+
connector: id,
|
|
193
|
+
mode: handoff.force ? "restart" : "continue",
|
|
194
|
+
state: status.state,
|
|
195
|
+
}));
|
|
196
|
+
return refuse(status.state === "auth_required"
|
|
197
|
+
? "OAuth authorization requires consent but no safe URL is available"
|
|
198
|
+
: "OAuth authorization could not start", 502);
|
|
199
|
+
}
|
|
200
|
+
const target = new URL(status.authorizationUrl);
|
|
201
|
+
if (!["https:", "http:"].includes(target.protocol) || target.username || target.password)
|
|
202
|
+
return refuse("OAuth authorization returned no safe URL", 502);
|
|
203
|
+
if (!(await finishOAuthConnectStart(opts, handoff, "started")))
|
|
204
|
+
return refuse("Invalid or expired connection link. Request a new link from connecta.", 400);
|
|
205
|
+
return withSessionCookies(new Response(null, { status: 302, headers: { Location: target.href, "Cache-Control": "no-store" } }), authz.sessionCookies);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
await drainOAuthStartResets(scope);
|
|
209
|
+
await registry.invalidateStored(id);
|
|
210
|
+
if (error === timeoutError)
|
|
211
|
+
return refuse("OAuth authorization start timed out", 504);
|
|
212
|
+
logFailure(opts.config.logger, "OAuth start failed", failureRecord({ connector: id }, error));
|
|
213
|
+
return refuse("OAuth authorization could not start", 400);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
await drainOAuthStartResets(scope);
|
|
217
|
+
await closeConnectorScope(connector, ctx, context.defer);
|
|
218
|
+
if (claimed)
|
|
219
|
+
await finishOAuthConnectStart(opts, handoff, "failed");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { failureRecord, logFailure } from "../operator-record.js";
|
|
1
2
|
import { Effect } from "effect";
|
|
2
3
|
import { claimConnectorScopeCleanup, closeConnectorScope } from "../connector-scope.js";
|
|
3
|
-
import { credentialTestRule, describeCredentialTestMismatch, storedCredentialShape
|
|
4
|
-
import { authorizedPerson, refuse, serveOperator, visibleRegistry
|
|
5
|
-
import { mayManageConnector, isSameOrigin, msg, privateJson
|
|
4
|
+
import { credentialTestRule, describeCredentialTestMismatch, storedCredentialShape } from "../credential-rules.js";
|
|
5
|
+
import { authorizedPerson, refuse, serveOperator, visibleRegistry } from "./operator.js";
|
|
6
|
+
import { mayManageConnector, isSameOrigin, msg, privateJson } from "./shared.js";
|
|
6
7
|
/** The longest body, in characters, a credential write accepts. */
|
|
7
8
|
const MAX_BODY_CHARS = 20_000;
|
|
8
9
|
/**
|
|
@@ -48,9 +49,7 @@ function credentialInput(body, config) {
|
|
|
48
49
|
}
|
|
49
50
|
return Effect.succeed({ kind: "single", value: body.value });
|
|
50
51
|
}
|
|
51
|
-
if (!body.values ||
|
|
52
|
-
typeof body.values !== "object" ||
|
|
53
|
-
Array.isArray(body.values)) {
|
|
52
|
+
if (!body.values || typeof body.values !== "object" || Array.isArray(body.values)) {
|
|
54
53
|
return refuse("values must be an object", 400);
|
|
55
54
|
}
|
|
56
55
|
const rawValues = body.values;
|
|
@@ -71,10 +70,7 @@ function credentialInput(body, config) {
|
|
|
71
70
|
}
|
|
72
71
|
function readCredentialInput(request, config) {
|
|
73
72
|
return Effect.gen(function* () {
|
|
74
|
-
if (!request.headers
|
|
75
|
-
.get("content-type")
|
|
76
|
-
?.toLowerCase()
|
|
77
|
-
.startsWith("application/json")) {
|
|
73
|
+
if (!request.headers.get("content-type")?.toLowerCase().startsWith("application/json")) {
|
|
78
74
|
return yield* refuse("Content-Type must be application/json", 415);
|
|
79
75
|
}
|
|
80
76
|
const raw = yield* Effect.promise(() => boundedText(request));
|
|
@@ -96,7 +92,7 @@ function readCredentialInput(request, config) {
|
|
|
96
92
|
function credentialRequest(context, connectorId, action) {
|
|
97
93
|
const { request, baseUrl, opts } = context;
|
|
98
94
|
return Effect.gen(function* () {
|
|
99
|
-
const vault = opts.
|
|
95
|
+
const vault = opts.config.vault;
|
|
100
96
|
if (!vault)
|
|
101
97
|
return yield* refuse("credential storage is not configured", 503);
|
|
102
98
|
if (!isSameOrigin(request, baseUrl)) {
|
|
@@ -117,7 +113,7 @@ function credentialRequest(context, connectorId, action) {
|
|
|
117
113
|
const owner = personal ? authz.principalKey : undefined;
|
|
118
114
|
const updatedBy = authz.identity.principal
|
|
119
115
|
? `${authz.identity.principal.namespace}:${authz.identity.principal.id}`
|
|
120
|
-
: authz.actor.id ?? authz.actor.kind;
|
|
116
|
+
: (authz.actor.id ?? authz.actor.kind);
|
|
121
117
|
if (action === "test") {
|
|
122
118
|
if (request.method !== "POST")
|
|
123
119
|
return yield* refuse("method not allowed", 405);
|
|
@@ -127,25 +123,16 @@ function credentialRequest(context, connectorId, action) {
|
|
|
127
123
|
const { mode, mismatch } = credentialTestRule(connector);
|
|
128
124
|
if (!mode) {
|
|
129
125
|
return yield* refuse(mismatch
|
|
130
|
-
? "this connector cannot test its credential: " +
|
|
131
|
-
describeCredentialTestMismatch(mismatch)
|
|
126
|
+
? "this connector cannot test its credential: " + describeCredentialTestMismatch(mismatch)
|
|
132
127
|
: "this connector does not support credential testing", 400);
|
|
133
128
|
}
|
|
134
129
|
// Whatever goes wrong past this point is the test's result, not the
|
|
135
130
|
// route's: the page shows it beside the Test button. Only `ok` leaves
|
|
136
131
|
// the host. A hook's message, and anything it throws, can quote the
|
|
137
132
|
// downstream's reply, and that reply can quote the credential it just
|
|
138
|
-
// rejected — so the
|
|
139
|
-
// says a fixed sentence for the outcome
|
|
140
|
-
|
|
141
|
-
if (!detail)
|
|
142
|
-
return;
|
|
143
|
-
const line = `[connecta] connector "${connectorId}" credential test ${ok ? "passed" : "failed"}: ${detail}`;
|
|
144
|
-
if (ok)
|
|
145
|
-
opts.logger.info(line);
|
|
146
|
-
else
|
|
147
|
-
opts.logger.warn(line);
|
|
148
|
-
};
|
|
133
|
+
// rejected — so neither the page nor the log repeats it (INV-6): the
|
|
134
|
+
// page says a fixed sentence for the outcome, and the log records a
|
|
135
|
+
// failure's class.
|
|
149
136
|
return yield* Effect.tryPromise({
|
|
150
137
|
try: async () => {
|
|
151
138
|
const values = await vault.getAll(connectorId, owner);
|
|
@@ -173,7 +160,8 @@ function credentialRequest(context, connectorId, action) {
|
|
|
173
160
|
// The single-value shape check above guarantees this key.
|
|
174
161
|
storedValues.value, ctx);
|
|
175
162
|
const ok = result?.ok === true;
|
|
176
|
-
|
|
163
|
+
if (!ok)
|
|
164
|
+
logFailure(opts.config.logger, "credential test failed", failureRecord({ connector: connectorId }, undefined));
|
|
177
165
|
return privateJson({ ok });
|
|
178
166
|
}
|
|
179
167
|
finally {
|
|
@@ -183,7 +171,7 @@ function credentialRequest(context, connectorId, action) {
|
|
|
183
171
|
},
|
|
184
172
|
catch: (error) => error,
|
|
185
173
|
}).pipe(Effect.catch((error) => {
|
|
186
|
-
|
|
174
|
+
logFailure(opts.config.logger, "credential test threw", failureRecord({ connector: connectorId }, error));
|
|
187
175
|
return Effect.succeed(privateJson({ ok: false }));
|
|
188
176
|
}));
|
|
189
177
|
}
|