@tinycloud/share-sdk 1.2.0-beta.5 → 1.2.0-beta.6
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/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/share-index.cjs.map +1 -1
- package/dist/share-index.js.map +1 -1
- package/dist/share-root.cjs +96 -0
- package/dist/share-root.cjs.map +1 -1
- package/dist/share-root.d.cts +74 -12
- package/dist/share-root.d.ts +74 -12
- package/dist/share-root.js +94 -0
- package/dist/share-root.js.map +1 -1
- package/package.json +1 -1
package/dist/share-root.d.cts
CHANGED
|
@@ -185,6 +185,16 @@ declare class ShareRoot {
|
|
|
185
185
|
toRecord(): ShareRootRecord;
|
|
186
186
|
/** True while the root is live, its key is held, and it lasts at least until `expiresAt`. Revocation is not visible here. */
|
|
187
187
|
covers(expiresAt: Date, now?: number): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Sign a 60-second `POST /revoke` invocation, by the share key, for one
|
|
190
|
+
* delegation this root issued (a bearer link). Zero approvals. The Node
|
|
191
|
+
* authorizes it because the share key is that delegation's delegator. The
|
|
192
|
+
* root itself is never revoked this way: only the owner does that.
|
|
193
|
+
*/
|
|
194
|
+
signLinkRevocation(input: {
|
|
195
|
+
readonly delegationCid: string;
|
|
196
|
+
readonly nodeDid: string;
|
|
197
|
+
}): Promise<string>;
|
|
188
198
|
/**
|
|
189
199
|
* Sign a bearer link: `kv/get` on one exact key under the prefix, to the
|
|
190
200
|
* link's own key, with this root as its only proof. It never outlives the
|
|
@@ -266,7 +276,7 @@ declare function signShareKeyDelegation(input: ShareKeyDelegationInput): Promise
|
|
|
266
276
|
type ShareGrantAccess = "view" | "edit";
|
|
267
277
|
/** P2(a): a read-back key lives at most one hour. */
|
|
268
278
|
declare const EDIT_SHARE_READER_MAX_LIFETIME_MS: number;
|
|
269
|
-
type FetchFn$
|
|
279
|
+
type FetchFn$2 = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
270
280
|
/**
|
|
271
281
|
* The share's one content key (I1): `xyz.tinycloud.share/shares/<shareId>/<file>`,
|
|
272
282
|
* or, for `view` only, a TC-1428 version file
|
|
@@ -322,11 +332,14 @@ interface ShareGrantRequest {
|
|
|
322
332
|
* exists and is `active` (a half-published share whose key was lost), and
|
|
323
333
|
* grant a new key on it. Without it, `prepare` refuses an existing network
|
|
324
334
|
* (`share-id-in-use`) before the prompt. A retired network is always
|
|
325
|
-
* refused.
|
|
335
|
+
* refused. After resuming, revoke the lost key's grant with an owner SIWE
|
|
336
|
+
* on its CID (`POST /revoke`; not a kill-all control, which would also
|
|
337
|
+
* retire this network): it stays live, with decrypt on this share, until
|
|
338
|
+
* revoked or expired.
|
|
326
339
|
*/
|
|
327
340
|
readonly resumeExistingNetwork?: boolean;
|
|
328
341
|
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
329
|
-
readonly fetch?: FetchFn$
|
|
342
|
+
readonly fetch?: FetchFn$2;
|
|
330
343
|
readonly now?: () => number;
|
|
331
344
|
}
|
|
332
345
|
/** A per-share grant whose key exists but which the owner has not approved yet. Persist `row` first, then `approve()`. */
|
|
@@ -412,7 +425,7 @@ declare class ShareGrant {
|
|
|
412
425
|
readonly record: ShareGrantRecord;
|
|
413
426
|
readonly key: DelegatedShareKey;
|
|
414
427
|
readonly now?: () => number;
|
|
415
|
-
readonly fetch?: FetchFn$
|
|
428
|
+
readonly fetch?: FetchFn$2;
|
|
416
429
|
}): Promise<ShareGrant>;
|
|
417
430
|
/**
|
|
418
431
|
* This grant's CACAO as base64url, UNPADDED (the record form). As a Node
|
|
@@ -475,7 +488,7 @@ declare function revokeShareGrant(input: {
|
|
|
475
488
|
readonly owner: {
|
|
476
489
|
signOwnerRevocation(cid: string): Promise<string>;
|
|
477
490
|
};
|
|
478
|
-
readonly fetch?: FetchFn$
|
|
491
|
+
readonly fetch?: FetchFn$2;
|
|
479
492
|
}): Promise<{
|
|
480
493
|
readonly network: "retired" | "absent";
|
|
481
494
|
readonly grant: "revoked";
|
|
@@ -520,15 +533,17 @@ declare function perShareNetworkIdFor(ownerDid: string, shareId: string): string
|
|
|
520
533
|
* production ingress (nginx 1.27.4, `large_client_header_buffers 4 32k`,
|
|
521
534
|
* HTTP/1.1): 32,751 bytes pass, 32,752 bytes get `400 Request Header Or
|
|
522
535
|
* Cookie Too Large`. A direct Node accepts ~483 KB and Cloudflare 128 KB.
|
|
523
|
-
* 30,000 bytes leave 8.4% for header-name and proxy overhead.
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
536
|
+
* 30,000 bytes leave 8.4% for header-name and proxy overhead. A control's
|
|
537
|
+
* capacity depends on the shareId length; with the `applications` and
|
|
538
|
+
* `default` rows in every control: 33 shares for UUID shareIds, 34 for
|
|
539
|
+
* 32-hex ids, 37 for 16-character ids (34 UUID shares + the `applications`
|
|
540
|
+
* row alone measured 29,800 bytes, imported by the Node). L shares take
|
|
541
|
+
* ceil(L/n) controls, n from the plan.
|
|
527
542
|
*/
|
|
528
543
|
declare const SHARE_CONTROL_HEADER_BUDGET_BYTES = 30000;
|
|
529
544
|
/** A control lives ten minutes: long enough to retire and revoke one batch. */
|
|
530
545
|
declare const SHARE_CONTROL_LIFETIME_MS: number;
|
|
531
|
-
type FetchFn = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
546
|
+
type FetchFn$1 = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
532
547
|
/** The owner wallet, as a SIWE signer: one call = one approval. */
|
|
533
548
|
interface ShareControlOwner {
|
|
534
549
|
/** `did:pkh:eip155:<chain>:<address>`. */
|
|
@@ -617,9 +632,56 @@ declare function runShareControlBatch(input: {
|
|
|
617
632
|
readonly revokeFallback?: {
|
|
618
633
|
signOwnerRevocation(cid: string): Promise<string>;
|
|
619
634
|
};
|
|
620
|
-
readonly fetch?: FetchFn;
|
|
635
|
+
readonly fetch?: FetchFn$1;
|
|
621
636
|
readonly now?: () => number;
|
|
622
637
|
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
623
638
|
}): Promise<ShareControlResult>;
|
|
624
639
|
|
|
625
|
-
|
|
640
|
+
type FetchFn = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
641
|
+
/** A bearer link minted under a share root, in today's `/viewer#tc1=` shape. */
|
|
642
|
+
interface RootBearerLink {
|
|
643
|
+
/** `https://<viewer>/viewer#tc1=<token>`: the token carries the link key and its delegation, as SharingService links do. */
|
|
644
|
+
readonly url: string;
|
|
645
|
+
readonly delegationCid: string;
|
|
646
|
+
readonly rootCid: string;
|
|
647
|
+
readonly spaceId: string;
|
|
648
|
+
readonly path: string;
|
|
649
|
+
readonly expiresAt: Date;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Mint a view-only bearer link under a share root (TC-1423 D3, slice 3).
|
|
653
|
+
* Zero approvals: the share key signs `kv/get` on one exact key under the
|
|
654
|
+
* share prefix (`ShareRoot.signBearerLink`: never beyond the root, never
|
|
655
|
+
* more than 30 days, exactly `expiresAt` to the second), the Node imports it
|
|
656
|
+
* (`POST /delegate`), and the link is encoded exactly like a SharingService
|
|
657
|
+
* link: a fresh per-link Ed25519 key and its delegation in a `tc1:` token in
|
|
658
|
+
* the `/viewer` fragment, so viewers and `openNativeShare` read it unchanged.
|
|
659
|
+
*
|
|
660
|
+
* The per-link key travels in the fragment by design (anyone with the link
|
|
661
|
+
* holds it). Links need no decrypt (share roots never have it). Use
|
|
662
|
+
* `ensureShareRoot` first; a `delegation-revoked` or expired root fails here
|
|
663
|
+
* and needs a re-root (one approval).
|
|
664
|
+
*/
|
|
665
|
+
declare function publishRootBearerLink(input: {
|
|
666
|
+
readonly root: ShareRoot;
|
|
667
|
+
readonly path: string;
|
|
668
|
+
readonly expiresAt: Date;
|
|
669
|
+
readonly viewerOrigin: string;
|
|
670
|
+
readonly fetch?: FetchFn;
|
|
671
|
+
}): Promise<RootBearerLink>;
|
|
672
|
+
/**
|
|
673
|
+
* Revoke one bearer link with the share key that issued it (zero
|
|
674
|
+
* approvals): a share-key-signed `POST /revoke` on the link's delegation CID.
|
|
675
|
+
* Only a `{ revoked: true, cid }` receipt counts as done. The owner can
|
|
676
|
+
* always revoke the same CID with an owner SIWE (one approval).
|
|
677
|
+
*/
|
|
678
|
+
declare function revokeRootBearerLink(input: {
|
|
679
|
+
readonly root: ShareRoot;
|
|
680
|
+
readonly delegationCid: string;
|
|
681
|
+
readonly fetch?: FetchFn;
|
|
682
|
+
}): Promise<{
|
|
683
|
+
readonly revoked: true;
|
|
684
|
+
readonly cid: string;
|
|
685
|
+
}>;
|
|
686
|
+
|
|
687
|
+
export { AGENT_SHARE_ROOT_MAX_LIFETIME_MS, type DecodedOwnerRootCacao, EDIT_SHARE_READER_MAX_LIFETIME_MS, type EditShareReader, type PendingShareGrant, type RootBearerLink, 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, publishRootBearerLink, revokeRootBearerLink, revokeShareGrant, runShareControlBatch, signShareKeyDelegation };
|
package/dist/share-root.d.ts
CHANGED
|
@@ -185,6 +185,16 @@ declare class ShareRoot {
|
|
|
185
185
|
toRecord(): ShareRootRecord;
|
|
186
186
|
/** True while the root is live, its key is held, and it lasts at least until `expiresAt`. Revocation is not visible here. */
|
|
187
187
|
covers(expiresAt: Date, now?: number): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Sign a 60-second `POST /revoke` invocation, by the share key, for one
|
|
190
|
+
* delegation this root issued (a bearer link). Zero approvals. The Node
|
|
191
|
+
* authorizes it because the share key is that delegation's delegator. The
|
|
192
|
+
* root itself is never revoked this way: only the owner does that.
|
|
193
|
+
*/
|
|
194
|
+
signLinkRevocation(input: {
|
|
195
|
+
readonly delegationCid: string;
|
|
196
|
+
readonly nodeDid: string;
|
|
197
|
+
}): Promise<string>;
|
|
188
198
|
/**
|
|
189
199
|
* Sign a bearer link: `kv/get` on one exact key under the prefix, to the
|
|
190
200
|
* link's own key, with this root as its only proof. It never outlives the
|
|
@@ -266,7 +276,7 @@ declare function signShareKeyDelegation(input: ShareKeyDelegationInput): Promise
|
|
|
266
276
|
type ShareGrantAccess = "view" | "edit";
|
|
267
277
|
/** P2(a): a read-back key lives at most one hour. */
|
|
268
278
|
declare const EDIT_SHARE_READER_MAX_LIFETIME_MS: number;
|
|
269
|
-
type FetchFn$
|
|
279
|
+
type FetchFn$2 = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
270
280
|
/**
|
|
271
281
|
* The share's one content key (I1): `xyz.tinycloud.share/shares/<shareId>/<file>`,
|
|
272
282
|
* or, for `view` only, a TC-1428 version file
|
|
@@ -322,11 +332,14 @@ interface ShareGrantRequest {
|
|
|
322
332
|
* exists and is `active` (a half-published share whose key was lost), and
|
|
323
333
|
* grant a new key on it. Without it, `prepare` refuses an existing network
|
|
324
334
|
* (`share-id-in-use`) before the prompt. A retired network is always
|
|
325
|
-
* refused.
|
|
335
|
+
* refused. After resuming, revoke the lost key's grant with an owner SIWE
|
|
336
|
+
* on its CID (`POST /revoke`; not a kill-all control, which would also
|
|
337
|
+
* retire this network): it stays live, with decrypt on this share, until
|
|
338
|
+
* revoked or expired.
|
|
326
339
|
*/
|
|
327
340
|
readonly resumeExistingNetwork?: boolean;
|
|
328
341
|
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
329
|
-
readonly fetch?: FetchFn$
|
|
342
|
+
readonly fetch?: FetchFn$2;
|
|
330
343
|
readonly now?: () => number;
|
|
331
344
|
}
|
|
332
345
|
/** A per-share grant whose key exists but which the owner has not approved yet. Persist `row` first, then `approve()`. */
|
|
@@ -412,7 +425,7 @@ declare class ShareGrant {
|
|
|
412
425
|
readonly record: ShareGrantRecord;
|
|
413
426
|
readonly key: DelegatedShareKey;
|
|
414
427
|
readonly now?: () => number;
|
|
415
|
-
readonly fetch?: FetchFn$
|
|
428
|
+
readonly fetch?: FetchFn$2;
|
|
416
429
|
}): Promise<ShareGrant>;
|
|
417
430
|
/**
|
|
418
431
|
* This grant's CACAO as base64url, UNPADDED (the record form). As a Node
|
|
@@ -475,7 +488,7 @@ declare function revokeShareGrant(input: {
|
|
|
475
488
|
readonly owner: {
|
|
476
489
|
signOwnerRevocation(cid: string): Promise<string>;
|
|
477
490
|
};
|
|
478
|
-
readonly fetch?: FetchFn$
|
|
491
|
+
readonly fetch?: FetchFn$2;
|
|
479
492
|
}): Promise<{
|
|
480
493
|
readonly network: "retired" | "absent";
|
|
481
494
|
readonly grant: "revoked";
|
|
@@ -520,15 +533,17 @@ declare function perShareNetworkIdFor(ownerDid: string, shareId: string): string
|
|
|
520
533
|
* production ingress (nginx 1.27.4, `large_client_header_buffers 4 32k`,
|
|
521
534
|
* HTTP/1.1): 32,751 bytes pass, 32,752 bytes get `400 Request Header Or
|
|
522
535
|
* Cookie Too Large`. A direct Node accepts ~483 KB and Cloudflare 128 KB.
|
|
523
|
-
* 30,000 bytes leave 8.4% for header-name and proxy overhead.
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
536
|
+
* 30,000 bytes leave 8.4% for header-name and proxy overhead. A control's
|
|
537
|
+
* capacity depends on the shareId length; with the `applications` and
|
|
538
|
+
* `default` rows in every control: 33 shares for UUID shareIds, 34 for
|
|
539
|
+
* 32-hex ids, 37 for 16-character ids (34 UUID shares + the `applications`
|
|
540
|
+
* row alone measured 29,800 bytes, imported by the Node). L shares take
|
|
541
|
+
* ceil(L/n) controls, n from the plan.
|
|
527
542
|
*/
|
|
528
543
|
declare const SHARE_CONTROL_HEADER_BUDGET_BYTES = 30000;
|
|
529
544
|
/** A control lives ten minutes: long enough to retire and revoke one batch. */
|
|
530
545
|
declare const SHARE_CONTROL_LIFETIME_MS: number;
|
|
531
|
-
type FetchFn = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
546
|
+
type FetchFn$1 = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
532
547
|
/** The owner wallet, as a SIWE signer: one call = one approval. */
|
|
533
548
|
interface ShareControlOwner {
|
|
534
549
|
/** `did:pkh:eip155:<chain>:<address>`. */
|
|
@@ -617,9 +632,56 @@ declare function runShareControlBatch(input: {
|
|
|
617
632
|
readonly revokeFallback?: {
|
|
618
633
|
signOwnerRevocation(cid: string): Promise<string>;
|
|
619
634
|
};
|
|
620
|
-
readonly fetch?: FetchFn;
|
|
635
|
+
readonly fetch?: FetchFn$1;
|
|
621
636
|
readonly now?: () => number;
|
|
622
637
|
readonly keyFactory?: () => Promise<DelegatedShareKey>;
|
|
623
638
|
}): Promise<ShareControlResult>;
|
|
624
639
|
|
|
625
|
-
|
|
640
|
+
type FetchFn = (input: URL, init?: RequestInit) => Promise<Response>;
|
|
641
|
+
/** A bearer link minted under a share root, in today's `/viewer#tc1=` shape. */
|
|
642
|
+
interface RootBearerLink {
|
|
643
|
+
/** `https://<viewer>/viewer#tc1=<token>`: the token carries the link key and its delegation, as SharingService links do. */
|
|
644
|
+
readonly url: string;
|
|
645
|
+
readonly delegationCid: string;
|
|
646
|
+
readonly rootCid: string;
|
|
647
|
+
readonly spaceId: string;
|
|
648
|
+
readonly path: string;
|
|
649
|
+
readonly expiresAt: Date;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Mint a view-only bearer link under a share root (TC-1423 D3, slice 3).
|
|
653
|
+
* Zero approvals: the share key signs `kv/get` on one exact key under the
|
|
654
|
+
* share prefix (`ShareRoot.signBearerLink`: never beyond the root, never
|
|
655
|
+
* more than 30 days, exactly `expiresAt` to the second), the Node imports it
|
|
656
|
+
* (`POST /delegate`), and the link is encoded exactly like a SharingService
|
|
657
|
+
* link: a fresh per-link Ed25519 key and its delegation in a `tc1:` token in
|
|
658
|
+
* the `/viewer` fragment, so viewers and `openNativeShare` read it unchanged.
|
|
659
|
+
*
|
|
660
|
+
* The per-link key travels in the fragment by design (anyone with the link
|
|
661
|
+
* holds it). Links need no decrypt (share roots never have it). Use
|
|
662
|
+
* `ensureShareRoot` first; a `delegation-revoked` or expired root fails here
|
|
663
|
+
* and needs a re-root (one approval).
|
|
664
|
+
*/
|
|
665
|
+
declare function publishRootBearerLink(input: {
|
|
666
|
+
readonly root: ShareRoot;
|
|
667
|
+
readonly path: string;
|
|
668
|
+
readonly expiresAt: Date;
|
|
669
|
+
readonly viewerOrigin: string;
|
|
670
|
+
readonly fetch?: FetchFn;
|
|
671
|
+
}): Promise<RootBearerLink>;
|
|
672
|
+
/**
|
|
673
|
+
* Revoke one bearer link with the share key that issued it (zero
|
|
674
|
+
* approvals): a share-key-signed `POST /revoke` on the link's delegation CID.
|
|
675
|
+
* Only a `{ revoked: true, cid }` receipt counts as done. The owner can
|
|
676
|
+
* always revoke the same CID with an owner SIWE (one approval).
|
|
677
|
+
*/
|
|
678
|
+
declare function revokeRootBearerLink(input: {
|
|
679
|
+
readonly root: ShareRoot;
|
|
680
|
+
readonly delegationCid: string;
|
|
681
|
+
readonly fetch?: FetchFn;
|
|
682
|
+
}): Promise<{
|
|
683
|
+
readonly revoked: true;
|
|
684
|
+
readonly cid: string;
|
|
685
|
+
}>;
|
|
686
|
+
|
|
687
|
+
export { AGENT_SHARE_ROOT_MAX_LIFETIME_MS, type DecodedOwnerRootCacao, EDIT_SHARE_READER_MAX_LIFETIME_MS, type EditShareReader, type PendingShareGrant, type RootBearerLink, 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, publishRootBearerLink, revokeRootBearerLink, revokeShareGrant, runShareControlBatch, signShareKeyDelegation };
|
package/dist/share-root.js
CHANGED
|
@@ -6932,6 +6932,35 @@ var ShareRoot = class _ShareRoot {
|
|
|
6932
6932
|
covers(expiresAt, now = this.#now()) {
|
|
6933
6933
|
return this.#key !== void 0 && now < this.expiresAt.getTime() && expiresAt.getTime() <= this.expiresAt.getTime();
|
|
6934
6934
|
}
|
|
6935
|
+
/**
|
|
6936
|
+
* Sign a 60-second `POST /revoke` invocation, by the share key, for one
|
|
6937
|
+
* delegation this root issued (a bearer link). Zero approvals. The Node
|
|
6938
|
+
* authorizes it because the share key is that delegation's delegator. The
|
|
6939
|
+
* root itself is never revoked this way: only the owner does that.
|
|
6940
|
+
*/
|
|
6941
|
+
async signLinkRevocation(input) {
|
|
6942
|
+
const key = this.#activeKey();
|
|
6943
|
+
if (typeof input.delegationCid !== "string" || !/^b[a-z2-7]+$/.test(input.delegationCid)) throw new ShareRootError("invalid-request", "delegation CID is invalid");
|
|
6944
|
+
if (input.delegationCid === this.cid) throw new ShareRootError("invalid-request", "the share key does not revoke its own root; the owner does");
|
|
6945
|
+
try {
|
|
6946
|
+
ed25519PublicKeyFromDidKey(input.nodeDid);
|
|
6947
|
+
} catch {
|
|
6948
|
+
throw new ShareRootError("invalid-request", "node DID must be an Ed25519 did:key");
|
|
6949
|
+
}
|
|
6950
|
+
const now = seconds(this.#now());
|
|
6951
|
+
const revocation = await signCompactUcanAuthorization({
|
|
6952
|
+
issuerDid: key.did,
|
|
6953
|
+
audienceDid: input.nodeDid,
|
|
6954
|
+
attenuation: { [`urn:cid:${input.delegationCid}`]: { "tinycloud.delegation/revoke": [{}] } },
|
|
6955
|
+
facts: [{}],
|
|
6956
|
+
proofs: [],
|
|
6957
|
+
notBefore: now,
|
|
6958
|
+
expiresAt: now + 60,
|
|
6959
|
+
nonce: `urn:uuid:${crypto.randomUUID()}`,
|
|
6960
|
+
sign: (bytes2) => key.sign(bytes2)
|
|
6961
|
+
});
|
|
6962
|
+
return revocation.authorization;
|
|
6963
|
+
}
|
|
6935
6964
|
/**
|
|
6936
6965
|
* Sign a bearer link: `kv/get` on one exact key under the prefix, to the
|
|
6937
6966
|
* link's own key, with this root as its only proof. It never outlives the
|
|
@@ -7714,6 +7743,69 @@ async function runShareControlBatch(input) {
|
|
|
7714
7743
|
key.clear();
|
|
7715
7744
|
}
|
|
7716
7745
|
}
|
|
7746
|
+
|
|
7747
|
+
// src/share-root-link.ts
|
|
7748
|
+
import { ed25519 as ed255196 } from "@noble/curves/ed25519";
|
|
7749
|
+
|
|
7750
|
+
// src/native.ts
|
|
7751
|
+
var NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
|
|
7752
|
+
function canonicalViewerUrl(viewerOrigin) {
|
|
7753
|
+
const url = new URL(viewerOrigin);
|
|
7754
|
+
if (url.protocol !== "https:" || url.origin !== viewerOrigin || url.pathname !== "/" || url.search || url.hash) throw new TypeError("viewer origin must be a canonical HTTPS origin");
|
|
7755
|
+
url.pathname = "/viewer";
|
|
7756
|
+
return url;
|
|
7757
|
+
}
|
|
7758
|
+
|
|
7759
|
+
// src/share-root-link.ts
|
|
7760
|
+
async function publishRootBearerLink(input) {
|
|
7761
|
+
const fetchFn = input.fetch ?? ((url, init) => globalThis.fetch(url, init));
|
|
7762
|
+
const viewerUrl = canonicalViewerUrl(input.viewerOrigin);
|
|
7763
|
+
const secret = ed255196.utils.randomPrivateKey();
|
|
7764
|
+
try {
|
|
7765
|
+
const publicKey = ed255196.getPublicKey(secret);
|
|
7766
|
+
const linkDid = didKeyFromEd25519PublicKey(publicKey);
|
|
7767
|
+
const link = await input.root.signBearerLink({ linkDid, path: input.path, expiresAt: input.expiresAt });
|
|
7768
|
+
const response = await fetchFn(new URL("/delegate", input.root.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: link.delegation.authorization } });
|
|
7769
|
+
if (!response.ok) throw new ShareRootError("node-refused", `the Node refused the link delegation: HTTP ${response.status} ${(await response.text()).slice(0, 120)}`, { rootCid: input.root.cid });
|
|
7770
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
7771
|
+
const shareData = {
|
|
7772
|
+
key: { kty: "OKP", crv: "Ed25519", x: toBase64Url(publicKey), d: toBase64Url(secret) },
|
|
7773
|
+
keyDid: `${linkDid}#${linkDid.slice("did:key:".length)}`,
|
|
7774
|
+
delegation: {
|
|
7775
|
+
cid: link.delegation.cid,
|
|
7776
|
+
delegateDID: linkDid,
|
|
7777
|
+
delegatorDID: input.root.keyDid,
|
|
7778
|
+
spaceId: input.root.spaceId,
|
|
7779
|
+
path: input.path,
|
|
7780
|
+
actions: ["tinycloud.kv/get"],
|
|
7781
|
+
expiry: link.expiresAt,
|
|
7782
|
+
isRevoked: false,
|
|
7783
|
+
allowSubDelegation: true,
|
|
7784
|
+
createdAt,
|
|
7785
|
+
parentCid: input.root.cid,
|
|
7786
|
+
authHeader: link.delegation.authorization
|
|
7787
|
+
},
|
|
7788
|
+
path: input.path,
|
|
7789
|
+
host: input.root.nodeOrigin,
|
|
7790
|
+
spaceId: input.root.spaceId,
|
|
7791
|
+
version: 1
|
|
7792
|
+
};
|
|
7793
|
+
const token = `tc1:${toBase64Url(new TextEncoder().encode(JSON.stringify(shareData)))}`;
|
|
7794
|
+
viewerUrl.hash = `${NATIVE_SHARE_FRAGMENT_PARAMETER}=${encodeURIComponent(token)}`;
|
|
7795
|
+
return { url: viewerUrl.toString(), delegationCid: link.delegation.cid, rootCid: input.root.cid, spaceId: input.root.spaceId, path: input.path, expiresAt: link.expiresAt };
|
|
7796
|
+
} finally {
|
|
7797
|
+
secret.fill(0);
|
|
7798
|
+
}
|
|
7799
|
+
}
|
|
7800
|
+
async function revokeRootBearerLink(input) {
|
|
7801
|
+
const fetchFn = input.fetch ?? ((url, init) => globalThis.fetch(url, init));
|
|
7802
|
+
const nodeDid = await nodeDidOf(input.root.nodeOrigin, fetchFn);
|
|
7803
|
+
const authorization = await input.root.signLinkRevocation({ delegationCid: input.delegationCid, nodeDid });
|
|
7804
|
+
const response = await fetchFn(new URL("/revoke", input.root.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: authorization } });
|
|
7805
|
+
if (!response.ok) throw new ShareRootError("node-refused", `the Node refused the link revocation: HTTP ${response.status} ${(await response.text()).slice(0, 120)}`, { rootCid: input.root.cid });
|
|
7806
|
+
if (!await isRevokeReceiptFor(response, input.delegationCid)) throw new ShareRootError("invalid-request", "link revocation receipt does not confirm this delegation");
|
|
7807
|
+
return { revoked: true, cid: input.delegationCid };
|
|
7808
|
+
}
|
|
7717
7809
|
export {
|
|
7718
7810
|
AGENT_SHARE_ROOT_MAX_LIFETIME_MS,
|
|
7719
7811
|
EDIT_SHARE_READER_MAX_LIFETIME_MS,
|
|
@@ -7737,6 +7829,8 @@ export {
|
|
|
7737
7829
|
ownerRootCacaoCid,
|
|
7738
7830
|
perShareNetworkIdFor,
|
|
7739
7831
|
planShareControlBatches,
|
|
7832
|
+
publishRootBearerLink,
|
|
7833
|
+
revokeRootBearerLink,
|
|
7740
7834
|
revokeShareGrant,
|
|
7741
7835
|
runShareControlBatch,
|
|
7742
7836
|
signShareKeyDelegation
|