@cotal-ai/auth 0.12.0 → 0.13.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/dist/admission-mediator.d.ts +319 -0
- package/dist/admission-mediator.d.ts.map +1 -0
- package/dist/admission-mediator.js +1033 -0
- package/dist/admission-mediator.js.map +1 -0
- package/dist/auth-admin.d.ts +29 -0
- package/dist/auth-admin.d.ts.map +1 -0
- package/dist/auth-admin.js +224 -0
- package/dist/auth-admin.js.map +1 -0
- package/dist/authority-client.d.ts +215 -0
- package/dist/authority-client.d.ts.map +1 -0
- package/dist/authority-client.js +346 -0
- package/dist/authority-client.js.map +1 -0
- package/dist/barrier-evict.d.ts +16 -0
- package/dist/barrier-evict.d.ts.map +1 -0
- package/dist/barrier-evict.js +100 -0
- package/dist/barrier-evict.js.map +1 -0
- package/dist/connect-reader.d.ts +72 -0
- package/dist/connect-reader.d.ts.map +1 -0
- package/dist/connect-reader.js +154 -0
- package/dist/connect-reader.js.map +1 -0
- package/dist/continuity.d.ts.map +1 -1
- package/dist/continuity.js +3 -0
- package/dist/continuity.js.map +1 -1
- package/dist/credential-ledger.d.ts +343 -0
- package/dist/credential-ledger.d.ts.map +1 -0
- package/dist/credential-ledger.js +732 -0
- package/dist/credential-ledger.js.map +1 -0
- package/dist/drain-repair.d.ts +95 -0
- package/dist/drain-repair.d.ts.map +1 -0
- package/dist/drain-repair.js +305 -0
- package/dist/drain-repair.js.map +1 -0
- package/dist/idp.d.ts +19 -0
- package/dist/idp.d.ts.map +1 -1
- package/dist/idp.js +17 -0
- package/dist/idp.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -2
- package/dist/index.js.map +1 -1
- package/dist/issuer.d.ts +8 -0
- package/dist/issuer.d.ts.map +1 -1
- package/dist/issuer.js +4 -2
- package/dist/issuer.js.map +1 -1
- package/dist/ledger-scanner.d.ts +98 -0
- package/dist/ledger-scanner.d.ts.map +1 -0
- package/dist/ledger-scanner.js +352 -0
- package/dist/ledger-scanner.js.map +1 -0
- package/dist/ledger.d.ts +7 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +53 -5
- package/dist/ledger.js.map +1 -1
- package/dist/lifecycle-registry.d.ts +397 -0
- package/dist/lifecycle-registry.d.ts.map +1 -0
- package/dist/lifecycle-registry.js +819 -0
- package/dist/lifecycle-registry.js.map +1 -0
- package/dist/permissions.d.ts +5 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +29 -6
- package/dist/permissions.js.map +1 -1
- package/dist/plane-claim.d.ts +83 -0
- package/dist/plane-claim.d.ts.map +1 -0
- package/dist/plane-claim.js +290 -0
- package/dist/plane-claim.js.map +1 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +2 -1
- package/dist/provider.js.map +1 -1
- package/dist/records-scanner.d.ts +78 -0
- package/dist/records-scanner.d.ts.map +1 -0
- package/dist/records-scanner.js +320 -0
- package/dist/records-scanner.js.map +1 -0
- package/dist/retirement-barrier.d.ts +217 -0
- package/dist/retirement-barrier.d.ts.map +1 -0
- package/dist/retirement-barrier.js +624 -0
- package/dist/retirement-barrier.js.map +1 -0
- package/dist/retirement-cleaner.d.ts +54 -0
- package/dist/retirement-cleaner.d.ts.map +1 -0
- package/dist/retirement-cleaner.js +158 -0
- package/dist/retirement-cleaner.js.map +1 -0
- package/dist/root-credential.d.ts +26 -0
- package/dist/root-credential.d.ts.map +1 -0
- package/dist/root-credential.js +116 -0
- package/dist/root-credential.js.map +1 -0
- package/dist/service.d.ts +60 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +317 -10
- package/dist/service.js.map +1 -1
- package/dist/session-ledger.d.ts +224 -0
- package/dist/session-ledger.d.ts.map +1 -0
- package/dist/session-ledger.js +713 -0
- package/dist/session-ledger.js.map +1 -0
- package/dist/token.d.ts +17 -0
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +30 -2
- package/dist/token.js.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The auth service's SELF-MINTED data-account connections (R1). The service already holds the
|
|
3
|
+
* data account's signing seed (minting scoped users IS its function), so its own infrastructure
|
|
4
|
+
* access is signed from that seed directly — a ledgered `cred.`/`epcred.` row would create no
|
|
5
|
+
* enforcement point (connect never re-reads the reader's own row) and would be enumerated by the
|
|
6
|
+
* lifecycle barriers as if it were an agent's. The revoke story is the D5 class: stop the service
|
|
7
|
+
* or rotate the data-account signing seed. A genuinely LEDGERED infra-credential family is the
|
|
8
|
+
* deferred infra-mint work; migrate onto it there, do not invent it here.
|
|
9
|
+
*
|
|
10
|
+
* Two connections, least-privilege by construction:
|
|
11
|
+
* - the CONNECT READER (`cotal:auth-reader:<space>`): read-only over both authority stores
|
|
12
|
+
* ({@link authConnectReaderGrants}), supervised — shape-proved on EVERY (re)bind, unproved
|
|
13
|
+
* while disconnected, so a reader that cannot currently prove its stores DENIES every connect.
|
|
14
|
+
* - the MINT WRITER (`cotal:auth-mint:<space>`): the exchange-time issuance executor
|
|
15
|
+
* ({@link authorityWriterGrants}) — store ensure + the activation saga + the root-credential
|
|
16
|
+
* mint protocol. Its unfenced reads only feed revision-pinned CASes (a stale read LOSES) or a
|
|
17
|
+
* bearer stamp the leader-served connect arm re-checks, so bind-time proof suffices.
|
|
18
|
+
*
|
|
19
|
+
* RENEWAL IS A FAIL-CLOSED BOUNDARY: each connection authenticates with a SHORT-exp user JWT over
|
|
20
|
+
* a stable nkey, re-minted in-process at half-life; the broker disconnects the connection at JWT
|
|
21
|
+
* expiry and the reconnect presents the freshest mint. A renewal-mint failure fires
|
|
22
|
+
* {@link AuthorityClientOpts.onRenewalFailure} IMMEDIATELY — the supervised reader downs itself
|
|
23
|
+
* on it (unproved + connection closed), so connects DENY from the moment the renewal fails, not
|
|
24
|
+
* only when the old credential eventually expires. A downed reader never comes back without a
|
|
25
|
+
* service restart; there is no file-only fallback.
|
|
26
|
+
*/
|
|
27
|
+
import { connect, jwtAuthenticator } from "@nats-io/transport-node";
|
|
28
|
+
import { encodeUser } from "@nats-io/jwt";
|
|
29
|
+
import { fromPublic, fromSeed } from "@nats-io/nkeys";
|
|
30
|
+
import { EpEnvelopeError, assertInboxConnId, endpointToken, epAuthBucket, epfStreamName, newIdentity, recordsBucket, retirementFrontierStreams, spacePrefix, assertPoolToken, principalTags, principalKey } from "@cotal-ai/core";
|
|
31
|
+
import { authConnectReaderGrants, openConnectReader } from "./connect-reader.js";
|
|
32
|
+
/** Self-minted infra-credential TTL (fact-3 pin: SHORT expiry + in-process renewal, a bounded
|
|
33
|
+
* in-memory credential that closes with the service — never an unbounded persisted one). */
|
|
34
|
+
export const AUTHORITY_CLIENT_TTL_SEC = 15 * 60;
|
|
35
|
+
/**
|
|
36
|
+
* The MINT WRITER's scoped credential grant (SPEC 13.9): the exchange-time issuance executor.
|
|
37
|
+
* Exactly the store-ensure + activation-saga + root-mint surface:
|
|
38
|
+
*
|
|
39
|
+
* - `$JS.API.STREAM.CREATE` on both authority KV streams and `STREAM.UPDATE` on records (the
|
|
40
|
+
* boot-time `ensureAuthorityStores`), `STREAM.INFO` on both (ensure verify + §13.12 bind
|
|
41
|
+
* proof), `STREAM.MSG.GET` on both (auth-store KV reads are leader-only by shape; fenced
|
|
42
|
+
* record reads), and records `DIRECT.GET` (the records bucket is Direct-capable and its KV
|
|
43
|
+
* reads here only feed revision-pinned CASes, which a stale read LOSES — fail-closed).
|
|
44
|
+
* - `$KV` writes on EXACTLY the issuance keys: auth `gate.` / `cred.` / `bysrc.` (the gate CAS
|
|
45
|
+
* fence and the mint's rows) and records `lifecycle.` / `uid.` (the alias head and the
|
|
46
|
+
* space-global uid reservation). No `srcgate.`/`stage.`/`session.` writes: root issuance
|
|
47
|
+
* presents no handles and runs no takeover; barriers are NOT this credential's job.
|
|
48
|
+
*
|
|
49
|
+
* Named residuals (D32 class, not pretend-confined): `STREAM.MSG.GET` is body-selected, so this
|
|
50
|
+
* is a stream-wide METADATA read of both stores; the `$KV` prefixes span every uid/alias (broker
|
|
51
|
+
* ACLs cannot scope "only the row you are minting"), so a compromised writer can forge issuance
|
|
52
|
+
* rows — it is the ISSUANCE AUTHORITY, that authority is exactly what it holds; and every raw
|
|
53
|
+
* MSG.GET/DIRECT.GET profile carries the caller-selected-reply injection class.
|
|
54
|
+
*/
|
|
55
|
+
export function authorityWriterGrants(space, connId) {
|
|
56
|
+
const auth = `KV_${epAuthBucket(space)}`;
|
|
57
|
+
const records = `KV_${recordsBucket(space)}`;
|
|
58
|
+
// Same connId hygiene as authConnectReaderGrants: the only untrusted subject-forming input goes
|
|
59
|
+
// through the shared inbox grammar so it can never widen the scoped inbox.
|
|
60
|
+
const inbox = assertInboxConnId(connId);
|
|
61
|
+
return {
|
|
62
|
+
publish: [
|
|
63
|
+
"$JS.API.INFO",
|
|
64
|
+
`$JS.API.STREAM.CREATE.${auth}`,
|
|
65
|
+
`$JS.API.STREAM.CREATE.${records}`,
|
|
66
|
+
`$JS.API.STREAM.UPDATE.${records}`,
|
|
67
|
+
`$JS.API.STREAM.INFO.${auth}`,
|
|
68
|
+
`$JS.API.STREAM.INFO.${records}`,
|
|
69
|
+
`$JS.API.STREAM.MSG.GET.${auth}`,
|
|
70
|
+
`$JS.API.STREAM.MSG.GET.${records}`,
|
|
71
|
+
`$JS.API.DIRECT.GET.${records}`,
|
|
72
|
+
`$JS.API.DIRECT.GET.${records}.>`,
|
|
73
|
+
`$KV.${epAuthBucket(space)}.gate.>`,
|
|
74
|
+
`$KV.${epAuthBucket(space)}.cred.>`,
|
|
75
|
+
`$KV.${epAuthBucket(space)}.bysrc.>`,
|
|
76
|
+
`$KV.${recordsBucket(space)}.lifecycle.>`,
|
|
77
|
+
`$KV.${recordsBucket(space)}.uid.>`,
|
|
78
|
+
],
|
|
79
|
+
subscribe: [`_INBOX_${inbox}.>`],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The BARRIER EXECUTOR's scoped credential grant (SPEC 13.9): the lifecycle-barrier surface —
|
|
84
|
+
* exactly what the takeover barrier (and its retirement sibling) reaches through the sealed
|
|
85
|
+
* registry, on its OWN connection so the latency-sensitive mint writer never carries barrier
|
|
86
|
+
* authority (its own doc pins "barriers are NOT this credential's job"):
|
|
87
|
+
*
|
|
88
|
+
* - `STREAM.INFO` on both stores (the registry's §13.12 bind proof), `STREAM.MSG.GET` on both
|
|
89
|
+
* (leader-served gate/cred/intent and head reads), and records `DIRECT.GET` (reads that only
|
|
90
|
+
* feed revision-pinned CASes — the mint writer's own discipline).
|
|
91
|
+
* - NO auth-stream `CONSUMER.CREATE` at all (46e778f re-verify): a consumer-create BODY is not
|
|
92
|
+
* subject-ACL confinable, so any create grant admits a durable PUSH exporter surviving revoke
|
|
93
|
+
* (nats-server#8274). This profile therefore does NOT enumerate: the barrier's
|
|
94
|
+
* family/intent/lineage LastPerSubject scans run on the SEALED auth-ledger scanner, a SEPARATE
|
|
95
|
+
* dedicated credential that holds the only auth-stream `CONSUMER.CREATE` and never escapes the
|
|
96
|
+
* process (`ledger-scanner.ts`). The `STREAM.MSG.GET` here is only this profile's leader-served
|
|
97
|
+
* POINT reads (a specific gate/cred/intent/head `last_by_subj`), never a family scan. No consumer
|
|
98
|
+
* authority on either stream.
|
|
99
|
+
* - `$KV` writes on EXACTLY the barrier keys: auth `gate.` (the freeze/reopen CAS), `cred.`
|
|
100
|
+
* (the family revokes), `stage.` (the durable operation intent), and records `lifecycle.`
|
|
101
|
+
* (the containment/epoch head CASes). No `bysrc.`/`uid.` (a barrier never mints), no
|
|
102
|
+
* `srcgate.`/`session.` (handle revocation and session reconcile are injected seams, not
|
|
103
|
+
* this credential's job).
|
|
104
|
+
*
|
|
105
|
+
* Named residuals (D32 class, not pretend-confined): the stream-wide body-selected `MSG.GET`
|
|
106
|
+
* metadata read (a ONE-SHOT read to the requester's own inbox while the credential is live — not
|
|
107
|
+
* a durable/push exporter) and the caller-selected-reply injection class ride this profile like
|
|
108
|
+
* its siblings; and the `$KV` prefixes span every uid (broker ACLs cannot scope "only the family
|
|
109
|
+
* you are containing") — the executor IS the barrier authority, and that authority is exactly
|
|
110
|
+
* what it holds.
|
|
111
|
+
*/
|
|
112
|
+
export function authorityBarrierGrants(space, connId) {
|
|
113
|
+
const auth = `KV_${epAuthBucket(space)}`;
|
|
114
|
+
const records = `KV_${recordsBucket(space)}`;
|
|
115
|
+
// Same connId hygiene as its siblings: the only untrusted subject-forming input goes through
|
|
116
|
+
// the shared inbox grammar so it can never widen the scoped inbox.
|
|
117
|
+
const inbox = assertInboxConnId(connId);
|
|
118
|
+
// The §13.1 frontier step reads the last sequence of every stream in the retirement's
|
|
119
|
+
// `frontierStreams` (a subset of the CLOSED {@link retirementFrontierStreams} set). Grant
|
|
120
|
+
// STREAM.INFO for exactly that set, from the same source the intent is validated against, so the
|
|
121
|
+
// frontier authority and the frontier-writable set never drift (a real callout broker denies an
|
|
122
|
+
// ungranted INFO and the resume wedges forever). records INFO is already listed; the frontier set
|
|
123
|
+
// adds EPF/EPW/EPE (records dedups below).
|
|
124
|
+
const frontierInfo = retirementFrontierStreams(space).map((s) => `$JS.API.STREAM.INFO.${s}`);
|
|
125
|
+
return {
|
|
126
|
+
publish: [
|
|
127
|
+
"$JS.API.INFO",
|
|
128
|
+
`$JS.API.STREAM.INFO.${auth}`,
|
|
129
|
+
`$JS.API.STREAM.INFO.${records}`,
|
|
130
|
+
...frontierInfo.filter((r) => r !== `$JS.API.STREAM.INFO.${records}`),
|
|
131
|
+
`$JS.API.STREAM.MSG.GET.${auth}`,
|
|
132
|
+
`$JS.API.STREAM.MSG.GET.${records}`,
|
|
133
|
+
`$JS.API.DIRECT.GET.${records}`,
|
|
134
|
+
`$JS.API.DIRECT.GET.${records}.>`,
|
|
135
|
+
// NO auth-stream `CONSUMER.CREATE` (security/fact/distsys/freelance, 46e778f re-verify): the
|
|
136
|
+
// extended `CONSUMER.CREATE.<auth>.<name>.<filter>` API pins name+filter on the SUBJECT but
|
|
137
|
+
// the request BODY is not subject-ACL confinable — a holder can set `durable_name`
|
|
138
|
+
// (== the subject name token) + PUSH `deliver_subject` (nats-server#8274) and leave a DURABLE
|
|
139
|
+
// exporter of every current/future gate/cred/stage row that SURVIVES this connection and JWT
|
|
140
|
+
// revoke. Subject-token pinning cannot stop it; only holding no create can. The barrier's
|
|
141
|
+
// family/intent/lineage enumerations therefore run on the SEALED auth-ledger scanner — a
|
|
142
|
+
// SEPARATE dedicated credential that holds the only auth-stream `CONSUMER.CREATE` and never
|
|
143
|
+
// escapes the process (ledger-scanner.ts) — NOT on this profile. The `STREAM.MSG.GET` granted
|
|
144
|
+
// above is only this profile's leader-served POINT reads (a specific gate/cred/intent/head
|
|
145
|
+
// `last_by_subj`). Remaining D32 residual (unchanged, accepted): that stream-wide `MSG.GET`
|
|
146
|
+
// while the credential is LIVE is a one-shot read to the requester's own inbox carrying the
|
|
147
|
+
// caller-selected-reply injection class — never a durable/push exporter.
|
|
148
|
+
`$KV.${epAuthBucket(space)}.gate.>`,
|
|
149
|
+
`$KV.${epAuthBucket(space)}.cred.>`,
|
|
150
|
+
// ONE token: the barrier's own durable operation intents `stage.<opId>` — never `stage.>`,
|
|
151
|
+
// which would also reach the session ledger's `stage.session.<sid>.<c|s>` release pins (a
|
|
152
|
+
// family the SESSION writer owns). The documented-future takeover/registration successor
|
|
153
|
+
// artifacts (`stage.<opId>.…`) have no writer yet; compose `stage.*.>` in the slice that
|
|
154
|
+
// introduces them, so forgetting fails loud here instead of widening silently now.
|
|
155
|
+
`$KV.${epAuthBucket(space)}.stage.*`,
|
|
156
|
+
// The PLANE CLAIM (#29 HIGH 3, SPEC 13.13): the ONE exact never-deleted key the plane-open
|
|
157
|
+
// create/CAS protocol writes (`plane` — no `.>`/`.*` widen; the row binds the two sealed
|
|
158
|
+
// scanner connection tuples and its held/released state). Reads ride the leader-served
|
|
159
|
+
// `STREAM.MSG.GET` above. Only this profile writes it; the claim IS the cross-process
|
|
160
|
+
// single-plane exclusion, so a wider row here would hand takeover authority to whatever
|
|
161
|
+
// else composes onto this profile.
|
|
162
|
+
`$KV.${epAuthBucket(space)}.plane`,
|
|
163
|
+
`$KV.${recordsBucket(space)}.lifecycle.>`,
|
|
164
|
+
// The retirement FRONTIER record `frontier.<lifecycleUid>` (§13.7: create-only, never
|
|
165
|
+
// deleted, one key per retired lifecycle, recorded once under its own operation's opId
|
|
166
|
+
// BEFORE the gate/head terminals): the barrier is its one writer, and the exact-arity
|
|
167
|
+
// `frontier.*` row is that authority (#29 piece 4; a barrier without it is broker-denied
|
|
168
|
+
// at step 6 on a real callout broker, masked by super-user smoke brokers).
|
|
169
|
+
`$KV.${recordsBucket(space)}.frontier.*`,
|
|
170
|
+
],
|
|
171
|
+
subscribe: [`_INBOX_${inbox}.>`],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** The RETIREMENT SETTLEMENT EXECUTOR's op-bounded rows (SPEC 13.9 "Retirement settlement",
|
|
175
|
+
* the ceee1a1 authority split): COMPOSED onto {@link authorityBarrierGrants} for ONE retirement
|
|
176
|
+
* operation over its EFFECTIVE INVENTORY endpoint/pools (the target's accepted obligation routes
|
|
177
|
+
* UNIONED with the durable intent's trusted additive hints) — never a standing grant, never
|
|
178
|
+
* minted without a live intent. Per effective-inventory pool: the lease-record CAS write
|
|
179
|
+
* (`lease.<endpoint>.<pool>.…` — create for the worker-less expiry sentinel, revision-pinned
|
|
180
|
+
* update otherwise) and the lease-derived `wrk` terminal create-only publish
|
|
181
|
+
* (`epf.<endpoint>.wrk.<pool>.>`, first terminal wins). Plus the leader-served EPF fencing
|
|
182
|
+
* reads (`STREAM.MSG.GET.EPF_<space>`: the acceptance-decision re-derivation and the
|
|
183
|
+
* terminal-observe/CAS-loser reads); the records-side fencing/CAS reads ride the barrier
|
|
184
|
+
* profile this composes onto. The `epw.>` ENQUEUE row is deliberately absent: the executor's
|
|
185
|
+
* reconcile is reachable only for items at/past their own `workExpiry` (the settlement seam
|
|
186
|
+
* refuses `expired` before the horizon), which structurally never takes the re-enqueue repair
|
|
187
|
+
* branch — if a code change ever reached it, the broker denies and the barrier fails loud.
|
|
188
|
+
*
|
|
189
|
+
* D32 residuals, EXPLICIT (the relocated forge the bounded cleaner does NOT carry): KV subject
|
|
190
|
+
* permissions cannot distinguish CAS from overwrite or DEL/PURGE markers, and the `wrk`
|
|
191
|
+
* publish is payload-blind, so a compromised executor can forge a lease settlement or work
|
|
192
|
+
* terminal WITHIN its EFFECTIVE-INVENTORY pools (never beyond them); the space-wide EPF
|
|
193
|
+
* `STREAM.MSG.GET` exposure and the caller-selected-reply injection ride it like every
|
|
194
|
+
* API-holding profile. Op-bounded, effective-inventory-confined, revoked with the operation. */
|
|
195
|
+
export function barrierExecutorSettlementGrants(space, endpoint, pools) {
|
|
196
|
+
if (!Array.isArray(pools) || pools.length === 0)
|
|
197
|
+
throw new EpEnvelopeError("failed-precondition", "an executor settlement grant lists at least one exact pool (SPEC 13.9: the op intent enumerates them; a poolless settlement authority is none)");
|
|
198
|
+
const e = endpointToken(endpoint);
|
|
199
|
+
const publish = [];
|
|
200
|
+
for (const pool of pools) {
|
|
201
|
+
const p = assertPoolToken(pool);
|
|
202
|
+
publish.push(`${spacePrefix(space)}.epf.${e}.wrk.${p}.>`, `$KV.${recordsBucket(space)}.lease.${e}.${p}.>`);
|
|
203
|
+
}
|
|
204
|
+
publish.push(`$JS.API.STREAM.MSG.GET.${epfStreamName(space)}`);
|
|
205
|
+
return { publish };
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Open one self-minted data-account connection: a stable user nkey, a short-exp user JWT signed
|
|
209
|
+
* by the data account's signing seed, re-minted in-process at half-life. The authenticator
|
|
210
|
+
* presents the FRESHEST mint on every (re)connect; see the module header for why a failed
|
|
211
|
+
* renewal fail-closes instead of retrying on a lapsed credential.
|
|
212
|
+
*/
|
|
213
|
+
export async function openAuthorityClient(opts) {
|
|
214
|
+
const identity = newIdentity();
|
|
215
|
+
const grants = opts.grants(identity.id);
|
|
216
|
+
const signer = fromSeed(new TextEncoder().encode(opts.dataAccount.signingSeed));
|
|
217
|
+
const mint = async () => encodeUser(opts.label, fromPublic(identity.id), fromPublic(opts.dataAccount.pub), { pub: { allow: grants.publish }, sub: { allow: grants.subscribe },
|
|
218
|
+
...(opts.principal ? { tags: principalTags(opts.principal.owner, opts.principal.actor) } : {}) },
|
|
219
|
+
// planeCandidate: NON-EXPIRING by design (see the option's doc — the named fact-3 residual);
|
|
220
|
+
// every other authority connection keeps the short-exp + half-life-renewal boundary.
|
|
221
|
+
opts.planeCandidate ? { signer } : { signer, exp: Math.floor(Date.now() / 1000) + AUTHORITY_CLIENT_TTL_SEC });
|
|
222
|
+
let currentJwt = await mint();
|
|
223
|
+
const renew = async () => {
|
|
224
|
+
if (opts.probeRenewal?.fail)
|
|
225
|
+
throw new Error("probe-forced renewal failure");
|
|
226
|
+
return mint();
|
|
227
|
+
};
|
|
228
|
+
// No renewal timer for a planeCandidate: nothing expires, and a fresh mint could only be
|
|
229
|
+
// presented on the reconnect the non-reconnecting shape forbids.
|
|
230
|
+
const renewal = opts.planeCandidate ? undefined : setInterval(() => {
|
|
231
|
+
void renew().then((jwt) => { currentJwt = jwt; }, (e) => {
|
|
232
|
+
opts.log(`${opts.label}: credential renewal FAILED (${e?.message ?? String(e)}) - failing closed NOW (fail-closed renewal boundary)`);
|
|
233
|
+
opts.onRenewalFailure?.();
|
|
234
|
+
});
|
|
235
|
+
}, opts.probeRenewal?.intervalMs ?? (AUTHORITY_CLIENT_TTL_SEC / 2) * 1000);
|
|
236
|
+
let nc;
|
|
237
|
+
try {
|
|
238
|
+
nc = await connect({
|
|
239
|
+
servers: opts.server,
|
|
240
|
+
name: opts.label,
|
|
241
|
+
authenticator: jwtAuthenticator(() => currentJwt, new TextEncoder().encode(identity.seed)),
|
|
242
|
+
inboxPrefix: `_INBOX_${identity.id}`,
|
|
243
|
+
// planeCandidate and the per-call repair connections (#4) never auto-reconnect; every standing
|
|
244
|
+
// authority connection reconnects infinitely so a broker blip never downs it silently.
|
|
245
|
+
...(opts.planeCandidate || opts.noReconnect ? { reconnect: false, maxReconnectAttempts: 0 } : { maxReconnectAttempts: -1 }),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
if (renewal !== undefined)
|
|
250
|
+
clearInterval(renewal);
|
|
251
|
+
throw e;
|
|
252
|
+
}
|
|
253
|
+
let tuple;
|
|
254
|
+
let gone;
|
|
255
|
+
if (opts.planeCandidate) {
|
|
256
|
+
// The claim row pins this exact identity; a connect whose INFO cannot supply it is unusable
|
|
257
|
+
// as an ownership-bearing connection — fail loud, never claim over a hole.
|
|
258
|
+
const info = nc.info;
|
|
259
|
+
if (typeof info?.server_id !== "string" || info.server_id.length === 0 ||
|
|
260
|
+
typeof info.client_id !== "number" || !Number.isSafeInteger(info.client_id) || info.client_id <= 0) {
|
|
261
|
+
await nc.close().catch(() => { });
|
|
262
|
+
throw new EpEnvelopeError("unavailable", `${opts.label}: the broker INFO carries no usable (server_id, client_id) for the plane claim (got server_id=${JSON.stringify(info?.server_id)}, client_id=${JSON.stringify(info?.client_id)}); an ownership-bearing scanner connection must be claim-pinnable (SPEC 13.13)`);
|
|
263
|
+
}
|
|
264
|
+
tuple = { serverId: info.server_id, cid: info.client_id, userNkey: identity.id };
|
|
265
|
+
gone = nc.closed().then(() => undefined, () => undefined);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
nc,
|
|
269
|
+
connId: identity.id,
|
|
270
|
+
...(opts.principal ? { principal: principalKey(opts.principal.owner, opts.principal.actor).key } : {}),
|
|
271
|
+
...(tuple ? { tuple } : {}),
|
|
272
|
+
...(gone ? { gone } : {}),
|
|
273
|
+
close: async () => {
|
|
274
|
+
if (renewal !== undefined)
|
|
275
|
+
clearInterval(renewal);
|
|
276
|
+
await nc.close().catch(() => { });
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Open the supervised connect reader: the self-minted reader connection plus the §13.12 bind
|
|
282
|
+
* discipline ACROSS RECONNECTS. The shape proof re-runs on EVERY (re)bind, not just startup — a
|
|
283
|
+
* dropped reader reconnecting could land on a mirror/follower, so it is UNPROVED from the moment
|
|
284
|
+
* the connection drops until {@link openConnectReader} passes again, and `current()` refuses in
|
|
285
|
+
* between. The initial proof is awaited: this constructor resolving IS the readiness signal.
|
|
286
|
+
*/
|
|
287
|
+
export async function openSupervisedConnectReader(opts) {
|
|
288
|
+
const label = `cotal:auth-reader:${opts.space}`;
|
|
289
|
+
let reader;
|
|
290
|
+
let closed = false;
|
|
291
|
+
let client;
|
|
292
|
+
client = await openAuthorityClient({
|
|
293
|
+
...opts,
|
|
294
|
+
label,
|
|
295
|
+
grants: (connId) => authConnectReaderGrants(opts.space, connId),
|
|
296
|
+
// Renewal failure is a fail-closed boundary NOW: down the reader the instant a remint
|
|
297
|
+
// rejects, so connects DENY immediately instead of riding the old credential to its expiry.
|
|
298
|
+
onRenewalFailure: () => {
|
|
299
|
+
reader = undefined;
|
|
300
|
+
opts.log(`${label}: credential renewal failed - reader downed NOW, connects deny until the service restarts (fail-closed)`);
|
|
301
|
+
if (!closed)
|
|
302
|
+
void client?.close();
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
const c = client;
|
|
306
|
+
try {
|
|
307
|
+
reader = await openConnectReader(c.nc, opts.space);
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
await c.close();
|
|
311
|
+
throw e;
|
|
312
|
+
}
|
|
313
|
+
void (async () => {
|
|
314
|
+
for await (const s of c.nc.status()) {
|
|
315
|
+
if (closed)
|
|
316
|
+
break;
|
|
317
|
+
if (s.type === "disconnect" || s.type === "error") {
|
|
318
|
+
reader = undefined;
|
|
319
|
+
opts.log(`${label}: connection lost (${s.type}) - connects DENY until the rebind shape proof passes`);
|
|
320
|
+
}
|
|
321
|
+
else if (s.type === "reconnect") {
|
|
322
|
+
try {
|
|
323
|
+
reader = await openConnectReader(c.nc, opts.space);
|
|
324
|
+
opts.log(`${label}: rebound and shape-proved; connect reads resume`);
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
reader = undefined;
|
|
328
|
+
opts.log(`${label}: REBIND SHAPE PROOF FAILED (${e?.message ?? String(e)}) - connects stay denied`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
})();
|
|
333
|
+
return {
|
|
334
|
+
current: () => {
|
|
335
|
+
if (reader === undefined)
|
|
336
|
+
throw new EpEnvelopeError("unavailable", "the connect-credential reader is not currently bound + shape-proved; a connect is never authorized without the live credential row (deny-new is fail-closed, SPEC 13.1/13.12)");
|
|
337
|
+
return reader;
|
|
338
|
+
},
|
|
339
|
+
close: async () => {
|
|
340
|
+
closed = true;
|
|
341
|
+
reader = undefined; // a closed reader refuses immediately, not on the next status event
|
|
342
|
+
await c.close();
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=authority-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authority-client.js","sourceRoot":"","sources":["../src/authority-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAuB,MAAM,yBAAyB,CAAC;AACzF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,yBAAyB,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAuB,MAAM,gBAAgB,CAAC;AACvP,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAsB,MAAM,qBAAqB,CAAC;AAErG;6FAC6F;AAC7F,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,MAAc;IACjE,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IAC7C,gGAAgG;IAChG,2EAA2E;IAC3E,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO;QACL,OAAO,EAAE;YACP,cAAc;YACd,yBAAyB,IAAI,EAAE;YAC/B,yBAAyB,OAAO,EAAE;YAClC,yBAAyB,OAAO,EAAE;YAClC,uBAAuB,IAAI,EAAE;YAC7B,uBAAuB,OAAO,EAAE;YAChC,0BAA0B,IAAI,EAAE;YAChC,0BAA0B,OAAO,EAAE;YACnC,sBAAsB,OAAO,EAAE;YAC/B,sBAAsB,OAAO,IAAI;YACjC,OAAO,YAAY,CAAC,KAAK,CAAC,SAAS;YACnC,OAAO,YAAY,CAAC,KAAK,CAAC,SAAS;YACnC,OAAO,YAAY,CAAC,KAAK,CAAC,UAAU;YACpC,OAAO,aAAa,CAAC,KAAK,CAAC,cAAc;YACzC,OAAO,aAAa,CAAC,KAAK,CAAC,QAAQ;SACpC;QACD,SAAS,EAAE,CAAC,UAAU,KAAK,IAAI,CAAC;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,MAAc;IAClE,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IAC7C,6FAA6F;IAC7F,mEAAmE;IACnE,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACxC,sFAAsF;IACtF,0FAA0F;IAC1F,iGAAiG;IACjG,gGAAgG;IAChG,kGAAkG;IAClG,2CAA2C;IAC3C,MAAM,YAAY,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,EAAE,CAAC,CAAC;IAC7F,OAAO;QACL,OAAO,EAAE;YACP,cAAc;YACd,uBAAuB,IAAI,EAAE;YAC7B,uBAAuB,OAAO,EAAE;YAChC,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,uBAAuB,OAAO,EAAE,CAAC;YACrE,0BAA0B,IAAI,EAAE;YAChC,0BAA0B,OAAO,EAAE;YACnC,sBAAsB,OAAO,EAAE;YAC/B,sBAAsB,OAAO,IAAI;YACjC,6FAA6F;YAC7F,4FAA4F;YAC5F,mFAAmF;YACnF,8FAA8F;YAC9F,6FAA6F;YAC7F,0FAA0F;YAC1F,yFAAyF;YACzF,4FAA4F;YAC5F,8FAA8F;YAC9F,2FAA2F;YAC3F,4FAA4F;YAC5F,4FAA4F;YAC5F,yEAAyE;YACzE,OAAO,YAAY,CAAC,KAAK,CAAC,SAAS;YACnC,OAAO,YAAY,CAAC,KAAK,CAAC,SAAS;YACnC,2FAA2F;YAC3F,0FAA0F;YAC1F,yFAAyF;YACzF,yFAAyF;YACzF,mFAAmF;YACnF,OAAO,YAAY,CAAC,KAAK,CAAC,UAAU;YACpC,2FAA2F;YAC3F,yFAAyF;YACzF,uFAAuF;YACvF,sFAAsF;YACtF,wFAAwF;YACxF,mCAAmC;YACnC,OAAO,YAAY,CAAC,KAAK,CAAC,QAAQ;YAClC,OAAO,aAAa,CAAC,KAAK,CAAC,cAAc;YACzC,sFAAsF;YACtF,uFAAuF;YACvF,sFAAsF;YACtF,yFAAyF;YACzF,2EAA2E;YAC3E,OAAO,aAAa,CAAC,KAAK,CAAC,aAAa;SACzC;QACD,SAAS,EAAE,CAAC,UAAU,KAAK,IAAI,CAAC;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;iGAoBiG;AACjG,MAAM,UAAU,+BAA+B,CAAC,KAAa,EAAE,QAAgB,EAAE,KAAe;IAC9F,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAC7C,MAAM,IAAI,eAAe,CAAC,qBAAqB,EAAE,gJAAgJ,CAAC,CAAC;IACrM,MAAM,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAClC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,CAAC,IAAI,CACV,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAC3C,OAAO,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAChD,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,0BAA0B,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/D,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAsED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAyB;IACjE,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,KAAK,IAAqB,EAAE,CACvC,UAAU,CACR,IAAI,CAAC,KAAK,EACV,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,EACvB,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAChC,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE;QAChE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;IAClG,6FAA6F;IAC7F,qFAAqF;IACrF,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,wBAAwB,EAAE,CAC7G,CAAC;IACJ,IAAI,UAAU,GAAG,MAAM,IAAI,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,IAAqB,EAAE;QACxC,IAAI,IAAI,CAAC,YAAY,EAAE,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC7E,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC,CAAC;IACF,yFAAyF;IACzF,iEAAiE;IACjE,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE;QACjE,KAAK,KAAK,EAAE,CAAC,IAAI,CACf,CAAC,GAAG,EAAE,EAAE,GAAG,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,EAC9B,CAAC,CAAC,EAAE,EAAE;YACJ,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,gCAAiC,CAAW,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,uDAAuD,CAAC,CAAC;YACjJ,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC5B,CAAC,CACF,CAAC;IACJ,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,IAAI,CAAC,wBAAwB,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3E,IAAI,EAAkB,CAAC;IACvB,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,OAAO,CAAC;YACjB,OAAO,EAAE,IAAI,CAAC,MAAM;YACpB,IAAI,EAAE,IAAI,CAAC,KAAK;YAChB,aAAa,EAAE,gBAAgB,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC1F,WAAW,EAAE,UAAU,QAAQ,CAAC,EAAE,EAAE;YACpC,+FAA+F;YAC/F,uFAAuF;YACvF,GAAG,CAAC,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,CAAC,CAAC,EAAE,CAAC;SAC5H,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,OAAO,KAAK,SAAS;YAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAClD,MAAM,CAAC,CAAC;IACV,CAAC;IACD,IAAI,KAAiC,CAAC;IACtC,IAAI,IAA+B,CAAC;IACpC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,4FAA4F;QAC5F,2EAA2E;QAC3E,MAAM,IAAI,GAAG,EAAE,CAAC,IAA8D,CAAC;QAC/E,IAAI,OAAO,IAAI,EAAE,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;YAClE,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;YACvG,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACjC,MAAM,IAAI,eAAe,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,KAAK,iGAAiG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,gFAAgF,CAAC,CAAC;QACxT,CAAC;QACD,KAAK,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjF,IAAI,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO;QACL,EAAE;QACF,MAAM,EAAE,QAAQ,CAAC,EAAE;QACnB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,IAAI,OAAO,KAAK,SAAS;gBAAE,aAAa,CAAC,OAAO,CAAC,CAAC;YAClD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC;AAUD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,IAAmD;IAEnD,MAAM,KAAK,GAAG,qBAAqB,IAAI,CAAC,KAAK,EAAE,CAAC;IAChD,IAAI,MAAiC,CAAC;IACtC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,MAAmC,CAAC;IACxC,MAAM,GAAG,MAAM,mBAAmB,CAAC;QACjC,GAAG,IAAI;QACP,KAAK;QACL,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC;QAC/D,sFAAsF;QACtF,4FAA4F;QAC5F,gBAAgB,EAAE,GAAG,EAAE;YACrB,MAAM,GAAG,SAAS,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,yGAAyG,CAAC,CAAC;YAC5H,IAAI,CAAC,MAAM;gBAAE,KAAK,MAAM,EAAE,KAAK,EAAE,CAAC;QACpC,CAAC;KACF,CAAC,CAAC;IACH,MAAM,CAAC,GAAG,MAAM,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;QAChB,MAAM,CAAC,CAAC;IACV,CAAC;IACD,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC;YACpC,IAAI,MAAM;gBAAE,MAAM;YAClB,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAClD,MAAM,GAAG,SAAS,CAAC;gBACnB,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,sBAAsB,CAAC,CAAC,IAAI,uDAAuD,CAAC,CAAC;YACxG,CAAC;iBAAM,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAClC,IAAI,CAAC;oBACH,MAAM,GAAG,MAAM,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;oBACnD,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,kDAAkD,CAAC,CAAC;gBACvE,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,MAAM,GAAG,SAAS,CAAC;oBACnB,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,gCAAiC,CAAW,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC;gBACjH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IACL,OAAO;QACL,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,MAAM,KAAK,SAAS;gBACtB,MAAM,IAAI,eAAe,CAAC,aAAa,EAAE,+KAA+K,CAAC,CAAC;YAC5N,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,GAAG,IAAI,CAAC;YACd,MAAM,GAAG,SAAS,CAAC,CAAC,oEAAoE;YACxF,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EvictPrincipal } from "./credential-ledger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the barrier executor's `evictPrincipal` capability over the delivery daemon's
|
|
4
|
+
* `ctl.delivery-admin` rail. Per-call connection (an eviction is a rare, heavyweight barrier
|
|
5
|
+
* step; a standing privileged connection would be a wider surface holding nothing).
|
|
6
|
+
*/
|
|
7
|
+
export declare function makeDeliveryAdminEvictor(opts: {
|
|
8
|
+
space: string;
|
|
9
|
+
server: string;
|
|
10
|
+
dataAccount: {
|
|
11
|
+
pub: string;
|
|
12
|
+
signingSeed: string;
|
|
13
|
+
};
|
|
14
|
+
log: (line: string) => void;
|
|
15
|
+
}): EvictPrincipal;
|
|
16
|
+
//# sourceMappingURL=barrier-evict.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"barrier-evict.d.ts","sourceRoot":"","sources":["../src/barrier-evict.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAO7D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,GAAG,cAAc,CAkEjB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The auth service's VERIFIED-EVICTION seam (SPEC 13.1/13.9). The lifecycle barriers require
|
|
3
|
+
* `verifiedGone === true` before any epoch advance or head terminal, and the `$SYS` scan → KICK →
|
|
4
|
+
* verify capability lives with the DELIVERY DAEMON (co-located with the broker) — NEVER in this
|
|
5
|
+
* process: the auth service holds the data-account signing seed, and the D5 rail-split exists
|
|
6
|
+
* precisely so no seed-holding process also holds broker-admin `$SYS` material. This module
|
|
7
|
+
* reaches the daemon over the wire on the privileged `ctl.delivery-admin` control rail (the
|
|
8
|
+
* exact mechanism `cotal actor revoke` uses), with a caller credential self-minted per call from
|
|
9
|
+
* the data-account seed the service already holds.
|
|
10
|
+
*
|
|
11
|
+
* FAIL-CLOSED MAPPING: every failure — no daemon (NoResponders/timeout), a refusal, a garbled
|
|
12
|
+
* reply, or a reply describing a DIFFERENT principal — returns `verifiedGone:false` with an
|
|
13
|
+
* honest note. The calling barrier then throws and its gate stays frozen/resumable; this seam
|
|
14
|
+
* never fabricates success and never throws raw (the barrier owns the canonical error copy).
|
|
15
|
+
*
|
|
16
|
+
* Named residual (panel-reviewable): the caller credential rides the SUPERVISOR profile (the
|
|
17
|
+
* `cotal actor revoke` precedent) — only that profile holds the `ctl.delivery-admin` publish
|
|
18
|
+
* grant. The service holds the account signing seed, so this confers no authority it does not
|
|
19
|
+
* already have; a dedicated narrow delivery-admin infra profile is the cleaner target once the
|
|
20
|
+
* ledgered infra-mint family (#30) lands — migrate there, do not invent it here.
|
|
21
|
+
*/
|
|
22
|
+
import { CotalEndpoint, mintCreds, newIdentity } from "@cotal-ai/core";
|
|
23
|
+
/** The per-eviction credential's TTL (Security H3): one delivery-admin call runs in a 15s request
|
|
24
|
+
* budget; 60s covers connect + the call + teardown with margin, and bounds the blast radius of a
|
|
25
|
+
* copied supervisor credential to a minute rather than the profile's default 24h. */
|
|
26
|
+
const EVICTOR_CRED_TTL_SECONDS = 60;
|
|
27
|
+
/**
|
|
28
|
+
* Build the barrier executor's `evictPrincipal` capability over the delivery daemon's
|
|
29
|
+
* `ctl.delivery-admin` rail. Per-call connection (an eviction is a rare, heavyweight barrier
|
|
30
|
+
* step; a standing privileged connection would be a wider surface holding nothing).
|
|
31
|
+
*/
|
|
32
|
+
export function makeDeliveryAdminEvictor(opts) {
|
|
33
|
+
// The stripped mint view (core's stripSpaceAuth shape): mintCreds reads ONLY space +
|
|
34
|
+
// account.pub + account.signingSeed; the operator/system material is genuinely absent here.
|
|
35
|
+
const auth = {
|
|
36
|
+
space: opts.space,
|
|
37
|
+
operator: { seed: "", jwt: "" },
|
|
38
|
+
account: { pub: opts.dataAccount.pub, seed: "", jwt: "", signingSeed: opts.dataAccount.signingSeed, signingPub: "" },
|
|
39
|
+
sys: { pub: "", jwt: "" },
|
|
40
|
+
};
|
|
41
|
+
const failClosed = (principal, note) => {
|
|
42
|
+
opts.log(`auth-barrier-evict: ${principal}: ${note}`);
|
|
43
|
+
return { principal, kicked: 0, remaining: 0, verifiedGone: false, scanComplete: false, note };
|
|
44
|
+
};
|
|
45
|
+
return async (principal) => {
|
|
46
|
+
const id = newIdentity();
|
|
47
|
+
let ep;
|
|
48
|
+
try {
|
|
49
|
+
// Security H3: this is a per-eviction credential for ONE ~15s delivery-admin call, not a
|
|
50
|
+
// standing connection. The `supervisor` profile is default standing-renewable (24h); mint it
|
|
51
|
+
// with a tight TTL so a COPIED credential expires almost immediately instead of carrying a
|
|
52
|
+
// day of full manager-control-tier + lease/presence authority. (The dedicated narrow
|
|
53
|
+
// delivery-admin infra profile is the #30 target; until then, bound the lifetime here.)
|
|
54
|
+
const creds = await mintCreds(auth, id, "supervisor", { expiresInSeconds: EVICTOR_CRED_TTL_SECONDS });
|
|
55
|
+
ep = new CotalEndpoint({
|
|
56
|
+
space: opts.space,
|
|
57
|
+
servers: opts.server,
|
|
58
|
+
creds,
|
|
59
|
+
card: { id: id.id, name: "auth-barrier-evict", kind: "endpoint" },
|
|
60
|
+
channels: [],
|
|
61
|
+
consume: false,
|
|
62
|
+
watchChannels: false,
|
|
63
|
+
watchPresence: false,
|
|
64
|
+
registerPresence: false,
|
|
65
|
+
});
|
|
66
|
+
ep.on("error", () => { });
|
|
67
|
+
await ep.start();
|
|
68
|
+
const r = await ep.requestDeliveryAdmin("evictPrincipal", { principal }, 15_000);
|
|
69
|
+
if (!r.ok)
|
|
70
|
+
return failClosed(principal, `the delivery daemon refused the eviction: ${r.error ?? "(no error copy)"}`);
|
|
71
|
+
const d = r.data;
|
|
72
|
+
if (d === undefined || d === null || d.principal !== principal ||
|
|
73
|
+
typeof d.kicked !== "number" || !Number.isSafeInteger(d.kicked) || d.kicked < 0 ||
|
|
74
|
+
typeof d.remaining !== "number" || !Number.isSafeInteger(d.remaining) || d.remaining < 0 ||
|
|
75
|
+
typeof d.verifiedGone !== "boolean" || typeof d.scanComplete !== "boolean")
|
|
76
|
+
return failClosed(principal, `the delivery daemon returned a garbled or foreign eviction result (${JSON.stringify(r.data ?? null)}); a result that does not verifiably describe this principal never authorizes`);
|
|
77
|
+
// An internally CONTRADICTORY success never authorizes: the barriers gate on
|
|
78
|
+
// `verifiedGone === true` alone, so a tuple claiming verified-gone while admitting an
|
|
79
|
+
// incomplete scan or surviving connections would launder a partial answer into an epoch
|
|
80
|
+
// advance / head terminal. Verified-gone is conclusive only as (scan complete, none remain).
|
|
81
|
+
if (d.verifiedGone === true && (d.scanComplete !== true || d.remaining !== 0))
|
|
82
|
+
return failClosed(principal, `the delivery daemon claimed verifiedGone with scanComplete=${String(d.scanComplete)} and remaining=${d.remaining}; an internally contradictory eviction result never authorizes`);
|
|
83
|
+
return {
|
|
84
|
+
principal,
|
|
85
|
+
kicked: d.kicked,
|
|
86
|
+
remaining: d.remaining,
|
|
87
|
+
verifiedGone: d.verifiedGone,
|
|
88
|
+
scanComplete: d.scanComplete,
|
|
89
|
+
...(typeof d.note === "string" ? { note: d.note } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return failClosed(principal, `the delivery-admin rail is unreachable (${e instanceof Error ? e.message : String(e)}); eviction is UNKNOWN and the barrier fails closed`);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await ep?.stop().catch(() => { });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=barrier-evict.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"barrier-evict.js","sourceRoot":"","sources":["../src/barrier-evict.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The connect-time credential reader — the R1 "deny-new" substrate. At every connect the callout
|
|
3
|
+
* must prove the presented bearer's `credentialId` still names a LIVE `cred.<uid>.<credid>` row,
|
|
4
|
+
* so that revoking that row denies the NEXT connect (the v1 revocation lever that actually bites).
|
|
5
|
+
*
|
|
6
|
+
* The credential rows and the lifecycle head live in the DATA account, but the callout runs on the
|
|
7
|
+
* CALLOUT account, and NATS accounts are isolated — so the auth service opens ONE standing
|
|
8
|
+
* data-account reader connection (self-minted from the `dataAccount` signing seed it already
|
|
9
|
+
* holds; see {@link authConnectReaderGrants}) and this module reads through it.
|
|
10
|
+
*
|
|
11
|
+
* Two authority stores, both leader-served and both shape-proved on EVERY (re)bind (SPEC 13.12):
|
|
12
|
+
* - `cred.<uid>.<credid>` lives in `KV_cotal_auth_<space>` (`allow_direct=false`);
|
|
13
|
+
* - the lifecycle head `lifecycle.<owner>.<actor>` (carrying `currentCredentialId`) lives in
|
|
14
|
+
* `KV_cotal_records_<space>`.
|
|
15
|
+
* A follower/mirror can serve a STALE row that still reads `active` after a revoke, silently
|
|
16
|
+
* defeating deny-new, so reads are leader-served `STREAM.MSG.GET` (never `DIRECT.GET`) AND the
|
|
17
|
+
* store is proved primary/un-mirrored/non-evicting at bind. Any read/shape/parse failure DENIES
|
|
18
|
+
* the connect; there is no file-only fallback.
|
|
19
|
+
*/
|
|
20
|
+
import type { NatsConnection } from "@nats-io/transport-node";
|
|
21
|
+
import type { ValidatedUserToken } from "./token.js";
|
|
22
|
+
/**
|
|
23
|
+
* The connect reader's SCOPED credential grant (SPEC 13.9). Read-only over BOTH authority stores,
|
|
24
|
+
* plus the bind-time shape-proof INFO and a connection-scoped reply inbox. It holds NO write, NO
|
|
25
|
+
* consumer create/update/delete, NO `DIRECT.GET`, and no account-wide inbox.
|
|
26
|
+
*
|
|
27
|
+
* D32 residual (named, not pretend-confined): `$JS.API.STREAM.MSG.GET.<stream>` is body-selected —
|
|
28
|
+
* broker ACLs cannot scope it to only `cred.`/head subjects, so this grant is a space-wide read of
|
|
29
|
+
* credential METADATA (holderPrincipal/state/chain/lifecycleUid/exp; NO secret key material rides
|
|
30
|
+
* the row, confirmed against the closed `CredentialLedgerRow` schema) plus the caller-selected-reply
|
|
31
|
+
* injection class every raw `MSG.GET` profile carries. Acceptable for this trusted, bounded-lived,
|
|
32
|
+
* short-exp+renewed infra reader; a genuinely ledgered infra-reader family is the deferred
|
|
33
|
+
* infra-mint work (a self-minted reader is revoked by stopping the service / rotating the seed, not
|
|
34
|
+
* an agent-barrier revoke — it is not a managed-agent or endpoint descendant).
|
|
35
|
+
*/
|
|
36
|
+
export declare function authConnectReaderGrants(space: string, connId: string): {
|
|
37
|
+
publish: string[];
|
|
38
|
+
subscribe: string[];
|
|
39
|
+
};
|
|
40
|
+
/** A sealed, brand-checked connect reader (a hand-assembled context never authorizes). */
|
|
41
|
+
export interface ConnectReader {
|
|
42
|
+
readonly space: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Open (or re-open, on reconnect) the connect reader on a DATA-account connection, shape-proving
|
|
46
|
+
* BOTH stores at bind (SPEC 13.12) — primary/un-mirrored/non-evicting, and the auth store
|
|
47
|
+
* additionally `allow_direct=false`. Call this on every (re)bind: a dropped reader reconnecting
|
|
48
|
+
* could land on a mirror/follower, so the primary-store proof must re-run before it serves any
|
|
49
|
+
* connect read again. A store that cannot be proved never serves.
|
|
50
|
+
*/
|
|
51
|
+
export declare function openConnectReader(nc: NatsConnection, space: string): Promise<ConnectReader>;
|
|
52
|
+
/**
|
|
53
|
+
* The connect-time deny-new check (SPEC 13.1). Assumes the caller (the ledger connect authorizer)
|
|
54
|
+
* has ALREADY run the actor-row + scope + lifecycle-equality checks; this adds the credential-row
|
|
55
|
+
* liveness proof. THROWS to deny; returns on allow. Fail-closed throughout: absent/revoked/expired/
|
|
56
|
+
* mismatched rows and every read failure deny.
|
|
57
|
+
*
|
|
58
|
+
* 1. the bearer MUST carry `act.credentialId` (grammar-valid from validateUserToken). Claimless =>
|
|
59
|
+
* deny (deny-new needs the claim; a pre-R1 bearer re-exchanges). A view bearer is NOT exempt;
|
|
60
|
+
* a bearer merely lacking `act.view` is a normal agent connect and is fine.
|
|
61
|
+
* 2. leader-read `cred.<lifecycleUid>.<credid>`; require it exist, be `active`, be unexpired under
|
|
62
|
+
* the service clock, and bind holderPrincipal `<owner>.<actor>` + lifecycleUid + credentialId
|
|
63
|
+
* with NO normalization slack.
|
|
64
|
+
* 3. root-issued credential (`sourceChain === ["root"]`): ALSO leader-read the lifecycle head and
|
|
65
|
+
* require it `active` with `currentCredentialId === credid`, so a SUPERSEDED root issuance is
|
|
66
|
+
* denied even if its old row still reads active (release-last keeps these consistent; head
|
|
67
|
+
* equality is the belt). A descendant (session/handle) leaf row is sufficient here: revoking an
|
|
68
|
+
* ancestor revokes the whole `cred.<uid>.>` family / `bysrc` chain and verified-evicts before
|
|
69
|
+
* the operation reports success, and after the leaf CAS deny-new holds.
|
|
70
|
+
*/
|
|
71
|
+
export declare function authorizeConnectCredential(reader: ConnectReader, t: ValidatedUserToken, now: () => number): Promise<void>;
|
|
72
|
+
//# sourceMappingURL=connect-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-reader.d.ts","sourceRoot":"","sources":["../src/connect-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAK9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAiBjH;AAQD,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmBjG;AAgCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC/H"}
|