@zackbart/connecta 0.23.0 → 0.24.1
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/AGENTS.md +5 -0
- package/CHANGELOG.md +53 -0
- package/README.md +18 -10
- package/dist/activity-friction.d.ts +3 -0
- package/dist/activity-friction.js +19 -0
- package/dist/activity.d.ts +11 -2
- package/dist/activity.js +15 -19
- package/dist/auth/downstream-oauth.d.ts +2 -1
- package/dist/auth/downstream-oauth.js +10 -1
- package/dist/branding.d.ts +67 -0
- package/dist/branding.js +176 -0
- package/dist/connectors/remote-mcp.js +3 -5
- package/dist/credential-contract.d.ts +24 -0
- package/dist/credential-contract.js +1 -0
- package/dist/credential-rules.d.ts +85 -0
- package/dist/credential-rules.js +107 -0
- package/dist/credentials.d.ts +4 -100
- package/dist/credentials.js +3 -107
- package/dist/execute.d.ts +6 -0
- package/dist/execute.js +17 -7
- package/dist/index.d.ts +35 -56
- package/dist/index.js +34 -58
- package/dist/invocation.js +19 -4
- package/dist/meta-tools.d.ts +4 -0
- package/dist/meta-tools.js +8 -4
- package/dist/module-contracts.d.ts +19 -0
- package/dist/module-contracts.js +1 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +6 -3
- package/dist/operator-ui/view.d.ts +2 -18
- package/dist/operator-ui/view.js +3 -20
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +4 -6
- package/dist/routes/activity.js +1 -1
- package/dist/routes/credentials.js +5 -2
- package/dist/routes/mcp.js +13 -3
- package/dist/routes/oauth-management.d.ts +2 -0
- package/dist/routes/oauth-management.js +108 -0
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +21 -121
- package/dist/routes/shared.d.ts +23 -17
- package/dist/routes/shared.js +48 -44
- package/dist/routes/ui.js +36 -33
- package/dist/server.js +6 -26
- package/dist/types.d.ts +2 -0
- package/dist/ui.d.ts +15 -70
- package/dist/ui.js +176 -317
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +26 -17
- package/documentation/auth.md +61 -106
- package/documentation/cloudflare.md +1 -1
- package/documentation/code-mode.md +2 -2
- package/documentation/connectors.md +1 -1
- package/documentation/linear.md +1 -1
- package/documentation/meta-tools.md +6 -4
- package/documentation/mixpanel.md +1 -1
- package/documentation/notion.md +2 -2
- package/documentation/operations.md +14 -14
- package/documentation/operator-ui.md +82 -104
- package/documentation/optional-modules-upgrade.md +243 -0
- package/documentation/provider-conventions.md +5 -3
- package/documentation/revenuecat.md +1 -1
- package/documentation/storage-and-credentials.md +59 -40
- package/documentation/stripe.md +1 -1
- package/documentation/upgrading.md +33 -4
- package/ethos.md +22 -30
- package/examples/worker/AGENTS.md +3 -1
- package/examples/worker/README.md +68 -84
- package/examples/worker/src/d1-activity.ts +1 -1
- package/examples/worker/src/index.ts +11 -6
- package/package.json +17 -1
- package/templates/node/AGENTS.md +8 -6
- package/templates/node/README.md +56 -67
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +1 -1
- package/templates/node/src/index.ts +11 -12
- package/dist/access-tokens.d.ts +0 -31
- package/dist/access-tokens.js +0 -236
- package/dist/routes/access-tokens.d.ts +0 -6
- package/dist/routes/access-tokens.js +0 -83
|
@@ -4,109 +4,87 @@ The browser surface a human uses to see what a deployment exposes and to manage
|
|
|
4
4
|
the authentication material behind it. It is a small Preact app compiled by the
|
|
5
5
|
repository's own esbuild step and inlined into a data-free server shell.
|
|
6
6
|
|
|
7
|
-
Read [`ethos.md`](../ethos.md) first.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
the one on screen. `resetIdentity` replaces all identity-scoped state at once
|
|
89
|
-
and bumps a generation that work already in flight compares itself against.
|
|
90
|
-
- **Escaping is structural.** Components return elements; nothing builds HTML
|
|
91
|
-
from strings. A value that could be a URL passes `safeHttpHref` before it may
|
|
92
|
-
become an `href`, mirroring the server-side gate in `src/ui.ts`.
|
|
93
|
-
- **Secrets are shown once.** A created access token lives in state only, and
|
|
94
|
-
leaving the page — by navigation or by `pagehide`, which covers the
|
|
95
|
-
back-forward cache — unmounts it.
|
|
96
|
-
- **Every flow has four states.** Loading, error, empty, and success, with no
|
|
97
|
-
dead end: a failed save keeps the form and its typed value, a failed list
|
|
98
|
-
offers a retry, and an empty collection says what would fill it. A mutation
|
|
99
|
-
that fails is still a resolved promise — `mutate` lands the failure in state
|
|
100
|
-
rather than rejecting — so a caller that clears a form must clear it on a
|
|
101
|
-
confirmed success, never on resolution. `createAccessToken` returns that
|
|
102
|
-
answer as a boolean for exactly this reason.
|
|
103
|
-
- **Drift is counts, and absence is its own answer.** The connector card reads
|
|
104
|
-
`catalogDrift` ([#343](https://github.com/zackbart/connecta/issues/343)) as
|
|
105
|
-
four category counts and a timestamp. There is no drill-down, because a tool
|
|
106
|
-
name or a schema here would make an operator page the payload surface the
|
|
107
|
-
drift model refuses to be. A connector with no report renders as *not
|
|
108
|
-
observed*, never as clean: this runtime having seen no refresh is not the
|
|
109
|
-
same claim as a refresh having found nothing.
|
|
7
|
+
Read [`ethos.md`](../ethos.md) first. Code declares capabilities and access;
|
|
8
|
+
the UI displays the current user's effective permissions and manages only
|
|
9
|
+
authentication material explicitly permitted by that code. It never edits the
|
|
10
|
+
connector set, tool catalog, annotations, OAuth scopes, or permission rules.
|
|
11
|
+
|
|
12
|
+
## Enable the UI
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { operatorUi } from "@zackbart/connecta/ui";
|
|
16
|
+
|
|
17
|
+
createConnecta({
|
|
18
|
+
connectors,
|
|
19
|
+
executor,
|
|
20
|
+
auth,
|
|
21
|
+
ui: operatorUi({ branding: { productName: "Team connections" } }),
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The UI module owns its browser bundle and routes. Omit `ui` to omit those
|
|
26
|
+
routes and runtime imports. OAuth callbacks remain in core; authorized
|
|
27
|
+
interactive MCP callers can complete consent without the UI. Branding belongs
|
|
28
|
+
to `operatorUi` options, with neutral callback branding when no UI is mounted.
|
|
29
|
+
|
|
30
|
+
## Connections and activity
|
|
31
|
+
|
|
32
|
+
Connections is the main page. Each connection combines its status, effective
|
|
33
|
+
permissions, credential metadata, and permitted OAuth or credential actions.
|
|
34
|
+
There is no separate Credentials or Tokens tab. A user may see and invoke a
|
|
35
|
+
shared connector without permission to replace the grant everyone uses.
|
|
36
|
+
`identity.credentialAdministration` and `identity.personalConnection` select
|
|
37
|
+
shared and personal management rights, and both default to none.
|
|
38
|
+
|
|
39
|
+
Activity appears only when the optional history module has a readable store
|
|
40
|
+
and the caller passes `identity.activityAccess` and any additional read gate.
|
|
41
|
+
It is a global history, so permission to use one connector does not imply
|
|
42
|
+
permission to inspect that history. There is no member roster or policy editor.
|
|
43
|
+
|
|
44
|
+
The Node and Worker deployment READMEs show how to enable the modules and grant
|
|
45
|
+
the intended identities access. The configured bearer in the Node template can
|
|
46
|
+
read connection status but never mutate credentials as an interactive human.
|
|
47
|
+
|
|
48
|
+
## Loading and request lifetime
|
|
49
|
+
|
|
50
|
+
The server shell contains no connector or credential data. Authenticated
|
|
51
|
+
`/ui/data` returns the configured visible connection list without waiting for
|
|
52
|
+
provider status or tool discovery. Details load through `GET /ui/connectors/<id>`, independently,
|
|
53
|
+
under a bounded request lifetime. Unknown and loading states stay explicit;
|
|
54
|
+
a provider failure leaves the other connections usable.
|
|
55
|
+
|
|
56
|
+
A status read does not start OAuth or create authorization handoffs. Connect is
|
|
57
|
+
an explicit authorized POST. Successful save, reconnect, and disconnect actions
|
|
58
|
+
show their result without waiting for an unrelated full-catalog reload. Server
|
|
59
|
+
mutations still await catalog invalidation before replying, so another request
|
|
60
|
+
cannot consume a persisted catalog from before a credential change.
|
|
61
|
+
|
|
62
|
+
Each details request owns and closes its downstream connector scope. Never
|
|
63
|
+
cache a transport, request signal, or awaited promise in the UI module.
|
|
64
|
+
|
|
65
|
+
## Browser identity and security
|
|
66
|
+
|
|
67
|
+
Cloudflare Access is ambient browser auth. When the Worker invocation has
|
|
68
|
+
`ctx.access`, the shell emits no Clerk loader or browser-readable token.
|
|
69
|
+
Same-origin fetch carries the HttpOnly Access cookie, and the server uses the
|
|
70
|
+
trusted runtime identity. Sign out navigates to `/cdn-cgi/access/logout`.
|
|
71
|
+
Clerk deployments use their configured interactive provider.
|
|
72
|
+
|
|
73
|
+
Mutation requires exact same-origin `Origin`, an interactive identity,
|
|
74
|
+
connector visibility, and the relevant management permission. Personal actions
|
|
75
|
+
resolve only to the current principal's partition. Credential reads return
|
|
76
|
+
metadata, never saved values or masked fragments. Mutation cannot change any
|
|
77
|
+
declared capability. `test/operator-boundary.test.ts` checks that boundary.
|
|
78
|
+
|
|
79
|
+
The browser store fences responses by identity generation. Switching identity
|
|
80
|
+
clears the prior identity's state and discards its outstanding responses.
|
|
81
|
+
Components render elements, not HTML strings; links pass the shared URL gate.
|
|
82
|
+
Loading, failure, empty, and success states must all provide a useful next
|
|
83
|
+
step. A failed mutation preserves form input and does not masquerade as success.
|
|
84
|
+
|
|
85
|
+
Catalog drift remains counts and a timestamp. A missing observation means
|
|
86
|
+
"not observed", not that the downstream catalog is unchanged. The UI does not
|
|
87
|
+
expose tool schemas or raw payloads as diagnostics.
|
|
110
88
|
|
|
111
89
|
## Working on it
|
|
112
90
|
|
|
@@ -127,7 +105,7 @@ well as Node and there is no DOM in either:
|
|
|
127
105
|
DOM-lib program (`tsconfig.operator-ui.json`) because it imports the store.
|
|
128
106
|
- `test/browser/operator-ui.spec.ts` — the wiring, in a real browser:
|
|
129
107
|
Clerk loader order across its version redirect and a real load failure, plus
|
|
130
|
-
credential
|
|
108
|
+
credential and OAuth flows end to end, including their failure and
|
|
131
109
|
empty states. Run it with `npm run test:browser`
|
|
132
110
|
(`npm run test:browser:install` once, for Chromium). It is not part of
|
|
133
111
|
`npm run check`.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Upgrade to 0.24.0 optional modules
|
|
2
|
+
|
|
3
|
+
This is a breaking configuration change. Core still owns the same seven tools,
|
|
4
|
+
connector discovery, execution, invocation, and enforcement. UI, encrypted
|
|
5
|
+
credential storage, activity history, and configured bearer authentication now
|
|
6
|
+
have explicit imports. Select features when constructing the deployment;
|
|
7
|
+
there is no runtime plugin installation or registration.
|
|
8
|
+
|
|
9
|
+
Do this migration in the deployment repository on a branch. Do not re-run
|
|
10
|
+
`connecta init`, replace the connector set, or copy a template over local code.
|
|
11
|
+
The examples below omit unchanged connectors, executors, and storage adapters.
|
|
12
|
+
Install and pin `@zackbart/connecta@0.24.0` after completing the migration.
|
|
13
|
+
|
|
14
|
+
### Configuration and import changes
|
|
15
|
+
|
|
16
|
+
| Before | After |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| UI implicitly available | Import `operatorUi` from `@zackbart/connecta/ui` and set `ui: operatorUi()` |
|
|
19
|
+
| Root `branding` | `ui: operatorUi({ branding })` |
|
|
20
|
+
| `credentials: { encryptionKey }` | Import `encryptedCredentialVault` from `@zackbart/connecta/credentials`; set `vault: encryptedCredentialVault(storage, encryptionKey)` |
|
|
21
|
+
| `activity: { store, deploymentId, readGate }` | Import `activityHistory` from `@zackbart/connecta/activity`; set `activity: activityHistory({ store, deploymentId, readGate })` |
|
|
22
|
+
| Root `bearerToken` import | Import from `@zackbart/connecta/auth/bearer`; preserve its token and subject configuration |
|
|
23
|
+
| `accessTokens: { ... }` | Remove after migrating every client that uses a Connecta-issued token |
|
|
24
|
+
| `identity.operatorAccess` | `identity.activityAccess` for global activity reads; grant auth management separately |
|
|
25
|
+
| Visibility implicitly permits auth changes | Explicit `credentialAdministration` for shared auth and `personalConnection` for personal auth; both default to `"none"` |
|
|
26
|
+
|
|
27
|
+
Removed configuration is refused at construction. Do not silence TypeScript
|
|
28
|
+
errors with casts; they identify decisions this upgrade needs you to make.
|
|
29
|
+
|
|
30
|
+
Before:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createConnecta, bearerToken } from "@zackbart/connecta";
|
|
34
|
+
|
|
35
|
+
const connecta = createConnecta({
|
|
36
|
+
connectors,
|
|
37
|
+
executor,
|
|
38
|
+
storage,
|
|
39
|
+
auth: [bearerToken(token), interactiveAuth],
|
|
40
|
+
branding,
|
|
41
|
+
credentials: { encryptionKey },
|
|
42
|
+
accessTokens: {},
|
|
43
|
+
activity: { store: activityStore, deploymentId: "production" },
|
|
44
|
+
identity: {
|
|
45
|
+
connectorAccess,
|
|
46
|
+
operatorAccess: isOwner,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
After, with the same features except issued tokens:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createConnecta } from "@zackbart/connecta";
|
|
55
|
+
import { bearerToken } from "@zackbart/connecta/auth/bearer";
|
|
56
|
+
import { operatorUi } from "@zackbart/connecta/ui";
|
|
57
|
+
import { encryptedCredentialVault } from "@zackbart/connecta/credentials";
|
|
58
|
+
import { activityHistory } from "@zackbart/connecta/activity";
|
|
59
|
+
|
|
60
|
+
const connecta = createConnecta({
|
|
61
|
+
connectors,
|
|
62
|
+
executor,
|
|
63
|
+
storage,
|
|
64
|
+
auth: [bearerToken(token), interactiveAuth],
|
|
65
|
+
ui: operatorUi({ branding }),
|
|
66
|
+
vault: encryptedCredentialVault(storage, encryptionKey),
|
|
67
|
+
activity: activityHistory({ store: activityStore, deploymentId: "production" }),
|
|
68
|
+
identity: {
|
|
69
|
+
connectorAccess,
|
|
70
|
+
credentialAdministration: ({ principal }) =>
|
|
71
|
+
principal && isOwner(principal) ? "all" : "none",
|
|
72
|
+
personalConnection: () => "all",
|
|
73
|
+
activityAccess: isOwner,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`personalConnection: () => "all"` permits visible personal connections for
|
|
79
|
+
interactive principals. It does not permit shared grant changes or create a
|
|
80
|
+
principal for bearer or service identities. Use an explicit list of connector
|
|
81
|
+
ids if only some personal connections should be connectable.
|
|
82
|
+
|
|
83
|
+
### Decide permissions explicitly
|
|
84
|
+
|
|
85
|
+
A teammate who can use a shared connector should not automatically be able to
|
|
86
|
+
replace the credentials everybody uses. Preserve the existing
|
|
87
|
+
`connectorAccess` resolver, then add the two management resolvers. They return
|
|
88
|
+
`"all"`, `"none"`, or declared connector ids and intersect with visibility.
|
|
89
|
+
Thrown resolvers and unknown ids fail closed. Literal deployment-provided
|
|
90
|
+
secrets remain configuration; UI permissions do not make them editable.
|
|
91
|
+
|
|
92
|
+
For a team Worker using Cloudflare Access:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { cloudflareAccessAuth } from "@zackbart/connecta/auth/cloudflare-access";
|
|
96
|
+
|
|
97
|
+
import type { ConnectaConfig } from "@zackbart/connecta";
|
|
98
|
+
|
|
99
|
+
const identity: NonNullable<ConnectaConfig["identity"]> = {
|
|
100
|
+
connectorAccess: ({ principal }) =>
|
|
101
|
+
principal?.id === "owner-access-id" ? "all" : ["shared_docs", "personal_linear"],
|
|
102
|
+
credentialAdministration: ({ principal }) =>
|
|
103
|
+
principal?.id === "owner-access-id" ? "all" : "none",
|
|
104
|
+
personalConnection: () => ["personal_linear"],
|
|
105
|
+
activityAccess: ({ id }) => id === "owner-access-id",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
createConnecta({
|
|
109
|
+
connectors, executor, storage,
|
|
110
|
+
auth: cloudflareAccessAuth(),
|
|
111
|
+
identity,
|
|
112
|
+
ui: operatorUi(),
|
|
113
|
+
vault: encryptedCredentialVault(storage, encryptionKey),
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use ids declared in this deployment and the real identity provider's stable
|
|
118
|
+
principal id. The strings above are illustrative, not a BePresent access
|
|
119
|
+
policy. Retain the Worker `ctx` forwarding and Worker-level Access application
|
|
120
|
+
with Managed OAuth. Access owns admission and identity; these resolvers own
|
|
121
|
+
Connecta permissions. Do not introduce a second user roster.
|
|
122
|
+
|
|
123
|
+
For a personal Node deployment with Clerk and a configured client bearer:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import type { AuthenticatedIdentity } from "@zackbart/connecta";
|
|
127
|
+
|
|
128
|
+
const isOwner = ({ principal }: AuthenticatedIdentity) =>
|
|
129
|
+
principal?.id === "owner-clerk-id";
|
|
130
|
+
|
|
131
|
+
createConnecta({
|
|
132
|
+
connectors, executor, storage,
|
|
133
|
+
auth: [bearerToken(token, { subjectId: "static-token" }), clerk],
|
|
134
|
+
ui: operatorUi({ branding }),
|
|
135
|
+
vault: encryptedCredentialVault(storage, encryptionKey),
|
|
136
|
+
identity: {
|
|
137
|
+
connectorAccess: () => "all",
|
|
138
|
+
credentialAdministration: identity => isOwner(identity) ? "all" : "none",
|
|
139
|
+
personalConnection: identity => isOwner(identity) ? "all" : "none",
|
|
140
|
+
activityAccess: ({ id }) => id === "owner-clerk-id",
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This preserves shared-connector MCP access for the configured bearer while
|
|
146
|
+
reserving auth changes for the owner signing in through Clerk. The bearer does
|
|
147
|
+
not inherit the owner's personal connections. To make invocation owner-only
|
|
148
|
+
as well, restrict inbound admission or `connectorAccess` explicitly. Do not
|
|
149
|
+
replace an existing identity resolver with `"all"` just to match this example.
|
|
150
|
+
|
|
151
|
+
`activityAccess` receives an `IdentityReference` with `id` and `namespace`,
|
|
152
|
+
while management resolvers receive an `AuthenticatedIdentity` with `principal`.
|
|
153
|
+
`activityAccess` defaults to interactive humans. Set it deliberately for a team
|
|
154
|
+
if global activity must remain owner-only. Omitting it is broader than an old
|
|
155
|
+
owner-only `operatorAccess` resolver.
|
|
156
|
+
|
|
157
|
+
### Migrate issued-token clients before upgrading
|
|
158
|
+
|
|
159
|
+
Connecta no longer issues or authenticates `cta_` tokens. The Tokens tab and
|
|
160
|
+
its creation, rename, and revocation endpoints are removed. Existing token
|
|
161
|
+
secrets immediately stop admitting requests to the upgraded deployment;
|
|
162
|
+
removing `accessTokens` alone does not migrate clients.
|
|
163
|
+
|
|
164
|
+
1. Inventory clients using issued tokens while the old deployment can still
|
|
165
|
+
identify them. Do not paste secrets into logs, documentation, or a migration
|
|
166
|
+
issue.
|
|
167
|
+
2. Move interactive clients to the configured identity provider's OAuth flow.
|
|
168
|
+
On Workers, use Cloudflare Access Managed OAuth and keep the required client
|
|
169
|
+
redirect allowlist. On Node, use the configured interactive provider.
|
|
170
|
+
3. If an actual unattended Node client remains, configure the optional
|
|
171
|
+
`bearerToken` adapter and update that client. Preserve stable `subjectId`
|
|
172
|
+
values for existing configured bearers. On Workers, Access service tokens
|
|
173
|
+
provide edge admission; a Connecta bearer alone cannot cross Access.
|
|
174
|
+
4. Verify each replacement client before removing `accessTokens` and deploying
|
|
175
|
+
the new package. An issued token previously inherited its creator's personal
|
|
176
|
+
principal; a configured bearer does not. Clients needing personal grants
|
|
177
|
+
must use a human identity flow.
|
|
178
|
+
|
|
179
|
+
Stored issued-token records become inert. They are not deleted automatically
|
|
180
|
+
and are not converted into configured bearer credentials. No storage sweep is
|
|
181
|
+
required for this upgrade. If rolling back to an older binary, remember that it
|
|
182
|
+
understands those old records again; retire or revoke old tokens in the old
|
|
183
|
+
system before migration if rollback must not restore them.
|
|
184
|
+
|
|
185
|
+
### Preserve vault and OAuth state
|
|
186
|
+
|
|
187
|
+
Pass the existing storage adapter and the same encryption key to
|
|
188
|
+
`encryptedCredentialVault`. Do not generate a replacement key as part of this
|
|
189
|
+
refactor. Keep connector ids, identity namespaces, principal ids, and owner
|
|
190
|
+
partitions stable. The encrypted vault binds connector and owner into its
|
|
191
|
+
AES-GCM context; moving records between owners does not migrate ownership.
|
|
192
|
+
|
|
193
|
+
No vault record, OAuth grant, or catalog storage-format migration is required.
|
|
194
|
+
Downstream OAuth continues to use connector storage, independently of the vault
|
|
195
|
+
module. Existing activity stores and retention policies remain deployment-owned;
|
|
196
|
+
wrap the existing store in `activityHistory` rather than creating a new database.
|
|
197
|
+
A replacement `CredentialVault` must preserve connector and owner isolation.
|
|
198
|
+
|
|
199
|
+
### Omit features you do not need
|
|
200
|
+
|
|
201
|
+
Omit `ui` and its import for an API-only deployment. UI shells, data APIs, and
|
|
202
|
+
auth mutation routes disappear. Core `/mcp`, `/health`, auth metadata, and OAuth
|
|
203
|
+
callbacks remain. An authorized interactive MCP caller can use
|
|
204
|
+
`authorize_connector` and complete OAuth without UI. Static credential recovery
|
|
205
|
+
returns `unavailable` without a mounted UI or vault, instead of a missing page.
|
|
206
|
+
UI-free callbacks use neutral branding and offer no dead return link.
|
|
207
|
+
|
|
208
|
+
Omit `activity` and its imports to stop recording and remove the Activity tab.
|
|
209
|
+
Remove deployment-only store wiring or database bindings only if nothing else
|
|
210
|
+
uses them. Existing history is not deleted. Diagnostic output is separate;
|
|
211
|
+
set `logger: "silent"` only when you also want to suppress it.
|
|
212
|
+
|
|
213
|
+
Omit `vault` when credentials come entirely from deployment configuration or
|
|
214
|
+
OAuth. A connector declaring a vault slot then remains unmanageable; do not
|
|
215
|
+
omit it while expecting stored vault credentials to keep resolving.
|
|
216
|
+
|
|
217
|
+
There is no generic module list. Keep ordinary explicit imports and the typed
|
|
218
|
+
configuration slots. The implementations may ship in the package, but core
|
|
219
|
+
does not load them merely because the package is installed.
|
|
220
|
+
|
|
221
|
+
### Verify the upgraded deployment
|
|
222
|
+
|
|
223
|
+
- Run the deployment's typecheck and build with the new imports and exact pin.
|
|
224
|
+
Keep its connector declarations, executor, and custom routing intact.
|
|
225
|
+
- Run `connecta doctor` against the running deployment using its actual inbound
|
|
226
|
+
authentication. Confirm the same seven tools and a working executor.
|
|
227
|
+
- Sign in as an owner and a teammate. Confirm visibility, shared auth controls,
|
|
228
|
+
personal connection controls, and activity access match code. A member may
|
|
229
|
+
invoke a shared connector while having no authority to change its grant.
|
|
230
|
+
- Open Connections. Its configured list should appear before downstream details;
|
|
231
|
+
one slow or failing provider should not delay the other cards. OAuth starts
|
|
232
|
+
only after an explicit action, and action feedback does not await other probes.
|
|
233
|
+
- Read existing vault-backed and personal OAuth connections without re-entering
|
|
234
|
+
secrets. Exercise one permitted auth flow and confirm other principals cannot
|
|
235
|
+
manage it. With UI omitted, verify OAuth callback completion and the honest
|
|
236
|
+
unavailable response for static credential handoff.
|
|
237
|
+
- Update bookmarks and operator instructions to the connection-centered UI.
|
|
238
|
+
There is no separate Credentials or Tokens tab. Verify optional Activity and
|
|
239
|
+
the absence of UI routes in a deployment that omits the UI.
|
|
240
|
+
|
|
241
|
+
Keep the old pin, configuration, and storage backup available until these checks
|
|
242
|
+
pass. This guide changes package configuration; it does not authorize or perform
|
|
243
|
+
any deployment, credential rotation, or storage deletion.
|
|
@@ -416,13 +416,15 @@ default. The provider's own headless credential — a personal API key, a
|
|
|
416
416
|
restricted key, a service account — is supported two ways: explicit `headers`
|
|
417
417
|
auth, documented as a secret rather than configuration, and `{ type:
|
|
418
418
|
"credential" }`, which declares an operator slot and takes the same secret from
|
|
419
|
-
|
|
419
|
+
the connection UI at `/` instead. Either way it is paired with the narrowest mode the
|
|
420
420
|
deployment can use, and the framing matches the provider's *published* contract
|
|
421
421
|
for the MCP endpoint — not a convention borrowed from that provider's other
|
|
422
422
|
APIs, and not this repository's earlier example, which is the same claim wearing
|
|
423
423
|
a circle. `requireHttps` is set. Recovery from an expired authorization is the
|
|
424
424
|
ordinary `auth_required` → `authorize_connector` route, which returns the
|
|
425
|
-
consent URL for OAuth and
|
|
425
|
+
consent URL for permitted OAuth starts and a connection UI handoff at `/` for
|
|
426
|
+
a declared slot when UI and vault are mounted. Missing modules yield
|
|
427
|
+
`unavailable`; never advertise a route the deployment cannot serve.
|
|
426
428
|
|
|
427
429
|
*Why:* one route back from an expired credential is what keeps a failed call
|
|
428
430
|
from becoming an abandoned task. *Cost:* wrong-tool selection.
|
|
@@ -442,7 +444,7 @@ deployment file to read — and a dead, revoked, or absent credential fails loud
|
|
|
442
444
|
at use as `auth_required` with the `authorize_connector` route attached (P9).
|
|
443
445
|
|
|
444
446
|
`testCredential` exists only behind the operator-pressed Test action on
|
|
445
|
-
|
|
447
|
+
the connection UI at `/`, and only for a declared slot. It connects with the stored value
|
|
446
448
|
and reports how many tools the downstream served, which is the whole honest
|
|
447
449
|
check for a proxy: which account, project, or mode a key reaches is the
|
|
448
450
|
provider's answer, not Connecta's. That is not the shape
|
|
@@ -81,7 +81,7 @@ connectors: [
|
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
Neither key has to be a runtime secret. Declare the slot instead and each
|
|
84
|
-
connector's key is pasted, tested, and rotated on
|
|
84
|
+
connector's key is pasted, tested, and rotated on the connection UI at `/`:
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
connectors: [
|
|
@@ -1,39 +1,59 @@
|
|
|
1
1
|
# Storage and credentials
|
|
2
2
|
|
|
3
|
-
The core `KVStorage`
|
|
4
|
-
implement `list(prefix)`.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
3
|
+
The core `KVStorage` contract supports `get`, `set`, and `delete`; adapters may
|
|
4
|
+
also implement `list(prefix)`. Core uses storage for connector state, catalogs,
|
|
5
|
+
and result paging. The vault is an explicit independent option:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { encryptedCredentialVault } from "@zackbart/connecta/credentials";
|
|
9
|
+
|
|
10
|
+
createConnecta({
|
|
11
|
+
connectors,
|
|
12
|
+
executor,
|
|
13
|
+
storage,
|
|
14
|
+
vault: encryptedCredentialVault(storage, encryptionKey),
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`encryptionKey` is a base64 32-byte AES key. The factory implements the
|
|
19
|
+
root-exported `CredentialVault` contract. Core depends on the contract without
|
|
20
|
+
importing the encrypted implementation; deployments may supply their own.
|
|
21
|
+
A replacement must isolate both connector id and optional owner, preserve
|
|
22
|
+
metadata-only reads for the UI, and keep plaintext confined to host credential
|
|
23
|
+
resolution. The built-in AES-GCM implementation binds connector and owner into
|
|
24
|
+
authenticated encryption context. Sharing a backend is not permission to share
|
|
25
|
+
a principal's credentials.
|
|
26
|
+
|
|
27
|
+
Connectors declare a human-managed `credential` slot in code. The vault exposes
|
|
28
|
+
read access only through that connector's `ctx.credential`. Values, masked
|
|
29
|
+
values, call arguments, and raw errors never enter model recovery or activity.
|
|
30
|
+
Omitting the vault leaves literal deployment-provided secrets and downstream
|
|
31
|
+
OAuth available, but declared vault slots cannot be managed.
|
|
32
|
+
|
|
33
|
+
Credentials fail at use. Proactive liveness probing remains removed by
|
|
34
|
+
[the 0.9 decision](../ethos.md). Operator-triggered tests and local credential
|
|
35
|
+
shape checks remain. `authorize_connector` returns a human credential handoff
|
|
36
|
+
only when both a vault and the UI are configured. Without either, recovery is
|
|
37
|
+
`unavailable` and explains the missing configuration.
|
|
38
|
+
|
|
39
|
+
A static bearer may invoke a connector but never mutate its auth as a browser
|
|
40
|
+
user. Interactive users need visibility plus `credentialAdministration` for
|
|
41
|
+
shared auth or `personalConnection` for their own personal auth. Both management
|
|
42
|
+
permissions default to none. Saving, testing, replacing, or removing a value
|
|
43
|
+
never returns it. The vault is read for each call, so a saved replacement takes
|
|
44
|
+
effect without restarting the deployment.
|
|
45
|
+
|
|
46
|
+
## Storage continuity
|
|
47
|
+
|
|
48
|
+
This module extraction changes no encrypted record keys, owner partitions, or
|
|
49
|
+
OAuth storage format. Reuse the existing storage and encryption key when
|
|
50
|
+
constructing `encryptedCredentialVault`. Keep connector ids, principal ids, and
|
|
51
|
+
identity namespaces unchanged. Losing the encryption key makes old vault
|
|
52
|
+
values unreadable; changing identity context selects another personal partition.
|
|
53
|
+
|
|
54
|
+
Removed Connecta-issued token records remain inert in storage. This upgrade
|
|
55
|
+
does not delete them, convert them to another credential, or migrate their
|
|
56
|
+
clients automatically.
|
|
37
57
|
|
|
38
58
|
## Shared and personal auth
|
|
39
59
|
|
|
@@ -54,8 +74,7 @@ Personal connectors disappear from a request that has no stable human
|
|
|
54
74
|
principal. For a principal that can see one, connecta partitions connector
|
|
55
75
|
storage, encrypted vault records, catalog caches, OAuth generations, and
|
|
56
76
|
observed result shapes under an opaque SHA-256 identity key. Results used by
|
|
57
|
-
`get_result` are partitioned by the authenticated subject, so one
|
|
58
|
-
page another token's call even when both tokens belong to the same principal.
|
|
77
|
+
`get_result` are partitioned by the authenticated subject, so one authenticated subject cannot page another subject's call.
|
|
59
78
|
|
|
60
79
|
Literal `auth: { type: "headers" }` cannot be personal because its secret lives
|
|
61
80
|
in deployment code. `remoteMcp()` refuses that combination at construction.
|
|
@@ -73,7 +92,7 @@ remoteMcp("revenuecat_bepresent", {
|
|
|
73
92
|
```
|
|
74
93
|
|
|
75
94
|
The connector, its endpoint, and the credential *slot* stay declared in code;
|
|
76
|
-
only the secret arrives through
|
|
95
|
+
only the secret arrives through the connection UI at `/`. That is the same boundary
|
|
77
96
|
`api()` has always had, and the reason a project-wide key no longer has to be a
|
|
78
97
|
Worker secret or an environment variable
|
|
79
98
|
([#439](https://github.com/zackbart/connecta/issues/439)).
|
|
@@ -99,8 +118,8 @@ redaction that keeps part of a secret is still a leak.
|
|
|
99
118
|
An empty slot is not a boot failure and not a silently absent connector. The
|
|
100
119
|
connector is present, its status reads `auth_required`, calls fail with the same
|
|
101
120
|
typed error a missing OAuth grant produces, and `authorize_connector` returns
|
|
102
|
-
|
|
103
|
-
`
|
|
121
|
+
a connection UI handoff at `/` when UI and vault are configured. Without either,
|
|
122
|
+
it returns `unavailable` and explains what is missing.
|
|
104
123
|
|
|
105
124
|
The vault is read before any cached downstream client is trusted, so a rotation
|
|
106
125
|
lands on the next call rather than the next deploy. Connecta compares a SHA-256
|
|
@@ -111,7 +130,7 @@ message. A cleartext `http://` destination warns at construction here exactly as
|
|
|
111
130
|
it does for literal headers — who owns the secret changed, not what the wire
|
|
112
131
|
carries.
|
|
113
132
|
|
|
114
|
-
|
|
133
|
+
The connection UI Test action connects with the stored value and reports how many
|
|
115
134
|
tools the downstream served. That is the whole honest check for a proxy: which
|
|
116
135
|
account, project, or mode the key reaches is the provider's answer, not
|
|
117
136
|
Connecta's.
|
package/documentation/stripe.md
CHANGED
|
@@ -95,7 +95,7 @@ Use a restricted key, not a secret key, and scope it to the operations the
|
|
|
95
95
|
agent actually needs; Stripe's own guidance is to "limit your agent's access to
|
|
96
96
|
exactly the functionality it requires". Keep it in the runtime's secret store.
|
|
97
97
|
|
|
98
|
-
The same key can come from
|
|
98
|
+
The same key can come from the connection UI at `/` instead:
|
|
99
99
|
|
|
100
100
|
```ts
|
|
101
101
|
stripe("stripe_sandbox", {
|