@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.
@@ -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,
@@ -4156,6 +4182,7 @@ var RECOVERABLE = /* @__PURE__ */ new Set([
4156
4182
  "ISSUER_UNREADY",
4157
4183
  "OFFLINE",
4158
4184
  "POPUP_BLOCKED",
4185
+ "PROOF_REJECTED",
4159
4186
  "REQUEST_EXPIRED",
4160
4187
  "SIGNATURE_REJECTED",
4161
4188
  "UNSUPPORTED_PROFILE",
@@ -4174,6 +4201,7 @@ var CredentialError = class extends Error {
4174
4201
 
4175
4202
  // src/credentials/requirement.ts
4176
4203
  var ID = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
4204
+ var CLAIM_NAME = /^[a-z0-9][A-Za-z0-9._-]{0,127}$/;
4177
4205
  function validateCredentialRequirement(value) {
4178
4206
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
4179
4207
  const raw = value;
@@ -4187,7 +4215,7 @@ function validateCredentialRequirement(value) {
4187
4215
  if (typeof raw.claims !== "object" || raw.claims === null || Array.isArray(raw.claims)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claims are invalid");
4188
4216
  const claims = {};
4189
4217
  for (const [key, value2] of Object.entries(raw.claims).sort(([a], [b]) => a.localeCompare(b))) {
4190
- if (!ID.test(key) || typeof value2 !== "string" || value2.length === 0 || value2.length > 4096) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claim is invalid");
4218
+ if (!CLAIM_NAME.test(key) || typeof value2 !== "string" || value2.length === 0 || value2.length > 4096) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential claim is invalid");
4191
4219
  claims[key] = value2;
4192
4220
  }
4193
4221
  if (raw.maxAgeSeconds !== void 0 && (!Number.isSafeInteger(raw.maxAgeSeconds) || raw.maxAgeSeconds < 1)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential freshness is invalid");
@@ -4568,24 +4596,21 @@ async function admitPolicyCredentialV3(input) {
4568
4596
  policy,
4569
4597
  challenge
4570
4598
  });
4571
- const response = await fetchFn(
4599
+ const response = await postPolicyDelegation(
4600
+ fetchFn,
4572
4601
  new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
4573
4602
  {
4574
- method: "POST",
4575
- redirect: "error",
4576
- signal: input.signal,
4577
- headers: { accept: "application/json", "content-type": "application/json" },
4578
- body: JSON.stringify({
4579
- policyCid: input.policyCid,
4580
- challengeId: challenge.challengeId,
4581
- nonce: challenge.nonce,
4582
- requirement: validateCredentialRequirement(input.requirement),
4583
- credential: envelopeFromVerified(input.credential),
4584
- accountAuthorizationCid,
4585
- credentialSpaceId,
4586
- presentation
4587
- })
4588
- }
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
4589
4614
  );
4590
4615
  if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4591
4616
  const value = record(await response.json(), "policy delegation");
@@ -4626,20 +4651,20 @@ async function admitPolicyCredentialV4(input) {
4626
4651
  policy,
4627
4652
  challenge
4628
4653
  });
4629
- const response = await fetchFn(new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin), {
4630
- method: "POST",
4631
- redirect: "error",
4632
- signal: input.signal,
4633
- headers: { accept: "application/json", "content-type": "application/json" },
4634
- body: JSON.stringify({
4654
+ const response = await postPolicyDelegation(
4655
+ fetchFn,
4656
+ new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
4657
+ {
4635
4658
  policyCid: input.policyCid,
4636
4659
  challengeId: challenge.challengeId,
4637
4660
  nonce: challenge.nonce,
4638
4661
  requirement: validateCredentialRequirement(input.requirement),
4639
4662
  credential: envelopeFromVerified(input.credential),
4640
4663
  presentation
4641
- })
4642
- });
4664
+ },
4665
+ requestedSessionExpiry(input.requestedExpiresAt, policy),
4666
+ input.signal
4667
+ );
4643
4668
  if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4644
4669
  const value = record(await response.json(), "policy delegation");
4645
4670
  if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
@@ -4663,6 +4688,27 @@ async function admitPolicyCredentialV4(input) {
4663
4688
  input.signal?.throwIfAborted();
4664
4689
  return Object.freeze({ challenge, presentation, session, delegationImported: true });
4665
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
+ }
4666
4712
  function policyRuntimeBasePath2(value) {
4667
4713
  const path = value ?? "/policy/v3";
4668
4714
  if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {