@tinycloud/share-sdk 1.0.0-beta.8 → 1.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.
package/dist/index.cjs CHANGED
@@ -6667,6 +6667,7 @@ function targetMatcher(target) {
6667
6667
  function targetKind(target) {
6668
6668
  return target.kind;
6669
6669
  }
6670
+ var OWNER_SHARE_ACTIONS = /* @__PURE__ */ new Set(["tinycloud.kv/get", "tinycloud.kv/list", "tinycloud.kv/metadata", "tinycloud.kv/put"]);
6670
6671
  function assertSafeInput(input) {
6671
6672
  if (!/^[A-Za-z0-9_-]{16,128}$/.test(input.shareId)) throw new TypeError("addressed share id is invalid");
6672
6673
  if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 0 || input.byteLength > SHARE_CONTENT_LIMIT) throw new TypeError("addressed content length is invalid");
@@ -6795,12 +6796,12 @@ function prepareAddressedShare(request) {
6795
6796
  throw new TypeError("addressed filename is invalid");
6796
6797
  }
6797
6798
  if (request.actions.length === 0 || request.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
6799
+ if (request.policyActions.some((action) => !OWNER_SHARE_ACTIONS.has(action))) throw new TypeError("addressed share action is not supported");
6798
6800
  const target = normalizeShareTarget(request.target);
6799
6801
  if (target.kind === "bearer") throw new TypeError("addressed target is required");
6800
6802
  if (target.kind === "recipientDid") return { target };
6801
- if (target.kind === "emailDomain") {
6802
- if (!request.actions.every((action) => action === "read" || action === "list") || !request.policyActions.every((action) => action === "tinycloud.kv/get" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata")) throw new TypeError("email-domain shares are view-only");
6803
- if (request.deliveryEmail !== void 0) throw new TypeError("email-domain shares are not emailed");
6803
+ if (target.kind === "emailDomain" && request.deliveryEmail !== void 0 && (request.deliveryEmail !== request.deliveryEmail.toLowerCase() || !request.deliveryEmail.endsWith(`@${target.domain}`))) {
6804
+ throw new TypeError("email-domain delivery address must be a lowercase mailbox at the domain");
6804
6805
  }
6805
6806
  return { target, credentialRequirement: mailboxCredentialCommitment(target) };
6806
6807
  }
@@ -8309,9 +8310,28 @@ function verifyV3PolicyAuthorization(input) {
8309
8310
  const binding = object3(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8310
8311
  const policy = object3(input.envelope.policy, "v3 policy");
8311
8312
  const now = Math.floor(Date.now() / 1e3);
8312
- if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || compact.payload.exp - compact.payload.nbf > 60 || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
8313
+ if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || !(compact.payload.exp * 1e3 <= Date.parse(input.envelope.expiry)) || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
8313
8314
  return compact;
8314
8315
  }
8316
+ function verifyV3PolicyDescendants(session, descendants, holderDid) {
8317
+ let parent = session;
8318
+ for (const link of descendants) {
8319
+ const child = verifyCompactUcanAuthorization(link.authorization, link.cid);
8320
+ const { remainingRedelegationDepth: parentDepth, ...parentFacts } = parent.payload.fct[0];
8321
+ const { remainingRedelegationDepth: depth, ...facts } = child.payload.fct[0];
8322
+ if (child.payload.iss.split("#", 1)[0] !== parent.payload.aud || child.payload.prf.length !== 1 || child.payload.prf[0] !== parent.cid || child.payload.nbf <= parent.payload.nbf || child.payload.exp >= parent.payload.exp || typeof parentDepth !== "number" || parentDepth <= 0 || depth !== parentDepth - 1 || canonicalize(facts) !== canonicalize(parentFacts) || canonicalize(child.payload.att) !== canonicalize(parent.payload.att)) throw new Error("v3 policy descendant does not extend its parent");
8323
+ parent = child;
8324
+ }
8325
+ const now = Math.floor(Date.now() / 1e3);
8326
+ if (parent.payload.aud !== holderDid) throw new Error("v3 policy delegation chain does not end at this holder");
8327
+ if (parent.payload.nbf > now || parent.payload.exp <= now) throw new Error("v3 policy delegation chain is not currently valid");
8328
+ return parent;
8329
+ }
8330
+ function requestedSessionExpiry(expiry) {
8331
+ const seconds = Math.floor(Date.parse(expiry) / 1e3);
8332
+ if (!Number.isFinite(seconds) || seconds <= Math.floor(Date.now() / 1e3) + 60) return void 0;
8333
+ return new Date(seconds * 1e3).toISOString().replace(/\.\d{3}Z$/, "Z");
8334
+ }
8315
8335
  function hex3(value) {
8316
8336
  return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
8317
8337
  }
@@ -8364,14 +8384,17 @@ var ShareRecipientClient = class {
8364
8384
  this.signer = options.sign;
8365
8385
  if (options.policyAuthorization !== void 0) {
8366
8386
  if (options.envelope.version !== 3) throw new Error("policy delegation fast path requires a v3 envelope");
8367
- const compact = verifyV3PolicyAuthorization({ ...options.policyAuthorization, envelope: options.envelope, holderDid: options.holderDid });
8387
+ const { authorization, cid: cid2, descendants = [] } = options.policyAuthorization;
8388
+ const sessionHolder = descendants.length === 0 ? options.holderDid : verifyCompactUcanAuthorization(authorization, cid2).payload.aud;
8389
+ const compact = verifyV3PolicyAuthorization({ authorization, cid: cid2, envelope: options.envelope, holderDid: sessionHolder });
8390
+ const leaf = verifyV3PolicyDescendants(compact, descendants, options.holderDid);
8368
8391
  const nodeAudience = compact.payload.fct[0].nodeAudience;
8369
- this.v3Authorization = options.policyAuthorization.authorization;
8392
+ this.v3Authorization = leaf.authorization;
8370
8393
  this.v3NodeAudience = nodeAudience;
8371
8394
  this.nativeSigner = options.sign;
8372
8395
  this.session = {
8373
- sessionId: options.policyAuthorization.cid,
8374
- expiresAt: new Date(compact.payload.exp * 1e3).toISOString(),
8396
+ sessionId: leaf.cid,
8397
+ expiresAt: new Date(leaf.payload.exp * 1e3).toISOString(),
8375
8398
  actions: options.envelope.actions,
8376
8399
  resource: options.envelope.resource
8377
8400
  };
@@ -8441,13 +8464,17 @@ var ShareRecipientClient = class {
8441
8464
  throw new Error(accountless ? "v4 ceremony requires a verified credential and requirement" : "v3 ceremony requires a claim");
8442
8465
  }
8443
8466
  this.options.onStage?.("policy-admission");
8444
- const delegationResponse = await this.fetchFn(new URL("/policy/v3/delegations", this.options.nodeOrigin), {
8467
+ const mintBody = accountless ? { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, requirement: material.requirement, credential: material.credentialEnvelope, presentation: material.presentation } : { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation };
8468
+ const mint = (body) => this.fetchFn(new URL("/policy/v3/delegations", this.options.nodeOrigin), {
8445
8469
  method: "POST",
8446
8470
  redirect: "error",
8447
8471
  headers: { accept: "application/json", "content-type": "application/json" },
8448
- body: JSON.stringify(accountless ? { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, requirement: material.requirement, credential: material.credentialEnvelope, presentation: material.presentation } : { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation }),
8472
+ body: JSON.stringify(body),
8449
8473
  ...this.options.signal === void 0 ? {} : { signal: this.options.signal }
8450
8474
  });
8475
+ const requestedExpiresAt = requestedSessionExpiry(envelope.expiry);
8476
+ let delegationResponse = await mint(requestedExpiresAt === void 0 ? mintBody : { ...mintBody, requestedExpiresAt });
8477
+ if (requestedExpiresAt !== void 0 && delegationResponse.status === 422) delegationResponse = await mint(mintBody);
8451
8478
  if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
8452
8479
  const delegation = object3(await delegationResponse.json(), "v3 policy delegation");
8453
8480
  if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");