@tinycloud/sdk-core 3.0.0-beta.1 → 3.0.0-beta.2

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
@@ -98,6 +98,7 @@ __export(index_exports, {
98
98
  DuckDbService: () => import_sdk_services8.DuckDbService,
99
99
  ED25519_JCS_SIGNATURE_SUITE: () => ED25519_JCS_SIGNATURE_SUITE,
100
100
  EIP191_JCS_SIGNATURE_SUITE: () => EIP191_JCS_SIGNATURE_SUITE,
101
+ EMAIL_CREDENTIAL_MAX_AGE_SECONDS: () => EMAIL_CREDENTIAL_MAX_AGE_SECONDS,
101
102
  ENCRYPTION: () => import_bootstrap4.ENCRYPTION,
102
103
  ENCRYPTION_MANIFEST_SPACE: () => ENCRYPTION_MANIFEST_SPACE,
103
104
  ENCRYPTION_NETWORK_URN_PREFIX: () => import_sdk_services8.ENCRYPTION_NETWORK_URN_PREFIX,
@@ -149,6 +150,8 @@ __export(index_exports, {
149
150
  POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA: () => POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA,
150
151
  POLICY_PRESENTATION_V3_DOMAIN: () => POLICY_PRESENTATION_V3_DOMAIN,
151
152
  POLICY_PRESENTATION_V3_SCHEMA: () => POLICY_PRESENTATION_V3_SCHEMA,
153
+ POLICY_PRESENTATION_V4_DOMAIN: () => POLICY_PRESENTATION_V4_DOMAIN,
154
+ POLICY_PRESENTATION_V4_SCHEMA: () => POLICY_PRESENTATION_V4_SCHEMA,
152
155
  POLICY_SCHEMA: () => POLICY_SCHEMA,
153
156
  POLICY_SESSION_UCAN_V1_PROFILE: () => POLICY_SESSION_UCAN_V1_PROFILE,
154
157
  POLICY_STATUS_SCHEMA: () => POLICY_STATUS_SCHEMA,
@@ -226,6 +229,7 @@ __export(index_exports, {
226
229
  activateSessionWithHost: () => activateSessionWithHost,
227
230
  addressStorageKey: () => addressStorageKey,
228
231
  admitPolicyCredentialV3: () => admitPolicyCredentialV3,
232
+ admitPolicyCredentialV4: () => admitPolicyCredentialV4,
229
233
  applyPrefix: () => applyPrefix,
230
234
  bootstrapEncryptionNetworkId: () => import_bootstrap4.bootstrapEncryptionNetworkId,
231
235
  bootstrapSpaceId: () => import_bootstrap4.bootstrapSpaceId,
@@ -236,6 +240,7 @@ __export(index_exports, {
236
240
  buildDecryptInvocation: () => import_sdk_services8.buildDecryptInvocation,
237
241
  buildNetworkId: () => import_sdk_services8.buildNetworkId,
238
242
  buildPolicyCredentialPresentationV3: () => buildPolicyCredentialPresentationV3,
243
+ buildPolicyCredentialPresentationV4: () => buildPolicyCredentialPresentationV4,
239
244
  buildSpaceUri: () => buildSpaceUri,
240
245
  canonicalDigest: () => canonicalDigest,
241
246
  canonicalHashHex: () => import_sdk_services8.canonicalHashHex,
@@ -266,6 +271,7 @@ __export(index_exports, {
266
271
  createAndSignSignedObject: () => createAndSignSignedObject,
267
272
  createCapabilityKeyRegistry: () => createCapabilityKeyRegistry,
268
273
  createDelegatedShareKey: () => createDelegatedShareKey,
274
+ createEmailCredentialRequirement: () => createEmailCredentialRequirement,
269
275
  createHolderBinding: () => createHolderBinding,
270
276
  createInMemoryLocalNodeIdentityStore: () => createInMemoryLocalNodeIdentityStore,
271
277
  createLocalStorageLocalNodeIdentityStore: () => createLocalStorageLocalNodeIdentityStore,
@@ -344,6 +350,7 @@ __export(index_exports, {
344
350
  isNetworkId: () => import_sdk_services8.isNetworkId,
345
351
  isPlausibleOpenKeyActionId: () => isPlausibleOpenKeyActionId,
346
352
  isV2ShareLink: () => isV2ShareLink,
353
+ issuedCredentialEnvelopeFromVerified: () => issuedCredentialEnvelopeFromVerified,
347
354
  jcsCanonicalize: () => jcsCanonicalize,
348
355
  loadManifest: () => loadManifest,
349
356
  locationPayloadForRecord: () => locationPayloadForRecord,
@@ -406,6 +413,7 @@ __export(index_exports, {
406
413
  signCompactUcanAuthorization: () => signCompactUcanAuthorization,
407
414
  signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
408
415
  signLocationRecord: () => signLocationRecord,
416
+ signPolicyCredentialPresentationV4: () => signPolicyCredentialPresentationV4,
409
417
  signShareEnvelope: () => signShareEnvelope,
410
418
  signedObjectIdFor: () => signedObjectIdFor,
411
419
  submitHostDelegation: () => submitHostDelegation,
@@ -1009,6 +1017,17 @@ function isDigest(value) {
1009
1017
 
1010
1018
  // src/credentials/requirement.ts
1011
1019
  var ID2 = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
1020
+ var EMAIL_CREDENTIAL_MAX_AGE_SECONDS = 3600;
1021
+ function createEmailCredentialRequirement(input) {
1022
+ return validateCredentialRequirement({
1023
+ type: "TinyCloudCredentialRequirement",
1024
+ version: 1,
1025
+ profile: input.profile,
1026
+ credentialType: input.credentialType,
1027
+ claims: { email: input.email },
1028
+ maxAgeSeconds: EMAIL_CREDENTIAL_MAX_AGE_SECONDS
1029
+ });
1030
+ }
1012
1031
  function validateCredentialRequirement(value) {
1013
1032
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
1014
1033
  const raw = value;
@@ -3873,6 +3892,7 @@ async function requestPolicyChallengeV3(input) {
3873
3892
  const response = await fetchFn(new URL("/share/v3/policy/challenges", input.nodeOrigin), {
3874
3893
  method: "POST",
3875
3894
  redirect: "error",
3895
+ signal: input.signal,
3876
3896
  headers: { accept: "application/json", "content-type": "application/json" },
3877
3897
  body: JSON.stringify({ policyCid: input.policyCid, recipientDid: input.recipientDid, requestedCapabilities: input.requestedCapabilities.map(normalizeUnifiedPolicyCapability) })
3878
3898
  });
@@ -4042,6 +4062,11 @@ var POLICY_SESSION_FACT_KEYS = [
4042
4062
  "issuanceAuditDigestHex",
4043
4063
  "remainingRedelegationDepth"
4044
4064
  ];
4065
+ var POLICY_SESSION_V4_AUDIT_FACT_KEYS = [
4066
+ "credentialIdAuditDigestHex",
4067
+ "presentationJtiAuditDigestHex"
4068
+ ];
4069
+ var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
4045
4070
  var KV_ACTIONS = /* @__PURE__ */ new Set([
4046
4071
  "tinycloud.kv/get",
4047
4072
  "tinycloud.kv/list",
@@ -4434,9 +4459,13 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
4434
4459
  if (expectedProofs !== void 0 && (parsed.payload.prf[0] !== expectedProofs[0] || parsed.payload.prf[1] !== expectedProofs[1]))
4435
4460
  throw new Error("policy session UCAN ordered proofs do not match");
4436
4461
  const factKeys = Object.keys(fact);
4437
- if (factKeys.length !== POLICY_SESSION_FACT_KEYS.length || factKeys.some((key) => !POLICY_SESSION_FACT_KEYS.includes(key)) || POLICY_SESSION_FACT_KEYS.some((key) => !(key in fact)))
4462
+ const hasLegacyFactShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
4463
+ const hasV4AuditFactShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
4464
+ if (!hasLegacyFactShape && !hasV4AuditFactShape)
4438
4465
  throw new Error("policy session UCAN fact is incomplete");
4439
- 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.enforcerDid || parsed.payload.aud !== fact.recipientDid)
4466
+ if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
4467
+ throw new Error("policy session UCAN v4 audit facts are invalid");
4468
+ 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)
4440
4469
  throw new Error("policy session UCAN fact is invalid");
4441
4470
  return {
4442
4471
  authorization,
@@ -4495,6 +4524,8 @@ var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
4495
4524
  var POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE = "TinyCloudPolicyCredentialRequirement";
4496
4525
  var POLICY_PRESENTATION_V3_SCHEMA = "xyz.tinycloud.policy/presentation/v3";
4497
4526
  var POLICY_PRESENTATION_V3_DOMAIN = "xyz.tinycloud.policy/Presentation/v3\0";
4527
+ var POLICY_PRESENTATION_V4_SCHEMA = "xyz.tinycloud.policy/presentation/v4";
4528
+ var POLICY_PRESENTATION_V4_DOMAIN = "xyz.tinycloud.policy/Presentation/v4\0";
4498
4529
  function record(value, label) {
4499
4530
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4500
4531
  throw new Error(`${label} must be an object`);
@@ -4669,6 +4700,97 @@ function envelopeFromVerified(verified) {
4669
4700
  } = verified;
4670
4701
  return envelope;
4671
4702
  }
4703
+ function issuedCredentialEnvelopeFromVerified(verified) {
4704
+ return envelopeFromVerified(verified);
4705
+ }
4706
+ async function signPolicyCredentialPresentationV4(value, sign) {
4707
+ const item = record(value, "Policy presentation v4");
4708
+ exactKeys2(item, [
4709
+ "schema",
4710
+ "jti",
4711
+ "challengeId",
4712
+ "nonce",
4713
+ "policyCid",
4714
+ "nodeAudience",
4715
+ "holderDid",
4716
+ "subjectDid",
4717
+ "credentialDigest",
4718
+ "requirementDigest",
4719
+ "descriptorDigest",
4720
+ "requestedCapabilities",
4721
+ "issuedAt",
4722
+ "expiresAt"
4723
+ ], "Policy presentation v4");
4724
+ if (item.schema !== POLICY_PRESENTATION_V4_SCHEMA || item.holderDid !== item.subjectDid || !Array.isArray(item.requestedCapabilities)) throw new Error("Policy presentation v4 is invalid");
4725
+ const holderDid = nonEmpty(item.holderDid, "Policy presentation v4 holder DID");
4726
+ didKeyBytes(holderDid);
4727
+ const unsigned = Object.freeze({
4728
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4729
+ jti: nonEmpty(item.jti, "Policy presentation v4 jti"),
4730
+ challengeId: nonEmpty(item.challengeId, "Policy presentation v4 challenge"),
4731
+ nonce: nonEmpty(item.nonce, "Policy presentation v4 nonce"),
4732
+ policyCid: nonEmpty(item.policyCid, "Policy presentation v4 policy CID"),
4733
+ nodeAudience: nonEmpty(item.nodeAudience, "Policy presentation v4 audience"),
4734
+ holderDid,
4735
+ subjectDid: holderDid,
4736
+ credentialDigest: nonEmpty(item.credentialDigest, "Policy presentation v4 credential digest"),
4737
+ requirementDigest: nonEmpty(item.requirementDigest, "Policy presentation v4 requirement digest"),
4738
+ descriptorDigest: nonEmpty(item.descriptorDigest, "Policy presentation v4 descriptor digest"),
4739
+ // The low-level signer preserves the frozen wire bytes exactly. The
4740
+ // policy-aware builder performs capability normalization and containment.
4741
+ requestedCapabilities: Object.freeze(item.requestedCapabilities),
4742
+ issuedAt: date(item.issuedAt, "Policy presentation v4 issuedAt"),
4743
+ expiresAt: date(item.expiresAt, "Policy presentation v4 expiresAt")
4744
+ });
4745
+ const digest4 = (0, import_sha2562.sha256)(new TextEncoder().encode(
4746
+ POLICY_PRESENTATION_V4_DOMAIN + jcsCanonicalize(unsigned)
4747
+ ));
4748
+ const signature = await sign(digest4);
4749
+ if (signature.length !== 64 || !import_ed255194.ed25519.verify(signature, digest4, didKeyBytes(holderDid))) throw new Error("policy presentation signature is invalid");
4750
+ return Object.freeze({
4751
+ ...unsigned,
4752
+ signature: Object.freeze({
4753
+ suite: "Ed25519",
4754
+ signerDid: holderDid,
4755
+ value: encodeBase64Url(signature)
4756
+ })
4757
+ });
4758
+ }
4759
+ async function buildPolicyCredentialPresentationV4(input) {
4760
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4761
+ const requirement = validateCredentialRequirement(input.requirement);
4762
+ const commitment = policy.credentialRequirement;
4763
+ const requirementDigest = await credentialRequirementDigest(requirement);
4764
+ const credentialDigest = await sha256Base64Url(input.credential.credential);
4765
+ if (requirementDigest !== commitment.requirementDigest || input.credential.descriptorDigest !== commitment.descriptorDigest || input.credential.credentialDigest !== credentialDigest || input.credential.issuerDid !== commitment.issuerDid || input.credential.issuerKid !== commitment.issuerKid || jcsCanonicalize(input.credential.profile) !== jcsCanonicalize(commitment.profile) || jcsCanonicalize(input.credential.credentialType) !== jcsCanonicalize(commitment.credentialType)) throw new Error("credential does not match the signed Policy/v2 requirement");
4766
+ const requestedCapabilities = input.requestedCapabilities.map(normalizeUnifiedPolicyCapability);
4767
+ if (requestedCapabilities.length === 0 || requestedCapabilities.some((requested) => !policy.capabilityCeiling.some((authority) => unifiedPolicyCapabilityContains(authority, requested)))) throw new Error("requested capabilities exceed the signed Policy/v2 ceiling");
4768
+ const holderDid = input.credential.holderDid;
4769
+ if (input.challenge.policyCid !== input.policyCid || input.challenge.recipientDid !== holderDid || input.credential.subjectDid !== holderDid || typeof input.challenge.nodeAudience !== "string" || input.challenge.nodeAudience.length === 0 || typeof input.challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(input.challenge.expiresAt))) throw new Error("policy challenge binding is invalid");
4770
+ const now = input.now ?? /* @__PURE__ */ new Date();
4771
+ const expiresAtMs = Math.min(
4772
+ now.getTime() + 6e4,
4773
+ Date.parse(input.challenge.expiresAt),
4774
+ Date.parse(input.credential.expiresAt)
4775
+ );
4776
+ if (expiresAtMs <= now.getTime()) throw new Error("policy challenge is expired");
4777
+ return signPolicyCredentialPresentationV4(Object.freeze({
4778
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4779
+ jti: input.jti ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16))),
4780
+ challengeId: input.challenge.challengeId,
4781
+ nonce: input.challenge.nonce,
4782
+ policyCid: input.policyCid,
4783
+ nodeAudience: input.challenge.nodeAudience,
4784
+ holderDid,
4785
+ subjectDid: holderDid,
4786
+ credentialDigest,
4787
+ requirementDigest,
4788
+ descriptorDigest: input.credential.descriptorDigest,
4789
+ requestedCapabilities: Object.freeze(requestedCapabilities),
4790
+ issuedAt: rfc3339Seconds(now),
4791
+ expiresAt: rfc3339Seconds(new Date(expiresAtMs))
4792
+ }), input.sign);
4793
+ }
4672
4794
  async function buildPolicyCredentialPresentationV3(input) {
4673
4795
  const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4674
4796
  const requirement = validateCredentialRequirement(input.requirement);
@@ -4758,7 +4880,8 @@ async function admitPolicyCredentialV3(input) {
4758
4880
  policyCid: input.policyCid,
4759
4881
  recipientDid: input.credential.holderDid,
4760
4882
  requestedCapabilities: input.requestedCapabilities,
4761
- fetch: fetchFn
4883
+ fetch: fetchFn,
4884
+ signal: input.signal
4762
4885
  });
4763
4886
  const presentation = await buildPolicyCredentialPresentationV3({
4764
4887
  ...input,
@@ -4770,6 +4893,7 @@ async function admitPolicyCredentialV3(input) {
4770
4893
  {
4771
4894
  method: "POST",
4772
4895
  redirect: "error",
4896
+ signal: input.signal,
4773
4897
  headers: { accept: "application/json", "content-type": "application/json" },
4774
4898
  body: JSON.stringify({
4775
4899
  policyCid: input.policyCid,
@@ -4798,6 +4922,57 @@ async function admitPolicyCredentialV3(input) {
4798
4922
  if (session.cid !== value.sessionCid || session.aud !== input.credential.holderDid || session.fact.policyCid !== input.policyCid || session.fact.recipientDid !== input.credential.holderDid || session.fact.policyDelegationCid !== input.policyRootCid || session.fact.enforcementDelegationCid !== input.enforcementRootCid || jcsCanonicalize(session.att) !== jcsCanonicalize(expectedAttenuation)) {
4799
4923
  throw new Error("policy delegation signed binding is invalid");
4800
4924
  }
4925
+ input.signal?.throwIfAborted();
4926
+ return Object.freeze({ challenge, presentation, session });
4927
+ }
4928
+ function validatePolicyCredentialAdmissionV4Authority(session, expected) {
4929
+ if (session.iss.split("#", 1)[0] !== expected.nodeAudience || session.fact.nodeAudience !== expected.nodeAudience || session.fact.enforcerDid !== expected.enforcerDid) throw new Error("policy delegation authority binding is invalid");
4930
+ }
4931
+ async function admitPolicyCredentialV4(input) {
4932
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4933
+ const nodeOrigin = policyNodeOrigin(input.nodeOrigin);
4934
+ const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
4935
+ const challenge = await requestPolicyChallengeV3({
4936
+ nodeOrigin,
4937
+ policyCid: input.policyCid,
4938
+ recipientDid: input.credential.holderDid,
4939
+ requestedCapabilities: input.requestedCapabilities,
4940
+ fetch: fetchFn,
4941
+ signal: input.signal
4942
+ });
4943
+ const presentation = await buildPolicyCredentialPresentationV4({
4944
+ ...input,
4945
+ policy,
4946
+ challenge
4947
+ });
4948
+ const response = await fetchFn(new URL("/share/v3/policy/delegations", nodeOrigin), {
4949
+ method: "POST",
4950
+ redirect: "error",
4951
+ signal: input.signal,
4952
+ headers: { accept: "application/json", "content-type": "application/json" },
4953
+ body: JSON.stringify({
4954
+ policyCid: input.policyCid,
4955
+ challengeId: challenge.challengeId,
4956
+ nonce: challenge.nonce,
4957
+ requirement: validateCredentialRequirement(input.requirement),
4958
+ credential: envelopeFromVerified(input.credential),
4959
+ presentation
4960
+ })
4961
+ });
4962
+ if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4963
+ const value = record(await response.json(), "policy delegation");
4964
+ if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
4965
+ const session = parsePolicySessionUcan(value.authorization, [
4966
+ input.policyRootCid,
4967
+ input.enforcementRootCid
4968
+ ]);
4969
+ const expectedAttenuation = compactAttenuationForPolicyCapabilities(input.requestedCapabilities);
4970
+ validatePolicyCredentialAdmissionV4Authority(session, {
4971
+ nodeAudience: input.expectedNodeAudience,
4972
+ enforcerDid: input.expectedEnforcerDid
4973
+ });
4974
+ if (session.cid !== value.sessionCid || session.aud !== input.credential.holderDid || session.fact.policyCid !== input.policyCid || session.fact.recipientDid !== input.credential.holderDid || session.fact.policyDelegationCid !== input.policyRootCid || session.fact.enforcementDelegationCid !== input.enforcementRootCid || jcsCanonicalize(session.att) !== jcsCanonicalize(expectedAttenuation)) throw new Error("policy delegation signed binding is invalid");
4975
+ input.signal?.throwIfAborted();
4801
4976
  return Object.freeze({ challenge, presentation, session });
4802
4977
  }
4803
4978