@tinycloud/sdk-core 3.0.0-beta.18 → 3.0.0-beta.19

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.
@@ -108,6 +108,7 @@ __export(policy_exports, {
108
108
  requestPolicyChallengeV3: () => requestPolicyChallengeV3,
109
109
  revokePolicyRootV3: () => revokePolicyRootV3,
110
110
  serializeJcsJson: () => serialize,
111
+ signCompactPolicyDescendant: () => signCompactPolicyDescendant,
111
112
  signCompactUcanAuthorization: () => signCompactUcanAuthorization,
112
113
  signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
113
114
  signPolicyCredentialPresentationV4: () => signPolicyCredentialPresentationV4,
@@ -3939,8 +3940,9 @@ async function signCompactUcanAuthorization(input) {
3939
3940
  throw new Error("compact invocation lifetime must be between one and 60 seconds");
3940
3941
  return signCompactUcan(input);
3941
3942
  }
3943
+ var MAX_POLICY_ROOT_SECONDS = 31 * 24 * 60 * 60;
3942
3944
  async function signCompactUcanRootAuthorization(input) {
3943
- if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 31 * 24 * 60 * 60)
3945
+ if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > MAX_POLICY_ROOT_SECONDS)
3944
3946
  throw new Error("compact policy root lifetime must be between one second and 31 days");
3945
3947
  return signCompactUcan({ ...input, proofs: [] });
3946
3948
  }
@@ -3975,7 +3977,7 @@ function createCompactPolicyInvocation(input) {
3975
3977
  const signature = import_ed255192.ed25519.sign(signingInput, input.privateKey);
3976
3978
  return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url(signature)}`);
3977
3979
  }
3978
- function createCompactPolicyDescendant(input) {
3980
+ function compactPolicyDescendantClaims(input) {
3979
3981
  const parent = parseCompactUcanAuthorization(input.parentAuthorization, input.parentCid);
3980
3982
  if (parent.payload.aud !== input.issuerDid) throw new Error("policy descendant issuer is not the parent audience");
3981
3983
  const parentFact = parent.payload.fct[0];
@@ -3983,29 +3985,53 @@ function createCompactPolicyDescendant(input) {
3983
3985
  if (!Number.isInteger(depth) || depth <= 0) throw new Error("policy descendant depth is exhausted");
3984
3986
  if (!compactAttenuationContains(parent.payload.att, input.attenuation))
3985
3987
  throw new Error("policy descendant exceeds the parent attenuation");
3988
+ const now = Math.max(input.now ?? parent.payload.nbf + 1, parent.payload.nbf + 1);
3989
+ const exp = Math.min(input.expiresAt ?? parent.payload.exp - 1, parent.payload.exp - 1);
3990
+ if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
3991
+ return {
3992
+ parentCid: parent.cid,
3993
+ fact: { ...parentFact, remainingRedelegationDepth: depth - 1 },
3994
+ notBefore: now,
3995
+ expiresAt: exp,
3996
+ nonce: input.nonce ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16)))
3997
+ };
3998
+ }
3999
+ function createCompactPolicyDescendant(input) {
4000
+ const claims = compactPolicyDescendantClaims(input);
3986
4001
  const principal = input.issuerDid.split("#", 1)[0];
3987
4002
  const didMaterial = base58btc.decode(principal.slice("did:key:".length));
3988
4003
  const publicKey = import_ed255192.ed25519.getPublicKey(input.privateKey);
3989
4004
  if (didMaterial.length !== 34 || !equalBytes(publicKey, didMaterial.slice(2))) throw new Error("policy descendant signer does not bind issuer DID");
3990
- const now = Math.max(input.now ?? Math.floor(Date.now() / 1e3), parent.payload.nbf + 1);
3991
- const exp = Math.min(input.expiresAt ?? now + 60, now + 60, parent.payload.exp - 1);
3992
- if (exp <= now) throw new Error("policy parent has no strictly narrower validity window");
3993
4005
  const header = { alg: "EdDSA", jwk: { alg: "EdDSA", crv: "Ed25519", kty: "OKP", x: encodeBase64Url(publicKey) }, typ: "JWT", ucv: "0.10.0" };
3994
4006
  const payload = {
3995
4007
  att: input.attenuation,
3996
4008
  aud: input.audienceDid,
3997
- exp,
3998
- fct: [{ ...parentFact, remainingRedelegationDepth: depth - 1 }],
4009
+ exp: claims.expiresAt,
4010
+ fct: [claims.fact],
3999
4011
  iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
4000
- nbf: now,
4001
- nnc: input.nonce ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16))),
4002
- prf: [parent.cid]
4012
+ nbf: claims.notBefore,
4013
+ nnc: claims.nonce,
4014
+ prf: [claims.parentCid]
4003
4015
  };
4004
4016
  const protectedSegment = encodeBase64Url(new TextEncoder().encode(jcsCanonicalize(header)));
4005
4017
  const payloadSegment = encodeBase64Url(new TextEncoder().encode(jcsCanonicalize(payload)));
4006
4018
  const signature = import_ed255192.ed25519.sign(new TextEncoder().encode(`${protectedSegment}.${payloadSegment}`), input.privateKey);
4007
4019
  return parseCompactUcanAuthorization(`${protectedSegment}.${payloadSegment}.${encodeBase64Url(signature)}`);
4008
4020
  }
4021
+ async function signCompactPolicyDescendant(input) {
4022
+ const claims = compactPolicyDescendantClaims(input);
4023
+ return signCompactUcan({
4024
+ issuerDid: input.issuerDid,
4025
+ audienceDid: input.audienceDid,
4026
+ attenuation: input.attenuation,
4027
+ facts: [claims.fact],
4028
+ proofs: [claims.parentCid],
4029
+ notBefore: claims.notBefore,
4030
+ expiresAt: claims.expiresAt,
4031
+ nonce: claims.nonce,
4032
+ sign: input.sign
4033
+ });
4034
+ }
4009
4035
  function compactAttenuationContains(parent, child) {
4010
4036
  const selector = (value) => {
4011
4037
  if (value.length !== 1 || !isRecordValue(value[0])) return void 0;
@@ -4075,7 +4101,7 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
4075
4101
  throw new Error("policy session UCAN fact is incomplete");
4076
4102
  if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
4077
4103
  throw new Error("policy session UCAN v4 audit facts are invalid");
4078
- 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)
4104
+ 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)
4079
4105
  throw new Error("policy session UCAN fact is invalid");
4080
4106
  return {
4081
4107
  authorization,
@@ -4570,24 +4596,21 @@ async function admitPolicyCredentialV3(input) {
4570
4596
  policy,
4571
4597
  challenge
4572
4598
  });
4573
- const response = await fetchFn(
4599
+ const response = await postPolicyDelegation(
4600
+ fetchFn,
4574
4601
  new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
4575
4602
  {
4576
- method: "POST",
4577
- redirect: "error",
4578
- signal: input.signal,
4579
- headers: { accept: "application/json", "content-type": "application/json" },
4580
- body: JSON.stringify({
4581
- policyCid: input.policyCid,
4582
- challengeId: challenge.challengeId,
4583
- nonce: challenge.nonce,
4584
- requirement: validateCredentialRequirement(input.requirement),
4585
- credential: envelopeFromVerified(input.credential),
4586
- accountAuthorizationCid,
4587
- credentialSpaceId,
4588
- presentation
4589
- })
4590
- }
4603
+ policyCid: input.policyCid,
4604
+ challengeId: challenge.challengeId,
4605
+ nonce: challenge.nonce,
4606
+ requirement: validateCredentialRequirement(input.requirement),
4607
+ credential: envelopeFromVerified(input.credential),
4608
+ accountAuthorizationCid,
4609
+ credentialSpaceId,
4610
+ presentation
4611
+ },
4612
+ requestedSessionExpiry(input.requestedExpiresAt, policy),
4613
+ input.signal
4591
4614
  );
4592
4615
  if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4593
4616
  const value = record(await response.json(), "policy delegation");
@@ -4628,20 +4651,20 @@ async function admitPolicyCredentialV4(input) {
4628
4651
  policy,
4629
4652
  challenge
4630
4653
  });
4631
- const response = await fetchFn(new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin), {
4632
- method: "POST",
4633
- redirect: "error",
4634
- signal: input.signal,
4635
- headers: { accept: "application/json", "content-type": "application/json" },
4636
- body: JSON.stringify({
4654
+ const response = await postPolicyDelegation(
4655
+ fetchFn,
4656
+ new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
4657
+ {
4637
4658
  policyCid: input.policyCid,
4638
4659
  challengeId: challenge.challengeId,
4639
4660
  nonce: challenge.nonce,
4640
4661
  requirement: validateCredentialRequirement(input.requirement),
4641
4662
  credential: envelopeFromVerified(input.credential),
4642
4663
  presentation
4643
- })
4644
- });
4664
+ },
4665
+ requestedSessionExpiry(input.requestedExpiresAt, policy),
4666
+ input.signal
4667
+ );
4645
4668
  if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4646
4669
  const value = record(await response.json(), "policy delegation");
4647
4670
  if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
@@ -4665,6 +4688,27 @@ async function admitPolicyCredentialV4(input) {
4665
4688
  input.signal?.throwIfAborted();
4666
4689
  return Object.freeze({ challenge, presentation, session, delegationImported: true });
4667
4690
  }
4691
+ function requestedSessionExpiry(requested, policy) {
4692
+ const value = requested === void 0 ? policy.expiresAt : requested;
4693
+ if (value === null || value === void 0) return void 0;
4694
+ const seconds = Math.floor(Date.parse(value) / 1e3);
4695
+ if (!Number.isFinite(seconds) || seconds <= Math.floor(Date.now() / 1e3) + 60) return void 0;
4696
+ return new Date(seconds * 1e3).toISOString().replace(/\.\d{3}Z$/, "Z");
4697
+ }
4698
+ async function postPolicyDelegation(fetchFn, url, body, requestedExpiresAt, signal) {
4699
+ const send = (payload) => fetchFn(url, {
4700
+ method: "POST",
4701
+ redirect: "error",
4702
+ signal,
4703
+ headers: { accept: "application/json", "content-type": "application/json" },
4704
+ body: JSON.stringify(payload)
4705
+ });
4706
+ if (requestedExpiresAt === void 0) return send(body);
4707
+ const response = await send({ ...body, requestedExpiresAt });
4708
+ if (response.status !== 422) return response;
4709
+ await response.body?.cancel().catch(() => void 0);
4710
+ return send(body);
4711
+ }
4668
4712
  function policyRuntimeBasePath2(value) {
4669
4713
  const path = value ?? "/policy/v3";
4670
4714
  if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {