@tinycloud/sdk-core 3.0.0-beta.8 → 3.0.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.
@@ -47,7 +47,6 @@ __export(delegations_exports, {
47
47
  MemorySenderShareRecordStorage: () => MemorySenderShareRecordStorage,
48
48
  MemoryShareCache: () => MemoryShareCache,
49
49
  ReceiveOptionsSchema: () => ReceiveOptionsSchema,
50
- SHARE_DELIVERY_AUTHORIZATION_DOMAIN: () => SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
51
50
  SenderShareStore: () => SenderShareStore,
52
51
  ShareAccessError: () => ShareAccessError,
53
52
  ShareActionSchema: () => ShareActionSchema,
@@ -87,10 +86,8 @@ __export(delegations_exports, {
87
86
  importPortableDelegation: () => importPortableDelegation,
88
87
  inspectShare: () => import_share_sdk2.inspectShare,
89
88
  intersectShareCapabilities: () => intersectShareCapabilities,
90
- isLegacyShareLink: () => import_share_sdk.isLegacyShareLink,
91
89
  isV2ShareLink: () => isV2ShareLink,
92
90
  listShares: () => import_share_sdk.listShares,
93
- migrateShare: () => import_share_sdk.migrateShare,
94
91
  normalizeShareActions: () => normalizeShareActions,
95
92
  normalizeShareRecipientTarget: () => normalizeShareRecipientTarget,
96
93
  normalizeShareResource: () => normalizeShareResource,
@@ -100,14 +97,12 @@ __export(delegations_exports, {
100
97
  parseShareUrl: () => parseShareUrl,
101
98
  publishPolicyShare: () => import_share_sdk.publishPolicyShare,
102
99
  publishTargetShare: () => import_share_sdk.publishTargetShare,
103
- receiveLegacyShare: () => import_share_sdk.receiveLegacyShare,
104
100
  receiveShare: () => import_share_sdk2.receiveShare,
105
101
  restoreDelegatedShareKey: () => restoreDelegatedShareKey,
106
102
  resumeShareAuthorization: () => import_share_sdk.resumeShareAuthorization,
107
103
  revokeShare: () => import_share_sdk.revokeShare,
108
104
  serializePortableDelegation: () => serializePortableDelegation,
109
105
  shareCapabilityAllows: () => shareCapabilityAllows,
110
- shareDeliveryTrustedKid: () => shareDeliveryTrustedKid,
111
106
  showShare: () => import_share_sdk.showShare,
112
107
  signShareEnvelope: () => signShareEnvelope,
113
108
  toShareErrorInfo: () => import_share_sdk2.toShareErrorInfo,
@@ -115,10 +110,8 @@ __export(delegations_exports, {
115
110
  validateOwnerSharePolicyRegistration: () => validateOwnerSharePolicyRegistration,
116
111
  validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes,
117
112
  validateReceiveOptions: () => validateReceiveOptions,
118
- validateShareDeliveryAuthorizationBytes: () => validateShareDeliveryAuthorizationBytes,
119
113
  validateShareDeliveryAuthorizationV3Bytes: () => validateShareDeliveryAuthorizationV3Bytes,
120
114
  validateSharingServiceConfig: () => validateSharingServiceConfig,
121
- verifyBearerEnvelope: () => import_share_sdk2.verifyBearerEnvelope,
122
115
  verifyShareCid: () => verifyShareCid,
123
116
  verifyShareEnvelope: () => verifyShareEnvelope
124
117
  });
@@ -177,6 +170,7 @@ var DelegationErrorSchema = import_zod.z.object({
177
170
  });
178
171
  var DelegationErrorCodes = {
179
172
  AUTH_REQUIRED: "AUTH_REQUIRED",
173
+ AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
180
174
  AUTH_EXPIRED: "AUTH_EXPIRED",
181
175
  NOT_INITIALIZED: "NOT_INITIALIZED",
182
176
  NOT_FOUND: "NOT_FOUND",
@@ -541,7 +535,7 @@ var CreateDelegationWasmResultSchema = import_zod.z.object({
541
535
  // src/expiry.ts
542
536
  var EPHEMERAL_MS = 60 * 60 * 1e3;
543
537
  var SIGNED_READ_URL_MS = 5 * 60 * 1e3;
544
- var SESSION_MS = 7 * 24 * 60 * 60 * 1e3;
538
+ var SESSION_MS = 30 * 24 * 60 * 60 * 1e3;
545
539
  var SHARE_MS = 7 * 24 * 60 * 60 * 1e3;
546
540
  var APP_MS = 30 * 24 * 60 * 60 * 1e3;
547
541
  var MAX_MS = 10 * 365 * 24 * 60 * 60 * 1e3;
@@ -2696,7 +2690,6 @@ var import_ed25519 = require("@noble/curves/ed25519");
2696
2690
  var import_blake3 = require("@noble/hashes/blake3");
2697
2691
  var import_sha256 = require("@noble/hashes/sha256");
2698
2692
  var ENVELOPE_SCHEMA = "xyz.tinycloud.share/envelope";
2699
- var INLINE_PREFIX = "tc2:";
2700
2693
  var RAW_CID_CODEC = 85;
2701
2694
  var BLAKE3_256_CODE = 30;
2702
2695
  var DEFAULT_MAX_INLINE_BYTES = 256 * 1024;
@@ -3401,25 +3394,8 @@ function parsePublishedShareLink(link, options = {}) {
3401
3394
  }
3402
3395
  const origin = normalizeOrigin(url.origin);
3403
3396
  if (options.trustedOrigins !== void 0 && !options.trustedOrigins.map(normalizeOrigin).includes(origin)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
3404
- if (url.search !== "" || url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
3405
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
3406
- let payload;
3407
- try {
3408
- const bytes2 = base64UrlToBytes(url.hash.slice("#v=2&p=".length));
3409
- if (bytes2.length > (options.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
3410
- const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
3411
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
3412
- payload = value;
3413
- } catch (error) {
3414
- throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
3415
- }
3416
- if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || Object.keys(payload).some((key3) => !["v", "c", "cid", "k"].includes(key3))) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
3417
- const sealed = base64UrlToBytes(payload.c);
3418
- if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
3419
- const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
3420
- if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
3421
- return { origin, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
3422
- }
3397
+ if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
3398
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
3423
3399
  const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
3424
3400
  if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
3425
3401
  const key = base64UrlToBytes(url.hash.slice(3));
@@ -3439,13 +3415,10 @@ function parseShareUrl(link, options = {}) {
3439
3415
  } catch {
3440
3416
  throw new ShareEnvelopeError("invalid-link", "share link must be an absolute URL");
3441
3417
  }
3442
- if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
3443
3418
  const origin = normalizeOrigin(url.origin);
3444
3419
  if (options.trustedOrigins !== void 0 && !options.trustedOrigins.map(normalizeOrigin).includes(origin)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
3445
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
3446
- const published = parsePublishedShareLink(link, options);
3447
- return { kind: "inline", origin, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
3448
- }
3420
+ if (url.pathname === "/viewer") throw new ShareEnvelopeError("invalid-link", "retired viewer links are not accepted");
3421
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
3449
3422
  const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
3450
3423
  if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
3451
3424
  const fragmentParams = url.hash === "" ? void 0 : new URLSearchParams(url.hash.slice(1));
@@ -3468,9 +3441,10 @@ function parseShareUrl(link, options = {}) {
3468
3441
  }
3469
3442
  function isV2ShareLink(link) {
3470
3443
  try {
3471
- return new URL(link).pathname.startsWith("/s/");
3444
+ const url = new URL(link);
3445
+ return url.search === "" && url.pathname.startsWith("/s/");
3472
3446
  } catch {
3473
- return link.startsWith(INLINE_PREFIX);
3447
+ return false;
3474
3448
  }
3475
3449
  }
3476
3450
  async function encryptShareBytes(bytes2, key) {
@@ -4031,6 +4005,7 @@ var CompactPayloadSchema = import_zod4.z.object({
4031
4005
  nnc: import_zod4.z.string().min(1),
4032
4006
  prf: import_zod4.z.array(import_zod4.z.string().min(1))
4033
4007
  }).strict();
4008
+ var MAX_POLICY_ROOT_SECONDS = 31 * 24 * 60 * 60;
4034
4009
  function createCompactPolicyInvocation(input) {
4035
4010
  const session = parsePolicySessionUcan(input.sessionAuthorization);
4036
4011
  if (session.cid !== input.sessionCid || session.aud !== input.recipientDid)
@@ -4062,7 +4037,7 @@ function createCompactPolicyInvocation(input) {
4062
4037
  const signature = import_ed255192.ed25519.sign(signingInput, input.privateKey);
4063
4038
  return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url(signature)}`);
4064
4039
  }
4065
- function createCompactPolicyDescendant(input) {
4040
+ function compactPolicyDescendantClaims(input) {
4066
4041
  const parent = parseCompactUcanAuthorization(input.parentAuthorization, input.parentCid);
4067
4042
  if (parent.payload.aud !== input.issuerDid) throw new Error("policy descendant issuer is not the parent audience");
4068
4043
  const parentFact = parent.payload.fct[0];
@@ -4070,23 +4045,33 @@ function createCompactPolicyDescendant(input) {
4070
4045
  if (!Number.isInteger(depth) || depth <= 0) throw new Error("policy descendant depth is exhausted");
4071
4046
  if (!compactAttenuationContains(parent.payload.att, input.attenuation))
4072
4047
  throw new Error("policy descendant exceeds the parent attenuation");
4048
+ const now = Math.max(input.now ?? parent.payload.nbf + 1, parent.payload.nbf + 1);
4049
+ const exp = Math.min(input.expiresAt ?? parent.payload.exp - 1, parent.payload.exp - 1);
4050
+ if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
4051
+ return {
4052
+ parentCid: parent.cid,
4053
+ fact: { ...parentFact, remainingRedelegationDepth: depth - 1 },
4054
+ notBefore: now,
4055
+ expiresAt: exp,
4056
+ nonce: input.nonce ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16)))
4057
+ };
4058
+ }
4059
+ function createCompactPolicyDescendant(input) {
4060
+ const claims = compactPolicyDescendantClaims(input);
4073
4061
  const principal = input.issuerDid.split("#", 1)[0];
4074
4062
  const didMaterial = base58btc.decode(principal.slice("did:key:".length));
4075
4063
  const publicKey = import_ed255192.ed25519.getPublicKey(input.privateKey);
4076
4064
  if (didMaterial.length !== 34 || !equalBytes2(publicKey, didMaterial.slice(2))) throw new Error("policy descendant signer does not bind issuer DID");
4077
- const now = Math.max(input.now ?? Math.floor(Date.now() / 1e3), parent.payload.nbf + 1);
4078
- const exp = Math.min(input.expiresAt ?? now + 60, now + 60, parent.payload.exp - 1);
4079
- if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
4080
4065
  const header = { alg: "EdDSA", jwk: { alg: "EdDSA", crv: "Ed25519", kty: "OKP", x: encodeBase64Url(publicKey) }, typ: "JWT", ucv: "0.10.0" };
4081
4066
  const payload = {
4082
4067
  att: input.attenuation,
4083
4068
  aud: input.audienceDid,
4084
- exp,
4085
- fct: [{ ...parentFact, remainingRedelegationDepth: depth - 1 }],
4069
+ exp: claims.expiresAt,
4070
+ fct: [claims.fact],
4086
4071
  iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
4087
- nbf: now,
4088
- nnc: input.nonce ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16))),
4089
- prf: [parent.cid]
4072
+ nbf: claims.notBefore,
4073
+ nnc: claims.nonce,
4074
+ prf: [claims.parentCid]
4090
4075
  };
4091
4076
  const protectedSegment = encodeBase64Url(new TextEncoder().encode(jcsCanonicalize(header)));
4092
4077
  const payloadSegment = encodeBase64Url(new TextEncoder().encode(jcsCanonicalize(payload)));
@@ -4162,7 +4147,7 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
4162
4147
  throw new Error("policy session UCAN fact is incomplete");
4163
4148
  if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
4164
4149
  throw new Error("policy session UCAN v4 audit facts are invalid");
4165
- if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf > 60 || parsed.payload.iss.split("#", 1)[0] !== fact.nodeAudience || parsed.payload.aud !== fact.recipientDid)
4150
+ if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf > MAX_POLICY_ROOT_SECONDS || parsed.payload.iss.split("#", 1)[0] !== fact.nodeAudience || parsed.payload.aud !== fact.recipientDid)
4166
4151
  throw new Error("policy session UCAN fact is invalid");
4167
4152
  return {
4168
4153
  authorization,
@@ -5383,6 +5368,13 @@ var ShareRecipientClient = class {
5383
5368
  };
5384
5369
 
5385
5370
  // src/delegations/SharingService.ts
5371
+ function spaceIdsEqual(left, right) {
5372
+ const normalizeOwnerAddress = (space) => {
5373
+ const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
5374
+ return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
5375
+ };
5376
+ return normalizeOwnerAddress(left) === normalizeOwnerAddress(right);
5377
+ }
5386
5378
  function inferShortServiceFromActionUrns(actions) {
5387
5379
  let short;
5388
5380
  for (const action of actions) {
@@ -5618,11 +5610,11 @@ var SharingService = class {
5618
5610
  }
5619
5611
  return result;
5620
5612
  };
5621
- const canSatisfyFromRegistry = this.findSuitableKeyForDelegation(
5613
+ const canSatisfyFromRegistry = this.classifyDelegationAuthority(
5622
5614
  fullPath,
5623
5615
  actions,
5624
5616
  requestedExpiry
5625
- );
5617
+ ) === "ok";
5626
5618
  if (canSatisfyFromRegistry) {
5627
5619
  const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
5628
5620
  const childDelegationActiveError = this.retiredGraphError();
@@ -5692,49 +5684,58 @@ var SharingService = class {
5692
5684
  return { ok: true, data: shareLink };
5693
5685
  }
5694
5686
  /**
5695
- * Check if any key in the registry can satisfy the delegation request.
5696
- * A key can satisfy if it has a delegation that:
5697
- * 1. Covers the required path (exact match or parent path)
5698
- * 2. Has all required actions
5699
- * 3. Has sufficient expiry (delegation.expiry >= requestedExpiry)
5700
- * 4. Allows sub-delegation
5687
+ * Report, without side effects, whether this session's registry authority
5688
+ * can issue the child delegation {@link generate} would create for the same
5689
+ * `path`, `actions` and `expiry` (defaults and `pathPrefix` applied the
5690
+ * same way):
5691
+ *
5692
+ * - `"ok"`: an uncaveated entry covers it.
5693
+ * - `"caveated"`: only caveated entries cover it. SharingService cannot
5694
+ * carry signed ReCap caveats into a child delegation.
5695
+ * - `"not-covered"`: no entry covers it, or there is no session.
5696
+ *
5697
+ * It does not consult `onRootDelegationNeeded`, so a signer-backed session
5698
+ * may still generate when this is not `"ok"`.
5699
+ */
5700
+ preflightGenerate(params) {
5701
+ if (!this.session || !params.path) return "not-covered";
5702
+ const fullPath = this.pathPrefix ? `${this.pathPrefix}/${params.path}`.replace(/\/+/g, "/") : params.path;
5703
+ return this.classifyDelegationAuthority(
5704
+ fullPath,
5705
+ params.actions ?? DEFAULT_READ_ACTIONS,
5706
+ params.expiry ?? new Date(Date.now() + DEFAULT_EXPIRY_MS)
5707
+ );
5708
+ }
5709
+ /**
5710
+ * Classify the registry's authority for a child delegation. An entry
5711
+ * satisfies it when it is in the session space, valid, lasts at least until
5712
+ * `requestedExpiry`, allows sub-delegation, covers `path` (exact or parent)
5713
+ * and grants every action. Satisfying entries with caveats are unusable.
5701
5714
  * @internal
5702
5715
  */
5703
- findSuitableKeyForDelegation(path, actions, requestedExpiry) {
5704
- const allKeys = this.registry.getAllKeys();
5705
- for (const key of allKeys) {
5706
- const delegations = this.registry.getDelegationsForKey(key.id);
5707
- for (const delegation of delegations) {
5708
- if (delegation.spaceId !== this.session?.spaceId) {
5709
- continue;
5710
- }
5711
- if ((delegation.caveats?.length ?? 0) > 0) {
5712
- continue;
5713
- }
5714
- if (!this.registry.isDelegationValid(delegation)) {
5715
- continue;
5716
- }
5717
- if (delegation.expiry < requestedExpiry) {
5718
- continue;
5719
- }
5720
- if (delegation.allowSubDelegation === false) {
5716
+ classifyDelegationAuthority(path, actions, requestedExpiry) {
5717
+ let coveredOnlyByCaveated = false;
5718
+ for (const key of this.registry.getAllKeys()) {
5719
+ for (const delegation of this.registry.getDelegationsForKey(key.id)) {
5720
+ if (delegation.spaceId === void 0 || !spaceIdsEqual(delegation.spaceId, this.session?.spaceId ?? "")) {
5721
5721
  continue;
5722
5722
  }
5723
- const delegationPath = delegation.path || "";
5724
- if (!this.pathMatches(delegationPath, path)) {
5723
+ if (!this.registry.isDelegationValid(delegation)) continue;
5724
+ if (delegation.expiry < requestedExpiry) continue;
5725
+ if (delegation.allowSubDelegation === false) continue;
5726
+ if (!this.pathMatches(delegation.path || "", path)) continue;
5727
+ const delegationActions = delegation.actions || [];
5728
+ if (!actions.every((action) => delegationActions.includes(action) || delegationActions.includes("*"))) {
5725
5729
  continue;
5726
5730
  }
5727
- const delegationActions = delegation.actions || [];
5728
- const hasAllActions = actions.every(
5729
- (action) => delegationActions.includes(action) || delegationActions.includes("*")
5730
- );
5731
- if (!hasAllActions) {
5731
+ if ((delegation.caveats?.length ?? 0) > 0) {
5732
+ coveredOnlyByCaveated = true;
5732
5733
  continue;
5733
5734
  }
5734
- return true;
5735
+ return "ok";
5735
5736
  }
5736
5737
  }
5737
- return false;
5738
+ return coveredOnlyByCaveated ? "caveated" : "not-covered";
5738
5739
  }
5739
5740
  retiredGraphError() {
5740
5741
  try {
@@ -5833,11 +5834,23 @@ var SharingService = class {
5833
5834
  const errorText = await registerRes.text();
5834
5835
  const registrationTextActiveError = this.retiredGraphError();
5835
5836
  if (registrationTextActiveError) return { ok: false, error: registrationTextActiveError };
5837
+ const message = `Failed to register delegation with server: ${registerRes.status} ${errorText}`;
5838
+ if (registerRes.status === 401 || registerRes.status === 403) {
5839
+ return {
5840
+ ok: false,
5841
+ error: createError2(
5842
+ DelegationErrorCodes.AUTH_UNAUTHORIZED,
5843
+ message,
5844
+ void 0,
5845
+ { status: registerRes.status }
5846
+ )
5847
+ };
5848
+ }
5836
5849
  return {
5837
5850
  ok: false,
5838
5851
  error: createError2(
5839
5852
  DelegationErrorCodes.CREATION_FAILED,
5840
- `Failed to register delegation with server: ${registerRes.status} ${errorText}`
5853
+ message
5841
5854
  )
5842
5855
  };
5843
5856
  }
@@ -6707,114 +6720,13 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
6707
6720
  return validateOwnerSharePolicyRegistration(value, expected);
6708
6721
  }
6709
6722
 
6710
- // src/delegations/share-delivery.ts
6711
- var import_ed255197 = require("@noble/curves/ed25519");
6712
- var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
6713
- function isCanonicalHttpsOrigin(value) {
6714
- let url;
6715
- try {
6716
- url = new URL(value);
6717
- } catch {
6718
- return false;
6719
- }
6720
- return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
6721
- }
6722
- var AUTHORIZATION_KEYS = [
6723
- "type",
6724
- "version",
6725
- "jti",
6726
- "shareCid",
6727
- "shareId",
6728
- "registrationCid",
6729
- "delegationCid",
6730
- "enforcementDelegationCid",
6731
- "envelopeCid",
6732
- "policyCid",
6733
- "nodeAudience",
6734
- "targetOrigin",
6735
- "openCredentialsAudience",
6736
- "holder",
6737
- "recipientMatcher",
6738
- "deliveryEmail",
6739
- "shareUrl",
6740
- "returnOrigin",
6741
- "documentName",
6742
- "senderDid",
6743
- "senderTrust",
6744
- "contentSource",
6745
- "contentSourceDigest",
6746
- "shareExpiresAt",
6747
- "issuedAt",
6748
- "reportAbuseToken",
6749
- "actions",
6750
- "resource",
6751
- "authorityMaterialHandle",
6752
- "authorityMaterialDigest",
6753
- "requestBodyDigest",
6754
- "idempotencyKey",
6755
- "expiresAt",
6756
- "dataAuthority"
6757
- ];
6758
- function assertObject2(value, keys, label) {
6759
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
6760
- const record = value;
6761
- if (Object.keys(record).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record, key))) throw new Error(`${label} has unknown or missing fields`);
6762
- return record;
6763
- }
6764
- function fromB642(value) {
6765
- if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
6766
- if (typeof atob === "function") {
6767
- const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
6768
- return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
6769
- }
6770
- if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
6771
- throw new Error("base64url decoding is unavailable");
6772
- }
6773
- function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
6774
- let value;
6775
- try {
6776
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
6777
- } catch {
6778
- throw new Error("share delivery authorization response is not valid UTF-8 JSON");
6779
- }
6780
- const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
6781
- const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
6782
- const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
6783
- const request = expected.request;
6784
- if (authorization.type !== "TinyCloudShareDeliveryAuthorization" || authorization.version !== 2 || authorization.dataAuthority !== false || authorization.jti !== request.jti || authorization.shareCid !== request.shareCid || authorization.shareId !== request.shareId || authorization.registrationCid !== request.registrationCid || authorization.delegationCid !== request.delegationCid || authorization.enforcementDelegationCid !== request.enforcementDelegationCid || authorization.envelopeCid !== request.envelopeCid || authorization.policyCid !== request.policyCid || authorization.deliveryEmail !== request.recipientEmail || authorization.shareUrl !== request.shareUrl || authorization.documentName !== request.documentName || authorization.requestBodyDigest !== request.requestBodyDigest || authorization.expiresAt !== request.expiresAt || authorization.idempotencyKey !== request.idempotencyKey || authorization.reportAbuseToken !== request.jti || authorization.authorityMaterialHandle !== request.registrationCid) {
6785
- throw new Error("share delivery authorization is not bound to the submitted request");
6786
- }
6787
- if (typeof authorization.issuedAt !== "string" || Date.parse(authorization.issuedAt) > Date.now() + 6e4) throw new Error("share delivery authorization issuedAt is invalid");
6788
- if (new Date(authorization.expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
6789
- if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
6790
- throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
6791
- }
6792
- if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
6793
- if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
6794
- const encodedKid = expected.nodeProof.publicKey;
6795
- const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
6796
- if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
6797
- const signatureBytes = fromB642(proof.signature);
6798
- const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalizeSignedObjectUnsigned(authorization)}`);
6799
- if (signatureBytes.length !== 64 || !import_ed255197.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
6800
- return { authorization, proof };
6801
- }
6802
- function shareDeliveryTrustedKid(publicKey) {
6803
- if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
6804
- const prefixed = new Uint8Array(34);
6805
- prefixed.set([237, 1]);
6806
- prefixed.set(publicKey, 2);
6807
- const did = `did:key:${base58btc.encode(prefixed)}`;
6808
- return `${did}#${did.slice("did:key:".length)}`;
6809
- }
6810
-
6811
6723
  // src/delegations/share-delivery-v3.ts
6812
- var import_ed255198 = require("@noble/curves/ed25519");
6724
+ var import_ed255197 = require("@noble/curves/ed25519");
6813
6725
  var import_sha2566 = require("@noble/hashes/sha256");
6814
6726
  var CREDENTIAL_INVITATION_REQUEST_DOMAIN = "xyz.tinycloud.credentials/invitation-request/v1\0";
6815
6727
  var DELIVERY_ADMISSION_DOMAIN = "xyz.tinycloud.policy/delivery-admission/v0\0";
6816
- var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
6817
- var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
6728
+ var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
6729
+ var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
6818
6730
  function exactObject(value, keys, label) {
6819
6731
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
6820
6732
  const record = value;
@@ -6858,15 +6770,16 @@ function validateShareDeliveryAuthorizationV3Bytes(bytes2, expected) {
6858
6770
  const contentSource = object(envelope.contentSource, "v3 content source");
6859
6771
  const envelopeSignature = object(envelope.signature, "v3 envelope signature");
6860
6772
  const target = object(envelope.target, "v3 target");
6861
- const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
6862
- if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.audience !== expected.credentialsAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || signature.signerDid !== target.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
6773
+ const binding = object(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
6774
+ const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
6775
+ if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.label !== expected.request.documentName || request.shareExpiresAt !== envelope.expiry || request.audience !== expected.deliveryAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || target.nodeAudience !== binding.nodeAudience || signature.signerDid !== binding.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
6863
6776
  const issuedAt = Date.parse(String(request.issuedAt));
6864
6777
  const expiresAt = Date.parse(String(request.expiresAt));
6865
6778
  if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || expiresAt <= issuedAt || expiresAt - issuedAt > 15 * 6e4) throw new Error("v3 share delivery authorization time is invalid");
6866
6779
  const admissionSignature = decodeBase64Url2(signature.value);
6867
6780
  const { signature: _signature, ...unsignedAdmission } = admission;
6868
6781
  const digest4 = (0, import_sha2566.sha256)(new TextEncoder().encode(`${DELIVERY_ADMISSION_DOMAIN}${canonicalizeSignedObjectUnsigned(unsignedAdmission)}`));
6869
- if (admissionSignature.length !== 64 || !import_ed255198.ed25519.verify(admissionSignature, digest4, didKeyBytes(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
6782
+ if (admissionSignature.length !== 64 || !import_ed255197.ed25519.verify(admissionSignature, digest4, didKeyBytes(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
6870
6783
  return { request, admission };
6871
6784
  }
6872
6785