@zackbart/connecta 0.21.1 → 0.22.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 +83 -0
- package/README.md +7 -0
- package/dist/access-tokens.d.ts +2 -2
- package/dist/access-tokens.js +14 -2
- package/dist/auth/downstream-oauth.d.ts +65 -2
- package/dist/auth/downstream-oauth.js +408 -20
- package/dist/connectors/api.d.ts +2 -0
- package/dist/connectors/api.js +1 -0
- package/dist/connectors/remote-mcp.d.ts +2 -0
- package/dist/connectors/remote-mcp.js +14 -4
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +25 -21
- package/dist/executors/quickjs.js +4 -0
- package/dist/identity.d.ts +4 -0
- package/dist/identity.js +17 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +6 -1
- package/dist/meta-tools.js +7 -2
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +4 -2
- package/dist/operator-ui/view.js +1 -1
- package/dist/providers/cloudflare.d.ts +2 -0
- package/dist/providers/cloudflare.js +1 -0
- package/dist/providers/linear.d.ts +2 -0
- package/dist/providers/linear.js +1 -0
- package/dist/providers/mixpanel.d.ts +2 -0
- package/dist/providers/mixpanel.js +1 -0
- package/dist/providers/notion.d.ts +2 -0
- package/dist/providers/notion.js +1 -0
- package/dist/providers/revenuecat.d.ts +2 -0
- package/dist/providers/revenuecat.js +1 -0
- package/dist/providers/stripe.d.ts +2 -0
- package/dist/providers/stripe.js +1 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.js +200 -4
- package/dist/routes/access-tokens.js +2 -2
- package/dist/routes/activity.js +4 -1
- package/dist/routes/credentials.js +31 -12
- package/dist/routes/mcp.js +17 -2
- package/dist/routes/oauth.js +55 -11
- package/dist/routes/shared.d.ts +20 -4
- package/dist/routes/shared.js +92 -24
- package/dist/routes/ui.js +32 -13
- package/dist/types.d.ts +28 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +18 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +31 -8
- package/documentation/auth.md +90 -10
- package/documentation/code-mode.md +4 -4
- package/documentation/connectors.md +13 -0
- package/documentation/meta-tools.md +4 -3
- package/documentation/operations.md +5 -3
- package/documentation/operator-ui.md +13 -4
- package/documentation/request-admission.md +2 -1
- package/documentation/storage-and-credentials.md +77 -4
- package/documentation/upgrading.md +38 -7
- package/ethos.md +8 -8
- package/examples/worker/AGENTS.md +44 -0
- package/examples/worker/README.md +63 -14
- package/examples/worker/src/index.ts +26 -22
- package/package.json +1 -1
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.22.0 — 2026-08-31
|
|
6
|
+
|
|
7
|
+
This release lets one deployment serve several authenticated people without
|
|
8
|
+
becoming an account system. Connector visibility comes from deployment config,
|
|
9
|
+
and downstream auth may be shared across the tenant or isolated per human.
|
|
10
|
+
Existing connectors stay shared, every visible connector stays visible, and
|
|
11
|
+
every interactive human stays an operator unless the deployment opts into the
|
|
12
|
+
new identity rules. A signed-in human may manage authentication for every
|
|
13
|
+
connector their code-derived view includes; operator status separately controls
|
|
14
|
+
tokens and global activity. Worker deployments also gain the complete Managed
|
|
15
|
+
OAuth callback allowlist in the shipped example and agent instructions. The
|
|
16
|
+
one upgrade seam is deliberate: in-flight `get_result` ids created by a named
|
|
17
|
+
caller do not cross into the new subject partition, so finish paging them
|
|
18
|
+
before deployment. Single-user deployments need no identity configuration,
|
|
19
|
+
and non-Worker deployments can ignore the callback-policy change.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Identity-derived connector views.** `identity.connectorAccess` selects
|
|
24
|
+
declared connector ids from the authenticated actor, subject, and principal.
|
|
25
|
+
The view reaches discovery, direct and program calls, status, catalogs,
|
|
26
|
+
observed output shapes, and paged results. Unknown ids and resolver failures
|
|
27
|
+
fail closed.
|
|
28
|
+
- **Shared and personal connector auth.** `authScope: "personal"` partitions
|
|
29
|
+
connector storage, encrypted credentials, OAuth state and tokens, catalogs,
|
|
30
|
+
and runtime observations by a hashed principal identity. Interactive members
|
|
31
|
+
manage auth for every visible connector: their own partition for personal
|
|
32
|
+
auth, or the deployment-wide grant for shared auth. `identity.operatorAccess`
|
|
33
|
+
reserves deployment access tokens and global activity for configured
|
|
34
|
+
operators.
|
|
35
|
+
- **Principal-bound access tokens and OAuth callbacks.** A new connecta access
|
|
36
|
+
token retains its creator's principal without gaining operator rights.
|
|
37
|
+
Personal OAuth handoffs bind a hash of state to the initiating principal for
|
|
38
|
+
15 minutes before the public callback can exchange a code.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **Result pages follow authenticated subjects.** `get_result` storage is now
|
|
43
|
+
partitioned for every namespaced subject, including existing Clerk, Access,
|
|
44
|
+
and connecta-token callers. In-flight result ids created before upgrading do
|
|
45
|
+
not cross that storage boundary; finish paging them before deployment when
|
|
46
|
+
that matters.
|
|
47
|
+
- **Worker Managed OAuth setup.** The Worker guide, source comment, upgrade
|
|
48
|
+
guide, and local `AGENTS.md` require Claude's fixed callback plus ChatGPT's
|
|
49
|
+
stable and callback-id forms in
|
|
50
|
+
`dynamic_client_registration.allowed_uris`.
|
|
51
|
+
- **One tenant, several people.** The ethos now refuses a connecta-owned account
|
|
52
|
+
model while allowing externally authenticated principals, config-derived
|
|
53
|
+
connector visibility, and personal downstream credentials inside one tenant.
|
|
54
|
+
- **Reviewed Cloudflare deletion contract.** Cloudflare removed a spurious
|
|
55
|
+
required empty request body from Worker script deletion. Connecta already
|
|
56
|
+
sent no body, so only the reviewed endpoint digest changes (#517).
|
|
57
|
+
|
|
58
|
+
## 0.21.2 — 2026-08-31
|
|
59
|
+
|
|
60
|
+
This patch closes two runtime isolation gaps: concurrent request scopes now
|
|
61
|
+
share one downstream OAuth refresh inside a connector runtime, and QuickJS
|
|
62
|
+
children no longer inherit the deployment process environment. Existing
|
|
63
|
+
deployments need no configuration or storage migration. OAuth deployments get
|
|
64
|
+
the refresh fix automatically; Node deployments using QuickJS get the tighter
|
|
65
|
+
child boundary automatically. Deployments using neither path can ignore this
|
|
66
|
+
release.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **Empty QuickJS child environments.** The Node process hosting the QuickJS
|
|
71
|
+
guest runtime starts with an explicit empty environment, so deployment
|
|
72
|
+
credentials and Node startup configuration such as `NODE_OPTIONS` never
|
|
73
|
+
cross the child-process boundary (#515).
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **One rotating-token redemption per runtime.** Concurrent request scopes for
|
|
78
|
+
one OAuth connector generation share the owner's refresh result or bounded
|
|
79
|
+
failure instead of independently redeeming the same refresh token. The gate
|
|
80
|
+
remains request-safe across follower and owner cancellation, storage
|
|
81
|
+
failures, force reauthorization, and issuer-generation changes (#514).
|
|
82
|
+
- **Late and byte-identical refresh races.** Generation-scoped mutation and
|
|
83
|
+
success identities close token-save TOCTOU and complete-flight ABA windows,
|
|
84
|
+
including authorization servers that preserve the refresh token or return
|
|
85
|
+
byte-identical credentials. The guarantee is deliberately runtime-local;
|
|
86
|
+
`KVStorage` still has no cross-isolate compare-and-set primitive (#514).
|
|
87
|
+
|
|
5
88
|
## 0.21.1 — 2026-08-30
|
|
6
89
|
|
|
7
90
|
This patch aligns the handwritten Cloudflare and Notion contracts and the four
|
package/README.md
CHANGED
|
@@ -82,6 +82,13 @@ payload-free activity log. Worker deployments can use Cloudflare Access for
|
|
|
82
82
|
both MCP and operator identity; Node deployments and existing Workers can use
|
|
83
83
|
Clerk.
|
|
84
84
|
|
|
85
|
+
One deployment may serve several authenticated people inside the same tenant.
|
|
86
|
+
Configuration can derive connector visibility from the admitted identity, and
|
|
87
|
+
each connector may keep one shared downstream grant or a separate encrypted
|
|
88
|
+
grant per human. Connecta does not own accounts or groups; Clerk or Cloudflare
|
|
89
|
+
Access remains the identity provider. See [inbound auth](./documentation/auth.md#principals-visibility-and-operators)
|
|
90
|
+
and [shared and personal auth](./documentation/storage-and-credentials.md#shared-and-personal-auth).
|
|
91
|
+
|
|
85
92
|
Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
|
|
86
93
|
service. Those are decisions, and the [ethos](./ethos.md) records each one
|
|
87
94
|
and why.
|
package/dist/access-tokens.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InboundAuth, KVStorage } from "./types.js";
|
|
1
|
+
import type { IdentityReference, InboundAuth, KVStorage } from "./types.js";
|
|
2
2
|
export interface AccessTokenMetadata {
|
|
3
3
|
id: string;
|
|
4
4
|
name: string;
|
|
@@ -24,7 +24,7 @@ export declare class AccessTokenManager {
|
|
|
24
24
|
});
|
|
25
25
|
private read;
|
|
26
26
|
list(): Promise<AccessTokenMetadata[]>;
|
|
27
|
-
create(name: unknown, createdBy: string): Promise<CreatedAccessToken>;
|
|
27
|
+
create(name: unknown, createdBy: string | IdentityReference): Promise<CreatedAccessToken>;
|
|
28
28
|
rename(id: string, name: unknown): Promise<AccessTokenMetadata | null>;
|
|
29
29
|
revoke(id: string, revokedBy: string): Promise<AccessTokenMetadata | null>;
|
|
30
30
|
private authorize;
|
package/dist/access-tokens.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validIdentityReference } from "./identity.js";
|
|
1
2
|
const TOKEN_PREFIX = "cta_";
|
|
2
3
|
const TOKEN_BYTES = 32;
|
|
3
4
|
const TOKEN_VALUE_RE = /^cta_[A-Za-z0-9_-]{43}$/;
|
|
@@ -54,6 +55,8 @@ function parseRecord(raw) {
|
|
|
54
55
|
typeof value.tokenPrefix !== "string" ||
|
|
55
56
|
typeof value.createdAt !== "string" ||
|
|
56
57
|
typeof value.createdBy !== "string" ||
|
|
58
|
+
(value.principal !== undefined &&
|
|
59
|
+
!validIdentityReference(value.principal)) ||
|
|
57
60
|
(value.revokedAt !== undefined &&
|
|
58
61
|
typeof value.revokedAt !== "string") ||
|
|
59
62
|
(value.revokedBy !== undefined &&
|
|
@@ -167,7 +170,12 @@ export class AccessTokenManager {
|
|
|
167
170
|
tokenHash: hash,
|
|
168
171
|
tokenPrefix: token.slice(0, 12),
|
|
169
172
|
createdAt: new Date().toISOString(),
|
|
170
|
-
createdBy
|
|
173
|
+
createdBy: typeof createdBy === "string"
|
|
174
|
+
? createdBy
|
|
175
|
+
: `${createdBy.namespace}:${createdBy.id}`,
|
|
176
|
+
...(typeof createdBy === "string"
|
|
177
|
+
? {}
|
|
178
|
+
: { principal: { ...createdBy } }),
|
|
171
179
|
};
|
|
172
180
|
await this.storage.set(recordKey(record.id), JSON.stringify(record));
|
|
173
181
|
try {
|
|
@@ -219,6 +227,10 @@ export class AccessTokenManager {
|
|
|
219
227
|
if (!record || record.revokedAt || record.tokenHash !== hash) {
|
|
220
228
|
return unauthorized();
|
|
221
229
|
}
|
|
222
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
ok: true,
|
|
232
|
+
subjectId: record.id,
|
|
233
|
+
...(record.principal ? { principal: { ...record.principal } } : {}),
|
|
234
|
+
};
|
|
223
235
|
}
|
|
224
236
|
}
|
|
@@ -1,5 +1,55 @@
|
|
|
1
|
-
import type { OAuthClientInformationContext, OAuthClientInformationMixed, OAuthClientMetadata, OAuthClientProvider, OAuthTokens } from "@modelcontextprotocol/client";
|
|
1
|
+
import type { FetchLike, OAuthClientInformationContext, OAuthClientInformationMixed, OAuthClientMetadata, OAuthClientProvider, OAuthTokens } from "@modelcontextprotocol/client";
|
|
2
2
|
import type { KVStorage } from "../types.js";
|
|
3
|
+
type OAuthRefreshFlightOutcome = {
|
|
4
|
+
status: "refreshed";
|
|
5
|
+
} | {
|
|
6
|
+
status: "retired";
|
|
7
|
+
} | {
|
|
8
|
+
status: "failed";
|
|
9
|
+
error: unknown;
|
|
10
|
+
};
|
|
11
|
+
interface OAuthRefreshFlight {
|
|
12
|
+
done: Promise<OAuthRefreshFlightOutcome>;
|
|
13
|
+
release: (outcome: OAuthRefreshFlightOutcome) => void;
|
|
14
|
+
stopObservingOwnerAbort: () => void;
|
|
15
|
+
mutationId: object;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Share one rotating-token redemption within one connector runtime and OAuth
|
|
19
|
+
* generation. The first request still owns the real fetch and response. Its
|
|
20
|
+
* abort signal has one bounded listener until the exact flight settles, so a
|
|
21
|
+
* cancellation after the response cannot strand waiters during token storage.
|
|
22
|
+
* Followers wait for that provider to save tokens, then re-read storage. The
|
|
23
|
+
* map never retains a token response or transport.
|
|
24
|
+
*
|
|
25
|
+
* This is intentionally runtime-local. KVStorage has no atomic coordination
|
|
26
|
+
* operation, so a second isolate can still race the same refresh token.
|
|
27
|
+
*/
|
|
28
|
+
export declare class OAuthRefreshCoordinator {
|
|
29
|
+
private readonly flights;
|
|
30
|
+
/** Opaque identities only: no request promise, signal, callback, or response. */
|
|
31
|
+
private readonly pendingMutations;
|
|
32
|
+
/** One bounded latest-success slot, containing only generation + identity. */
|
|
33
|
+
private successfulRefresh;
|
|
34
|
+
/** Replaced on every map mutation, closing flight/pending ABA across awaits. */
|
|
35
|
+
private stateRevision;
|
|
36
|
+
private advanceStateRevision;
|
|
37
|
+
private observeAuthoritativeGeneration;
|
|
38
|
+
private settle;
|
|
39
|
+
private markMutationPending;
|
|
40
|
+
private finishMutation;
|
|
41
|
+
/** @internal Opaque basis for issuer-aware provider token reads. */
|
|
42
|
+
successfulRefreshIdentity(generation: string): object | undefined;
|
|
43
|
+
coordinatedFetch(provider: KvOAuthProvider, baseFetch: FetchLike, requestSignal?: AbortSignal): FetchLike;
|
|
44
|
+
/** Publish one exact owner's successful save without disturbing a newer try. */
|
|
45
|
+
succeedMutation(generation: string, flight: OAuthRefreshFlight): void;
|
|
46
|
+
/** Give joined callers a fetch/flow failure, without rejecting the gate. */
|
|
47
|
+
fail(generation: string, flight: OAuthRefreshFlight, error: unknown): void;
|
|
48
|
+
/** Finish an exact failed credential write, then publish its failure. */
|
|
49
|
+
failMutation(generation: string, flight: OAuthRefreshFlight, error: unknown): void;
|
|
50
|
+
/** Force reauthorization fences and wakes every waiter on the retired epoch. */
|
|
51
|
+
retire(generation: string): void;
|
|
52
|
+
}
|
|
3
53
|
/**
|
|
4
54
|
* Physical key for an OAuth value in one authorization epoch. Legacy values
|
|
5
55
|
* keep their historical names so upgrades can read an existing grant. Modern
|
|
@@ -20,19 +70,31 @@ export declare class KvOAuthProvider implements OAuthClientProvider {
|
|
|
20
70
|
private readonly connectorId;
|
|
21
71
|
private readonly storage;
|
|
22
72
|
private readonly redirectUri;
|
|
73
|
+
private readonly refreshCoordinator?;
|
|
23
74
|
/**
|
|
24
75
|
* The reset generation this provider's flow started under. Every OAuth value
|
|
25
76
|
* it writes carries this epoch, so a late write can land after a reset without
|
|
26
77
|
* becoming readable under the new generation.
|
|
27
78
|
*/
|
|
28
79
|
private capturedGeneration;
|
|
29
|
-
|
|
80
|
+
private refreshFlight;
|
|
81
|
+
/** Tokens this request's issuer-aware auth flow decided to refresh. */
|
|
82
|
+
private refreshBasis;
|
|
83
|
+
constructor(connectorId: string, storage: KVStorage, redirectUri: string, refreshCoordinator?: OAuthRefreshCoordinator | undefined);
|
|
30
84
|
/**
|
|
31
85
|
* Stamp the force-reauth generation the current connect flow started under.
|
|
32
86
|
* Called by the connector once per connect, before c.connect(). The callback
|
|
33
87
|
* path captures the generation stored beside its verified state instead.
|
|
34
88
|
*/
|
|
35
89
|
captureGeneration(gen: string): void;
|
|
90
|
+
/** The generation captured for this flow, before a concurrent reset. */
|
|
91
|
+
flowGeneration(): Promise<string>;
|
|
92
|
+
/** @internal Record the refresh attempt this provider owns. */
|
|
93
|
+
captureRefreshFlight(generation: string, flight: OAuthRefreshFlight): void;
|
|
94
|
+
private succeedRefreshFlight;
|
|
95
|
+
private failRefreshFlight;
|
|
96
|
+
/** True when another request saved a refresh result after this flow's read. */
|
|
97
|
+
refreshBasisChanged(current: OAuthTokens, generation: string): boolean;
|
|
36
98
|
/**
|
|
37
99
|
* The epoch this provider writes under. Direct unit/custom use lazily captures
|
|
38
100
|
* the current generation; connector-driven connect and callback paths stamp it
|
|
@@ -121,3 +183,4 @@ export declare class KvOAuthProvider implements OAuthClientProvider {
|
|
|
121
183
|
resetAuthorization(operatorDisconnected?: boolean): Promise<void>;
|
|
122
184
|
invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): Promise<void>;
|
|
123
185
|
}
|
|
186
|
+
export {};
|