@tinycloud/share-sdk 1.2.0-beta.2 → 1.2.0-beta.3
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/share-root.cjs +667 -294
- package/dist/share-root.cjs.map +1 -1
- package/dist/share-root.d.cts +340 -194
- package/dist/share-root.d.ts +340 -194
- package/dist/share-root.js +656 -287
- package/dist/share-root.js.map +1 -1
- package/package.json +1 -1
package/dist/share-root.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompactUcanAuthorization } from '@tinycloud/share-envelope';
|
|
1
|
+
import { CompactUcanAuthorization, UnifiedContentSource } from '@tinycloud/share-envelope';
|
|
2
2
|
import { C as CreateOwnerDelegationParams, r as OwnerDelegationReceipt, A as AddressedOwnerRootInput, i as AddressedOwnerRootReceipt, D as DelegatedShareKey, l as AddressedPublishAuthority } from './addressed-publish-CsUS7-SI.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -41,47 +41,28 @@ declare const SHARE_ROOT_PREFIX = "xyz.tinycloud.share/shares/";
|
|
|
41
41
|
declare const SHARE_ROOT_MAX_LIFETIME_MS: number;
|
|
42
42
|
/** Longest lifetime of an agent root (the OpenKey device-grant cap). */
|
|
43
43
|
declare const AGENT_SHARE_ROOT_MAX_LIFETIME_MS: number;
|
|
44
|
-
/** Product cap on a bearer link
|
|
44
|
+
/** Product cap on a bearer link, a per-share grant, or a policy root. */
|
|
45
45
|
declare const SHARE_ROOT_LINK_MAX_LIFETIME_MS: number;
|
|
46
46
|
/**
|
|
47
|
-
* Signer clock skew tolerated on a signed
|
|
48
|
-
* `exp - now` stays strict), so an approval is not wasted when the
|
|
49
|
-
* sets its own issue time.
|
|
47
|
+
* Signer clock skew tolerated on a signed delegation's `exp - iat` (only
|
|
48
|
+
* there: `exp - now` stays strict), so an approval is not wasted when the
|
|
49
|
+
* signer sets its own issue time.
|
|
50
50
|
*/
|
|
51
51
|
declare const SHARE_ROOT_CLOCK_SKEW_MS: number;
|
|
52
52
|
/** `ensureShareRoot` asks for the cap minus this margin, so a clamping or slow signer stays inside the cap. */
|
|
53
53
|
declare const SHARE_ROOT_REQUEST_MARGIN_MS: number;
|
|
54
|
-
/**
|
|
55
|
-
* NODE-B2 (§9 "Advertised mode"): a Node lists these in the `features` of
|
|
56
|
-
* its public `GET /info` (sdk-core `checkNodeInfo` → `NodeInfo.features`):
|
|
57
|
-
* the capability marker plus exactly one active mode. Read in one place,
|
|
58
|
-
* {@link readPolicyOwnerAuthorityMode}; the Node version is never consulted.
|
|
59
|
-
*/
|
|
60
|
-
declare const POLICY_OWNER_AUTHORITY_FEATURES: {
|
|
61
|
-
readonly marker: "policy-owner-authority-v1";
|
|
62
|
-
readonly record: "policy-owner-authority-record";
|
|
63
|
-
readonly enforce: "policy-owner-authority-enforce";
|
|
64
|
-
};
|
|
65
|
-
/** How long a known mode is trusted before the Node is asked again (NODE-B2: at most 5 minutes). */
|
|
66
|
-
declare const POLICY_OWNER_AUTHORITY_PROBE_TTL_MS: number;
|
|
67
|
-
/** `share`: Share/CLI root in `applications`. `agent`: `default` space, bearer only. */
|
|
54
|
+
/** `share`: Share/CLI root in `applications`. `agent`: `default` space. Both are kv/bearer only. */
|
|
68
55
|
type ShareRootKind = "share" | "agent";
|
|
69
|
-
|
|
70
|
-
* What the owner signed. `kv`: get + metadata on the share prefix only.
|
|
71
|
-
* `kv+share-decrypt`: also decrypt on the owner's `:share` network, which a
|
|
72
|
-
* policy owner needs; minted only while the Node advertises `enforce` and
|
|
73
|
-
* `shareRootPolicyRoots` is on.
|
|
74
|
-
*/
|
|
75
|
-
type ShareRootScope = "kv" | "kv+share-decrypt";
|
|
76
|
-
type ShareRootErrorCode = "invalid-request" | "invalid-key" | "key-cleared" | "lifetime-exceeds-cap" | "expired" | "malformed-root" | "issuer-mismatch" | "audience-mismatch" | "resource-mismatch" | "ability-mismatch" | "expiry-mismatch" | "reroot-same-key" | "link-out-of-scope" | "policy-roots-disabled" | "policy-roots-not-enforced-by-node" | "root-not-policy-capable";
|
|
56
|
+
type ShareRootErrorCode = "invalid-request" | "invalid-key" | "key-cleared" | "lifetime-exceeds-cap" | "expired" | "malformed-root" | "issuer-mismatch" | "audience-mismatch" | "resource-mismatch" | "ability-mismatch" | "expiry-mismatch" | "reroot-same-key" | "link-out-of-scope" | "share-id-not-network-safe" | "network-not-active" | "node-refused" | "grant-revoke-incomplete" | "share-id-in-use" | "control-target-invalid";
|
|
77
57
|
interface ShareRootErrorDetails {
|
|
78
58
|
/** The delegation CID the Node imported (the receipt's), when the owner already signed. */
|
|
79
59
|
readonly rootCid?: string;
|
|
80
60
|
/** CID of the signed bytes, when it differs from `rootCid`. */
|
|
81
61
|
readonly signedCid?: string;
|
|
82
|
-
/** The imported
|
|
62
|
+
/** The imported delegation may grant more, or longer, than requested: the owner must revoke `rootCid`. */
|
|
83
63
|
readonly mustRevoke?: boolean;
|
|
84
64
|
}
|
|
65
|
+
/** The error every share-root, per-share grant and owner-control operation throws. */
|
|
85
66
|
declare class ShareRootError extends Error {
|
|
86
67
|
readonly code: ShareRootErrorCode;
|
|
87
68
|
readonly rootCid?: string;
|
|
@@ -98,26 +79,16 @@ interface ShareRootOwnerAuthority {
|
|
|
98
79
|
}
|
|
99
80
|
interface ShareRootOptions {
|
|
100
81
|
readonly kind?: ShareRootKind;
|
|
101
|
-
/**
|
|
102
|
-
* Feature flag `shareRootPolicyRoots`, off by default. A root gains `:share`
|
|
103
|
-
* decrypt, and can sign policy roots through {@link ShareRoot.policyRootSigner},
|
|
104
|
-
* only while this is on and the root's Node advertises owner-authority
|
|
105
|
-
* mode `enforce`.
|
|
106
|
-
*/
|
|
107
|
-
readonly shareRootPolicyRoots?: boolean;
|
|
108
|
-
/** Defaults to {@link defaultPolicyOwnerModeProbe}. */
|
|
109
|
-
readonly policyOwnerModeProbe?: PolicyOwnerModeProbe;
|
|
110
82
|
readonly now?: () => number;
|
|
111
83
|
}
|
|
112
84
|
/** Persistable root metadata. Holds no key material: the key lives in its own store. */
|
|
113
85
|
interface ShareRootRecord {
|
|
114
86
|
readonly version: 1;
|
|
115
87
|
readonly kind: ShareRootKind;
|
|
116
|
-
|
|
117
|
-
/** The Node the root was created against; its advertised mode gates policy signing. */
|
|
88
|
+
/** The Node the root was created against. */
|
|
118
89
|
readonly nodeOrigin: string;
|
|
119
90
|
readonly cid: string;
|
|
120
|
-
/** Base64url of the exact owner-signed CACAO bytes. */
|
|
91
|
+
/** Base64url (unpadded) of the exact owner-signed CACAO bytes. */
|
|
121
92
|
readonly authorization: string;
|
|
122
93
|
readonly spaceId: string;
|
|
123
94
|
readonly ownerDid: string;
|
|
@@ -133,68 +104,33 @@ interface ShareRootLink {
|
|
|
133
104
|
readonly delegation: CompactUcanAuthorization;
|
|
134
105
|
}
|
|
135
106
|
/**
|
|
136
|
-
* Policy/v3 owner signing for
|
|
137
|
-
* `AddressedPublishAuthority` without `registerPolicy`. Every call
|
|
138
|
-
*
|
|
139
|
-
* live, key present), so a signer kept past a mode change stops working.
|
|
107
|
+
* Policy/v3 owner signing for one per-share grant. Shaped like
|
|
108
|
+
* `AddressedPublishAuthority` without `registerPolicy`. Every call checks the
|
|
109
|
+
* key is held and the grant is live.
|
|
140
110
|
*/
|
|
141
|
-
interface
|
|
142
|
-
/** The share key DID: the `ownerDid` of the policy roots. */
|
|
111
|
+
interface SharePolicySigner {
|
|
112
|
+
/** The per-share key DID: the `ownerDid` of the policy roots. */
|
|
143
113
|
readonly ownerDid: string;
|
|
114
|
+
/** The owner delegation (grant) CID that authorizes the key. */
|
|
144
115
|
readonly rootCid: string;
|
|
145
|
-
/**
|
|
116
|
+
/** No policy root may outlive the grant. */
|
|
146
117
|
readonly notAfter: Date;
|
|
147
118
|
/** Build and sign one proofless policy root; its expiry must be within `notAfter` and 31 days. */
|
|
148
119
|
createOwnerRoot(input: AddressedOwnerRootInput): Promise<AddressedOwnerRootReceipt>;
|
|
149
120
|
/** Sign a 32-byte digest (policy or envelope); nothing else. */
|
|
150
121
|
sign(digest: Uint8Array): Promise<Uint8Array>;
|
|
151
122
|
}
|
|
152
|
-
/**
|
|
153
|
-
* The single adapter over the Node's advertisement. Takes the `features`
|
|
154
|
-
* list of `GET /info` and returns `"enforce"` only for the marker plus the
|
|
155
|
-
* `-enforce` mode and no `-record`; `"record"` for the marker plus
|
|
156
|
-
* `-record` only; `undefined` for anything else (no marker, as on every
|
|
157
|
-
* Node up to 1.21.0, both modes, or a malformed list). Only `"enforce"`
|
|
158
|
-
* enables policy-capable roots.
|
|
159
|
-
*/
|
|
160
|
-
declare function readPolicyOwnerAuthorityMode(features: unknown): "enforce" | "record" | undefined;
|
|
161
|
-
/** Per-Node cache of "does this Node enforce owner-authority expiry". */
|
|
162
|
-
interface PolicyOwnerModeProbe {
|
|
163
|
-
/** True only when the Node currently advertises `enforce`. Never throws. */
|
|
164
|
-
isEnforced(nodeOrigin: string): Promise<boolean>;
|
|
165
|
-
/**
|
|
166
|
-
* Record the `features` a sign-in just read from `GET /info`
|
|
167
|
-
* (`NodeInfo.features`), so each sign-in refreshes the answer without a
|
|
168
|
-
* second fetch.
|
|
169
|
-
*/
|
|
170
|
-
observe(nodeOrigin: string, features: readonly string[]): void;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Keep each Node's answer in memory for `ttlMs` (default and ceiling 5
|
|
174
|
-
* minutes), then read `GET /info` again, so a Node that stops advertising
|
|
175
|
-
* `enforce` turns the gate off within one TTL. Only `https:` origins (and
|
|
176
|
-
* `http:` on loopback) are probed. A failure, a non-JSON or oversized
|
|
177
|
-
* (64 KiB) answer, or a malformed list counts as not enforcing and is cached
|
|
178
|
-
* for the same TTL.
|
|
179
|
-
*/
|
|
180
|
-
declare function createPolicyOwnerModeProbe(options?: {
|
|
181
|
-
readonly fetch?: (input: URL, init: RequestInit) => Promise<Response>;
|
|
182
|
-
readonly ttlMs?: number;
|
|
183
|
-
readonly now?: () => number;
|
|
184
|
-
}): PolicyOwnerModeProbe;
|
|
185
|
-
/** Process-wide default; sign-in code should feed it `NodeInfo.features` through `observe`. */
|
|
186
|
-
declare const defaultPolicyOwnerModeProbe: PolicyOwnerModeProbe;
|
|
187
123
|
/**
|
|
188
124
|
* A wallet-signed, read-only root delegated to one non-extractable share key,
|
|
189
|
-
* so
|
|
125
|
+
* so bearer links outlive the owner's login session (TC-1423 D3, option B').
|
|
190
126
|
*
|
|
191
|
-
* - Scope: `kv/get` + `kv/metadata` on the share prefix
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* policy over
|
|
196
|
-
* `policy-owner-not-authorized`). Never put, del, list,
|
|
197
|
-
* `network.create` or
|
|
127
|
+
* - Scope: `kv/get` + `kv/metadata` on the share prefix, and nothing else.
|
|
128
|
+
* The root never decrypts, before or after the Node enforces owner-authority
|
|
129
|
+
* binding: every addressed (email/domain) or edit share uses its own
|
|
130
|
+
* per-share grant and network (`ShareGrant`). Without decrypt, the key
|
|
131
|
+
* cannot own a policy over encrypted content (the Node answers
|
|
132
|
+
* `policy-owner-not-authorized`). Never put, del, list,
|
|
133
|
+
* `capabilities/read`, `network.create` or any encryption network.
|
|
198
134
|
* - Created only through `createOwnerDelegation({ permissions })`; the signed
|
|
199
135
|
* bytes are decoded and checked here.
|
|
200
136
|
* - Lifetime: at most 37 days (`agent`: 30). One key per root.
|
|
@@ -203,14 +139,10 @@ declare const defaultPolicyOwnerModeProbe: PolicyOwnerModeProbe;
|
|
|
203
139
|
* share key requires the composer to be on its own origin first (REVIEW-D3
|
|
204
140
|
* V2-2); this package provides none. CLI profiles may keep a key in a 0600
|
|
205
141
|
* file and rebuild with `restore`.
|
|
206
|
-
* - The client flag and `policyRootSigner` gate only this API: whoever holds
|
|
207
|
-
* the key can sign through other APIs. What bounds that is the scope of the
|
|
208
|
-
* root (no decrypt before `enforce`) and the Node.
|
|
209
142
|
*/
|
|
210
143
|
declare class ShareRoot {
|
|
211
144
|
#private;
|
|
212
145
|
readonly kind: ShareRootKind;
|
|
213
|
-
readonly scope: ShareRootScope;
|
|
214
146
|
readonly nodeOrigin: string;
|
|
215
147
|
readonly cid: string;
|
|
216
148
|
readonly ownerDid: string;
|
|
@@ -220,12 +152,10 @@ declare class ShareRoot {
|
|
|
220
152
|
readonly expiresAt: Date;
|
|
221
153
|
private constructor();
|
|
222
154
|
/**
|
|
223
|
-
* Ask the owner for a new root (one approval) for a fresh share key.
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* the key is cleared and the error carries the imported `rootCid` (and
|
|
228
|
-
* `mustRevoke` when the root may be wider than requested).
|
|
155
|
+
* Ask the owner for a new root (one approval) for a fresh share key. All
|
|
156
|
+
* request checks run before anything is signed. If the signed result is
|
|
157
|
+
* rejected, the key is cleared and the error carries the imported `rootCid`
|
|
158
|
+
* (and `mustRevoke` when the root may be wider than requested).
|
|
229
159
|
*
|
|
230
160
|
* `keyFactory` defaults to a fresh non-extractable WebCrypto key that only
|
|
231
161
|
* this root holds. A custom factory must not persist the key before
|
|
@@ -245,10 +175,12 @@ declare class ShareRoot {
|
|
|
245
175
|
readonly key: DelegatedShareKey;
|
|
246
176
|
readonly options?: ShareRootOptions;
|
|
247
177
|
}): Promise<ShareRoot>;
|
|
248
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* This root's CACAO as base64url, UNPADDED (the record form). As a Node
|
|
180
|
+
* `Authorization` header it must be padded to a multiple of 4 characters:
|
|
181
|
+
* the Node refuses an unpadded value otherwise.
|
|
182
|
+
*/
|
|
249
183
|
get authorization(): string;
|
|
250
|
-
/** True when the root holds `:share` decrypt and may act as a policy owner (still gated per call). */
|
|
251
|
-
get policyCapable(): boolean;
|
|
252
184
|
toRecord(): ShareRootRecord;
|
|
253
185
|
/** True while the root is live, its key is held, and it lasts at least until `expiresAt`. Revocation is not visible here. */
|
|
254
186
|
covers(expiresAt: Date, now?: number): boolean;
|
|
@@ -262,27 +194,17 @@ declare class ShareRoot {
|
|
|
262
194
|
readonly path: string;
|
|
263
195
|
readonly expiresAt: Date;
|
|
264
196
|
}): Promise<ShareRootLink>;
|
|
265
|
-
/**
|
|
266
|
-
* The only ShareRoot API that signs Policy/v3 (email/domain) owner material.
|
|
267
|
-
* Needs a `kv+share-decrypt` root, the `shareRootPolicyRoots` flag, a live
|
|
268
|
-
* root and key, and the root's Node advertising `enforce`; the returned
|
|
269
|
-
* signer re-checks all of it on every call. Until then addressed shares use
|
|
270
|
-
* per-share owner grants. Agent roots are bearer-only.
|
|
271
|
-
*/
|
|
272
|
-
policyRootSigner(): Promise<ShareRootPolicySigner>;
|
|
273
197
|
/** Drop this root's key handle. It does not revoke the root: only the owner does that. */
|
|
274
198
|
clear(): void;
|
|
275
199
|
}
|
|
276
200
|
/**
|
|
277
|
-
* Re-root only when needed
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* `kv`; it never hands back a `kv` root. A re-root costs one approval, uses a
|
|
282
|
-
* new key and requests the kind's cap minus {@link SHARE_ROOT_REQUEST_MARGIN_MS}.
|
|
201
|
+
* Re-root only when needed: keep a live root of the same kind, space and
|
|
202
|
+
* Node that covers `expiresAt`; otherwise mint one new root (one approval)
|
|
203
|
+
* with a new key, requesting the kind's cap minus
|
|
204
|
+
* {@link SHARE_ROOT_REQUEST_MARGIN_MS}.
|
|
283
205
|
*
|
|
284
|
-
* - Links
|
|
285
|
-
*
|
|
206
|
+
* - Links signed under the old root stay valid until their own expiry; keep
|
|
207
|
+
* the old root (and its key) to revoke them (REVIEW-D3 V2-5).
|
|
286
208
|
* - Concurrent calls each re-root (two approvals, and one orphaned root if
|
|
287
209
|
* both write the same store slot): callers serialize, e.g. with a lock.
|
|
288
210
|
* - A revoked root still "covers" here: re-root when `/delegate` answers
|
|
@@ -291,7 +213,6 @@ declare class ShareRoot {
|
|
|
291
213
|
declare function ensureShareRoot(input: {
|
|
292
214
|
readonly current?: ShareRoot;
|
|
293
215
|
readonly expiresAt: Date;
|
|
294
|
-
readonly purpose?: "bearer" | "policy";
|
|
295
216
|
readonly owner: ShareRootOwnerAuthority;
|
|
296
217
|
readonly spaceId: string;
|
|
297
218
|
readonly nodeOrigin: string;
|
|
@@ -342,9 +263,33 @@ declare function signShareKeyDelegation(input: ShareKeyDelegationInput): Promise
|
|
|
342
263
|
|
|
343
264
|
/** `view`: kv get + metadata. `edit`: also kv put. Both on one exact key. */
|
|
344
265
|
type ShareGrantAccess = "view" | "edit";
|
|
345
|
-
/**
|
|
266
|
+
/** P2(a): a read-back key lives at most one hour. */
|
|
267
|
+
declare const EDIT_SHARE_READER_MAX_LIFETIME_MS: number;
|
|
268
|
+
type FetchFn$1 = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
269
|
+
/**
|
|
270
|
+
* The share's one content key (I1): `xyz.tinycloud.share/shares/<shareId>/<file>`,
|
|
271
|
+
* or, for `view` only, a TC-1428 version file
|
|
272
|
+
* `xyz.tinycloud.share/artifacts/<aid>/versions/<n>/<file>` (versions are
|
|
273
|
+
* immutable, and that path is not bound to the shareId, so `edit` is refused).
|
|
274
|
+
*/
|
|
275
|
+
declare function assertShareGrantPath(path: string, shareId: string, access: ShareGrantAccess): void;
|
|
276
|
+
/** Progress of one addressed share, for the consumer's history/index row (the SDK does not store rows). */
|
|
277
|
+
type ShareGrantRowState = "pending" | "granted" | "network" | "active" | "abandoned" | "retired";
|
|
278
|
+
/** The history/index row to persist at each step. Write `pending` BEFORE the approval prompt. */
|
|
279
|
+
interface ShareGrantRow {
|
|
280
|
+
readonly shareId: string;
|
|
281
|
+
/** Network name, `share-<shareId>`. */
|
|
282
|
+
readonly nw: string;
|
|
283
|
+
readonly networkId: string;
|
|
284
|
+
readonly keyDid: string;
|
|
285
|
+
readonly access: ShareGrantAccess;
|
|
286
|
+
readonly path: string;
|
|
287
|
+
readonly state: ShareGrantRowState;
|
|
288
|
+
readonly grantCid?: string;
|
|
289
|
+
}
|
|
290
|
+
/** Persistable grant metadata (record v2). Holds no key material. */
|
|
346
291
|
interface ShareGrantRecord {
|
|
347
|
-
readonly version:
|
|
292
|
+
readonly version: 2;
|
|
348
293
|
readonly cid: string;
|
|
349
294
|
/** Base64url of the exact owner-signed CACAO bytes. */
|
|
350
295
|
readonly authorization: string;
|
|
@@ -352,38 +297,89 @@ interface ShareGrantRecord {
|
|
|
352
297
|
readonly ownerDid: string;
|
|
353
298
|
readonly keyDid: string;
|
|
354
299
|
readonly nodeOrigin: string;
|
|
355
|
-
|
|
300
|
+
readonly shareId: string;
|
|
301
|
+
readonly networkId: string;
|
|
356
302
|
readonly path: string;
|
|
357
303
|
readonly access: ShareGrantAccess;
|
|
358
304
|
readonly expiresAt: string;
|
|
359
305
|
}
|
|
306
|
+
/** What one addressed or edit share asks the owner for. */
|
|
307
|
+
interface ShareGrantRequest {
|
|
308
|
+
readonly owner: ShareRootOwnerAuthority;
|
|
309
|
+
/** The owner's `applications` space. */
|
|
310
|
+
readonly spaceId: string;
|
|
311
|
+
readonly nodeOrigin: string;
|
|
312
|
+
/** Names the network `share-<shareId>`: must match `^[a-z0-9][a-z0-9-]{15,35}$`. */
|
|
313
|
+
readonly shareId: string;
|
|
314
|
+
/** The share's one content key (see `assertShareGrantPath`). */
|
|
315
|
+
readonly path: string;
|
|
316
|
+
readonly access: ShareGrantAccess;
|
|
317
|
+
/** At most 30 days ahead. */
|
|
318
|
+
readonly expiresAt: Date;
|
|
319
|
+
/**
|
|
320
|
+
* Key-gone recovery only: accept the share's network when it already
|
|
321
|
+
* exists and is `active` (a half-published share whose key was lost), and
|
|
322
|
+
* grant a new key on it. Without it, `prepare` refuses an existing network
|
|
323
|
+
* (`share-id-in-use`) before the prompt. A retired network is always
|
|
324
|
+
* refused.
|
|
325
|
+
*/
|
|
326
|
+
readonly resumeExistingNetwork?: boolean;
|
|
327
|
+
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
328
|
+
readonly fetch?: FetchFn$1;
|
|
329
|
+
readonly now?: () => number;
|
|
330
|
+
}
|
|
331
|
+
/** A per-share grant whose key exists but which the owner has not approved yet. Persist `row` first, then `approve()`. */
|
|
332
|
+
interface PendingShareGrant {
|
|
333
|
+
readonly shareId: string;
|
|
334
|
+
readonly networkId: string;
|
|
335
|
+
readonly keyDid: string;
|
|
336
|
+
/** The `pending` row: write it before calling `approve()`. */
|
|
337
|
+
readonly row: ShareGrantRow;
|
|
338
|
+
/** Ask the owner for the grant: exactly one approval. */
|
|
339
|
+
approve(): Promise<ShareGrant>;
|
|
340
|
+
/** Drop the key without asking (e.g. the row write failed). */
|
|
341
|
+
discard(): void;
|
|
342
|
+
}
|
|
360
343
|
/**
|
|
361
|
-
* A per-share owner grant
|
|
362
|
-
*
|
|
344
|
+
* A per-share owner grant on the share's own encryption network (TC-1423,
|
|
345
|
+
* PERSHARE-NETWORK design v2). Every addressed (email/domain) or edit share
|
|
346
|
+
* gets one, before and after the Node enforces owner-authority binding; the
|
|
347
|
+
* share root never decrypts.
|
|
363
348
|
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* -
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* The per-share key then owns that one share's policy: `publishAuthority`
|
|
374
|
-
* signs its two policy roots (bounded by the grant's expiry and exact path)
|
|
375
|
-
* and its 32-byte policy and envelope digests.
|
|
349
|
+
* One owner approval signs one CACAO to a FRESH non-extractable per-share key:
|
|
350
|
+
* - kv `get` + `metadata` (+ `put` for `edit`) on the share's one content key;
|
|
351
|
+
* - `network.create`, `network.revoke` and `decrypt` on exactly
|
|
352
|
+
* `urn:tinycloud:encryption:<owner did:pkh>:share-<shareId>`, and nothing
|
|
353
|
+
* else (the Node matches network resources by exact parsed id, so these
|
|
354
|
+
* reach no other network).
|
|
355
|
+
* The key creates that network (owned by the owner) and owns the share's
|
|
356
|
+
* policy. Decrypt therefore covers this one share's ciphertext only, for the
|
|
357
|
+
* key and for the share's recipients.
|
|
376
358
|
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
359
|
+
* Publish order (the consumer stores the rows; see `ShareGrantRow`):
|
|
360
|
+
* 0. `ShareGrant.prepare`: shareId rule, path, access, expiry, space and
|
|
361
|
+
* origin are checked, and the network is looked up (an existing or
|
|
362
|
+
* retired one is refused); a typed error costs no approval.
|
|
363
|
+
* 1. Persist `pending.row` (state `pending`), and a CLI persists the key file.
|
|
364
|
+
* 2. `pending.approve()`: one approval; row `granted` with `grantCid`.
|
|
365
|
+
* 3. `grant.ensureNetwork()`: creates the network if absent, re-reads it after
|
|
366
|
+
* any answer (racing creates can answer 500), accepts only `active`; row
|
|
367
|
+
* `network`. Retry at no approval while the key is held.
|
|
368
|
+
* 4. The owner session encrypts to `grant.networkId` and stores the object
|
|
369
|
+
* (`grant.assertContentSource`); on failure `grant.retireNetwork()`, row
|
|
370
|
+
* `abandoned`.
|
|
371
|
+
* 5. `publishAddressedShare({ authority: grant.publishAuthority(registerPolicy) })`:
|
|
372
|
+
* the network is re-read as `active` right before registration.
|
|
373
|
+
* 6. Row `active`.
|
|
382
374
|
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
375
|
+
* Revoke: `revokeShareGrant` retires the network first (permanent; every
|
|
376
|
+
* recipient loses decrypt at the next request; no approval while the key is
|
|
377
|
+
* held), then revokes the grant (one owner approval). Revoking the grant
|
|
378
|
+
* first would strand an active network the dead key can no longer retire.
|
|
379
|
+
*
|
|
380
|
+
* Key custody: no browser persistence (needs the composer on its own origin
|
|
381
|
+
* first, REVIEW-D3 V2-2); keep the grant in memory, or in a CLI 0600 key file
|
|
382
|
+
* via `restore`, until the share is retired or expired.
|
|
387
383
|
*/
|
|
388
384
|
declare class ShareGrant {
|
|
389
385
|
#private;
|
|
@@ -392,87 +388,237 @@ declare class ShareGrant {
|
|
|
392
388
|
readonly spaceId: string;
|
|
393
389
|
readonly keyDid: string;
|
|
394
390
|
readonly nodeOrigin: string;
|
|
391
|
+
readonly shareId: string;
|
|
392
|
+
readonly networkId: string;
|
|
395
393
|
readonly path: string;
|
|
396
394
|
readonly access: ShareGrantAccess;
|
|
397
395
|
readonly issuedAt: Date;
|
|
398
396
|
readonly expiresAt: Date;
|
|
399
397
|
private constructor();
|
|
400
398
|
/**
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
399
|
+
* Step 0: check everything that does not need the owner, and make the key.
|
|
400
|
+
* Also looks the share's network up (no approval): a shareId whose network
|
|
401
|
+
* is retired (burned), or already exists from an earlier grant, is refused
|
|
402
|
+
* with `share-id-in-use` before the prompt (see `resumeExistingNetwork`).
|
|
403
|
+
* Throws `share-id-not-network-safe`, `invalid-request`, `lifetime-exceeds-cap`,
|
|
404
|
+
* `share-id-in-use`, `node-refused` or `invalid-key` with no approval spent.
|
|
405
405
|
*/
|
|
406
|
-
static
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
readonly path: string;
|
|
411
|
-
readonly access: ShareGrantAccess;
|
|
412
|
-
readonly expiresAt: Date;
|
|
413
|
-
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
414
|
-
readonly now?: () => number;
|
|
415
|
-
}): Promise<ShareGrant>;
|
|
416
|
-
/** Rebuild a stored grant with its key (e.g. a CLI 0600 key file); the signed bytes are checked again. */
|
|
406
|
+
static prepare(input: ShareGrantRequest): Promise<PendingShareGrant>;
|
|
407
|
+
/** `prepare` then `approve`, for callers that keep no `pending` row (tests, one-shot tools). */
|
|
408
|
+
static create(input: ShareGrantRequest): Promise<ShareGrant>;
|
|
409
|
+
/** Rebuild a stored grant with its key (e.g. a CLI 0600 key file); the signed bytes are checked again. Only record v2. */
|
|
417
410
|
static restore(input: {
|
|
418
411
|
readonly record: ShareGrantRecord;
|
|
419
412
|
readonly key: DelegatedShareKey;
|
|
420
413
|
readonly now?: () => number;
|
|
414
|
+
readonly fetch?: FetchFn$1;
|
|
421
415
|
}): Promise<ShareGrant>;
|
|
422
|
-
/**
|
|
416
|
+
/**
|
|
417
|
+
* This grant's CACAO as base64url, UNPADDED (the record form). As a Node
|
|
418
|
+
* `Authorization` header it must be padded to a multiple of 4 characters:
|
|
419
|
+
* the Node refuses an unpadded value otherwise.
|
|
420
|
+
*/
|
|
423
421
|
get authorization(): string;
|
|
424
422
|
/** The exact KV resource this grant covers. */
|
|
425
423
|
get resource(): string;
|
|
426
424
|
toRecord(): ShareGrantRecord;
|
|
425
|
+
/** The history/index row for `state`. */
|
|
426
|
+
row(state: ShareGrantRowState): ShareGrantRow;
|
|
427
|
+
/**
|
|
428
|
+
* Step 3: make sure the share's network exists and is usable. Creates it
|
|
429
|
+
* when absent (key-signed, no approval), then decides on a fresh lookup
|
|
430
|
+
* after any answer. Returns `networkId` (the grant's lowercase spelling;
|
|
431
|
+
* the Node echoes the owner in EIP-55 case). A retired network is never
|
|
432
|
+
* re-created (the Node answers 409): `network-not-active`.
|
|
433
|
+
*/
|
|
434
|
+
ensureNetwork(): Promise<string>;
|
|
435
|
+
/** Throw `network-not-active` unless the share's network is `active` now (fresh lookup). */
|
|
436
|
+
assertNetworkActive(): Promise<string>;
|
|
437
|
+
/**
|
|
438
|
+
* Retire the share's network with the per-share key (no approval):
|
|
439
|
+
* PERMANENT, and every holder of decrypt on it (recipients included) is
|
|
440
|
+
* refused at their next request. Idempotent: a repeat or a never-created
|
|
441
|
+
* network counts as done.
|
|
442
|
+
*/
|
|
443
|
+
retireNetwork(): Promise<"retired" | "absent">;
|
|
444
|
+
/** Step 4 check (I1): the stored object belongs to this share, on its exact key and network. */
|
|
445
|
+
assertContentSource(contentSource: UnifiedContentSource): void;
|
|
427
446
|
/**
|
|
428
447
|
* Policy/v3 owner signing for this one share. Every call checks the key is
|
|
429
448
|
* held and the grant is live. Policy roots must be owned by the per-share
|
|
430
449
|
* key, expire no later than the grant, and name only this grant's exact
|
|
431
|
-
* KV resource (
|
|
450
|
+
* KV resource (no action the grant lacks) and decrypt on this share's
|
|
451
|
+
* network (parsed equality).
|
|
452
|
+
*/
|
|
453
|
+
policySigner(): SharePolicySigner;
|
|
454
|
+
/**
|
|
455
|
+
* Step 5: `publishAddressedShare`'s `authority` for this share. Its
|
|
456
|
+
* `registerPolicy` first re-reads the network and refuses unless it is
|
|
457
|
+
* `active` (released Nodes accept registrations on missing or retired
|
|
458
|
+
* networks).
|
|
432
459
|
*/
|
|
433
|
-
policySigner(): ShareRootPolicySigner;
|
|
434
|
-
/** `publishAddressedShare`'s `authority` for this share: the policy signer plus the caller's `registerPolicy`. */
|
|
435
460
|
publishAuthority(registerPolicy: AddressedPublishAuthority["registerPolicy"]): AddressedPublishAuthority;
|
|
436
|
-
/** Drop the per-share key. It does not
|
|
461
|
+
/** Drop the per-share key. It does not retire the network or revoke the grant. */
|
|
437
462
|
clear(): void;
|
|
438
463
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Revoke one addressed share: retire its network with the per-share key
|
|
466
|
+
* (permanent, no approval), THEN revoke the grant with an owner SIWE (one
|
|
467
|
+
* approval). Only a `{ revoked: true, cid }` receipt counts. If the owner
|
|
468
|
+
* step fails, decrypt is already cut: the error `grant-revoke-incomplete`
|
|
469
|
+
* says so, and the caller can retry the grant revoke. Without the key, use
|
|
470
|
+
* the owner control batch (`runShareControlBatch`).
|
|
471
|
+
*/
|
|
472
|
+
declare function revokeShareGrant(input: {
|
|
473
|
+
readonly grant: ShareGrant;
|
|
474
|
+
readonly owner: {
|
|
475
|
+
signOwnerRevocation(cid: string): Promise<string>;
|
|
476
|
+
};
|
|
477
|
+
readonly fetch?: FetchFn$1;
|
|
478
|
+
}): Promise<{
|
|
479
|
+
readonly network: "retired" | "absent";
|
|
480
|
+
readonly grant: "revoked";
|
|
481
|
+
}>;
|
|
482
|
+
/** A short-lived key that lets the owner read back one share's content (P2(a)). */
|
|
483
|
+
interface EditShareReader {
|
|
484
|
+
readonly cid: string;
|
|
485
|
+
readonly keyDid: string;
|
|
442
486
|
readonly networkId: string;
|
|
443
|
-
|
|
444
|
-
readonly
|
|
487
|
+
readonly path: string;
|
|
488
|
+
readonly expiresAt: Date;
|
|
489
|
+
/** Signs this reader's kv/get and decrypt invocations; refuses after expiry or `clear()`. */
|
|
490
|
+
readonly signer: ShareKeySigner;
|
|
491
|
+
clear(): void;
|
|
445
492
|
}
|
|
446
493
|
/**
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
|
|
494
|
+
* Let the owner read back an edit share (P2(a)): one approval for kv `get`
|
|
495
|
+
* on the share's key and `decrypt` on its network, to a throwaway in-memory
|
|
496
|
+
* key that lives at most one hour. Nothing is stored.
|
|
497
|
+
*/
|
|
498
|
+
declare function openEditShare(input: {
|
|
499
|
+
readonly owner: ShareRootOwnerAuthority;
|
|
500
|
+
readonly record: Pick<ShareGrantRecord, "spaceId" | "ownerDid" | "shareId" | "networkId" | "path">;
|
|
501
|
+
readonly lifetimeMs?: number;
|
|
502
|
+
readonly now?: () => number;
|
|
503
|
+
}): Promise<EditShareReader>;
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* One encryption network per addressed share (TC-1423, PERSHARE-NETWORK
|
|
507
|
+
* design): `urn:tinycloud:encryption:<owner did:pkh, lowercase>:share-<shareId>`.
|
|
508
|
+
* A stored ciphertext is wrapped to exactly one share's network, so a key or
|
|
509
|
+
* recipient session holding decrypt on it reaches that one share only.
|
|
510
|
+
*/
|
|
511
|
+
declare const SHARE_NETWORK_NAME_PREFIX = "share-";
|
|
512
|
+
/** Throws `share-id-not-network-safe` unless `shareId` matches `^[a-z0-9][a-z0-9-]{15,35}$` (UUIDs and 32-hex ids do). */
|
|
513
|
+
declare function assertNetworkSafeShareId(shareId: string): void;
|
|
514
|
+
/** The share's own network: `urn:tinycloud:encryption:<owner did:pkh, lowercase>:share-<shareId>`. */
|
|
515
|
+
declare function perShareNetworkIdFor(ownerDid: string, shareId: string): string;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Largest `Authorization` value an owner control may carry. Measured on the
|
|
519
|
+
* production ingress (nginx 1.27.4, `large_client_header_buffers 4 32k`,
|
|
520
|
+
* HTTP/1.1): 32,751 bytes pass, 32,752 bytes get `400 Request Header Or
|
|
521
|
+
* Cookie Too Large`. A direct Node accepts ~483 KB and Cloudflare 128 KB.
|
|
522
|
+
* 30,000 bytes leave 8.4% for header-name and proxy overhead. Each share
|
|
523
|
+
* (network URN with `delegation/revoke` + `network.revoke`) costs ~845
|
|
524
|
+
* bytes: 34 shares per control with the space rows (29,800 bytes measured
|
|
525
|
+
* with the `applications` row, imported by the Node), 35 without.
|
|
451
526
|
*/
|
|
452
|
-
declare
|
|
527
|
+
declare const SHARE_CONTROL_HEADER_BUDGET_BYTES = 30000;
|
|
528
|
+
/** A control lives ten minutes: long enough to retire and revoke one batch. */
|
|
529
|
+
declare const SHARE_CONTROL_LIFETIME_MS: number;
|
|
453
530
|
type FetchFn = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
531
|
+
/** The owner wallet, as a SIWE signer: one call = one approval. */
|
|
532
|
+
interface ShareControlOwner {
|
|
533
|
+
/** `did:pkh:eip155:<chain>:<address>`. */
|
|
534
|
+
readonly ownerDid: string;
|
|
535
|
+
/** The SIWE `domain` the wallet shows (the composer's host). */
|
|
536
|
+
readonly domain: string;
|
|
537
|
+
/** EIP-191 `personal_sign` of exactly `message`; returns the 65-byte signature as 0x hex. */
|
|
538
|
+
signMessage(message: string): Promise<string>;
|
|
539
|
+
}
|
|
540
|
+
/** One share to cut: its network, and its grant when known. */
|
|
541
|
+
interface ShareControlTarget {
|
|
542
|
+
readonly networkId: string;
|
|
543
|
+
readonly grantCid?: string;
|
|
544
|
+
}
|
|
545
|
+
/** One owner control: one approval, sized to the header budget before signing. */
|
|
546
|
+
interface ShareControlBatch {
|
|
547
|
+
readonly targets: readonly ShareControlTarget[];
|
|
548
|
+
/** `<space>/delegation` rows (first batch only): revoke any delegation in those spaces (share roots, links). */
|
|
549
|
+
readonly spaceDelegationResources: readonly string[];
|
|
550
|
+
/** Exact `Authorization` value length of the signed control, in bytes. */
|
|
551
|
+
readonly headerBytes: number;
|
|
552
|
+
}
|
|
454
553
|
/**
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
* network whose owner is the key would be root authority for that key
|
|
459
|
-
* (tinycloud-node `delegation.rs:792-809`), so revoking its grant would not
|
|
460
|
-
* cut decrypt.
|
|
554
|
+
* Split a kill-all into owner controls (one approval each), sized by the
|
|
555
|
+
* exact `Authorization` bytes of each signed control, never above
|
|
556
|
+
* `budgetBytes` (default {@link SHARE_CONTROL_HEADER_BUDGET_BYTES}).
|
|
461
557
|
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
558
|
+
* EVERY control carries the same `<space>/delegation` row for each of
|
|
559
|
+
* `spaceIds`: a grant's kv row is revoked through the `applications` row,
|
|
560
|
+
* and Nodes that accept control revokes (v1.20.6, NODE-B2 N4') need it in
|
|
561
|
+
* the control that revokes that grant. The `default` row covers agent roots
|
|
562
|
+
* and their links. `createOwnerDelegation` cannot express these rows (and
|
|
563
|
+
* caps a delegation at 16 permissions), so controls are built here.
|
|
564
|
+
*
|
|
565
|
+
* Targets are restricted to this owner's per-share networks
|
|
566
|
+
* (`control-target-invalid` otherwise), checked before anything is signed.
|
|
468
567
|
*/
|
|
469
|
-
declare function
|
|
470
|
-
readonly owner:
|
|
471
|
-
readonly
|
|
568
|
+
declare function planShareControlBatches(input: {
|
|
569
|
+
readonly owner: Pick<ShareControlOwner, "ownerDid" | "domain">;
|
|
570
|
+
readonly targets: readonly ShareControlTarget[];
|
|
571
|
+
/** Spaces whose delegations every control may revoke (e.g. the owner's `applications` and `default`). */
|
|
572
|
+
readonly spaceIds?: readonly string[];
|
|
573
|
+
readonly budgetBytes?: number;
|
|
574
|
+
}): ShareControlBatch[];
|
|
575
|
+
interface ShareControlResult {
|
|
576
|
+
readonly controlCid: string;
|
|
577
|
+
readonly networks: readonly {
|
|
578
|
+
readonly networkId: string;
|
|
579
|
+
readonly result: "retired" | "absent" | "failed";
|
|
580
|
+
readonly error?: string;
|
|
581
|
+
}[];
|
|
582
|
+
/**
|
|
583
|
+
* `revoked`: by the control. `revoked-by-owner`: the Node refused the
|
|
584
|
+
* control (released Nodes refuse it for grants that carry decrypt) and
|
|
585
|
+
* the per-grant owner fallback (one approval each) revoked it. `refused`:
|
|
586
|
+
* still live.
|
|
587
|
+
*/
|
|
588
|
+
readonly grants: readonly {
|
|
589
|
+
readonly grantCid: string;
|
|
590
|
+
readonly result: "revoked" | "revoked-by-owner" | "refused";
|
|
591
|
+
readonly status: number;
|
|
592
|
+
}[];
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Run one control (one approval): sign it to a fresh throwaway key, import
|
|
596
|
+
* it (`POST /delegate`), retire EVERY target network first (decrypt is cut
|
|
597
|
+
* for everyone at the next request), then revoke each known grant through
|
|
598
|
+
* the control. A revoke counts only with a `{ revoked: true, cid }` receipt
|
|
599
|
+
* for that grant. With `revokeFallback`, a grant the Node refuses to revoke
|
|
600
|
+
* through the control (1.21.0 refuses it for decrypt-bearing grants) is
|
|
601
|
+
* revoked by the owner directly (one approval each). The throwaway key is
|
|
602
|
+
* cleared before returning.
|
|
603
|
+
*
|
|
604
|
+
* The batch is checked again here (targets, space rows, domain, and the
|
|
605
|
+
* byte budget on the real values) before the owner is asked, so a stored or
|
|
606
|
+
* hand-built batch gets the same checks as a planned one.
|
|
607
|
+
*
|
|
608
|
+
* A grant is revoked even if its network's retire failed: re-run (the same
|
|
609
|
+
* batch, or a new plan for the leftovers) until every network is `retired`
|
|
610
|
+
* or `absent`. Re-runs are idempotent.
|
|
611
|
+
*/
|
|
612
|
+
declare function runShareControlBatch(input: {
|
|
613
|
+
readonly owner: ShareControlOwner;
|
|
472
614
|
readonly nodeOrigin: string;
|
|
615
|
+
readonly batch: ShareControlBatch;
|
|
616
|
+
readonly revokeFallback?: {
|
|
617
|
+
signOwnerRevocation(cid: string): Promise<string>;
|
|
618
|
+
};
|
|
473
619
|
readonly fetch?: FetchFn;
|
|
474
|
-
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
475
620
|
readonly now?: () => number;
|
|
476
|
-
|
|
621
|
+
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
622
|
+
}): Promise<ShareControlResult>;
|
|
477
623
|
|
|
478
|
-
export { AGENT_SHARE_ROOT_MAX_LIFETIME_MS, type DecodedOwnerRootCacao,
|
|
624
|
+
export { AGENT_SHARE_ROOT_MAX_LIFETIME_MS, type DecodedOwnerRootCacao, EDIT_SHARE_READER_MAX_LIFETIME_MS, type EditShareReader, type PendingShareGrant, SHARE_CONTROL_HEADER_BUDGET_BYTES, SHARE_CONTROL_LIFETIME_MS, SHARE_NETWORK_NAME_PREFIX, SHARE_ROOT_CLOCK_SKEW_MS, SHARE_ROOT_LINK_MAX_LIFETIME_MS, SHARE_ROOT_MAX_LIFETIME_MS, SHARE_ROOT_PREFIX, SHARE_ROOT_REQUEST_MARGIN_MS, type ShareControlBatch, type ShareControlOwner, type ShareControlResult, type ShareControlTarget, ShareGrant, type ShareGrantAccess, type ShareGrantRecord, type ShareGrantRequest, type ShareGrantRow, type ShareGrantRowState, type ShareKeyDelegationInput, type ShareKeySigner, type SharePolicySigner, ShareRoot, ShareRootError, type ShareRootErrorCode, type ShareRootErrorDetails, type ShareRootKind, type ShareRootLink, type ShareRootOptions, type ShareRootOwnerAuthority, type ShareRootRecord, assertNetworkSafeShareId, assertNonExtractableShareKey, assertShareGrantPath, decodeOwnerRootCacao, ensureShareRoot, openEditShare, ownerRootCacaoCid, perShareNetworkIdFor, planShareControlBatches, revokeShareGrant, runShareControlBatch, signShareKeyDelegation };
|