@tinycloud/sdk-core 3.0.0-beta.0 → 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,
@@ -136,6 +137,7 @@ __export(index_exports, {
136
137
  NETWORK_NAME_PATTERN: () => import_sdk_services8.NETWORK_NAME_PATTERN,
137
138
  NetworkIdError: () => import_sdk_services8.NetworkIdError,
138
139
  OPENKEY_ACTION_ID_SEPARATOR: () => OPENKEY_ACTION_ID_SEPARATOR,
140
+ OpenKeyManageKeyError: () => OpenKeyManageKeyError,
139
141
  POLICY_CAPABILITY_V1_DOMAIN: () => POLICY_CAPABILITY_V1_DOMAIN,
140
142
  POLICY_CAPABILITY_V1_SCHEMA: () => POLICY_CAPABILITY_V1_SCHEMA,
141
143
  POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE: () => POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE,
@@ -148,6 +150,8 @@ __export(index_exports, {
148
150
  POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA: () => POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA,
149
151
  POLICY_PRESENTATION_V3_DOMAIN: () => POLICY_PRESENTATION_V3_DOMAIN,
150
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,
151
155
  POLICY_SCHEMA: () => POLICY_SCHEMA,
152
156
  POLICY_SESSION_UCAN_V1_PROFILE: () => POLICY_SESSION_UCAN_V1_PROFILE,
153
157
  POLICY_STATUS_SCHEMA: () => POLICY_STATUS_SCHEMA,
@@ -206,7 +210,9 @@ __export(index_exports, {
206
210
  SpaceService: () => SpaceService,
207
211
  TINYCLOUD_ACCOUNT_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_ACCOUNT_SPACE_MANIFEST,
208
212
  TINYCLOUD_APPLICATIONS_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_APPLICATIONS_SPACE_MANIFEST,
213
+ TINYCLOUD_CANONICAL_IDENTITY_CLAIM: () => TINYCLOUD_CANONICAL_IDENTITY_CLAIM,
209
214
  TINYCLOUD_DEFAULT_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_DEFAULT_SPACE_MANIFEST,
215
+ TINYCLOUD_MANAGE_KEY_SCOPE: () => TINYCLOUD_MANAGE_KEY_SCOPE,
210
216
  TINYCLOUD_PUBLIC_SPACE_MANIFEST: () => import_bootstrap4.TINYCLOUD_PUBLIC_SPACE_MANIFEST,
211
217
  TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST: () => import_bootstrap4.TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
212
218
  TinyCloud: () => TinyCloud,
@@ -223,6 +229,7 @@ __export(index_exports, {
223
229
  activateSessionWithHost: () => activateSessionWithHost,
224
230
  addressStorageKey: () => addressStorageKey,
225
231
  admitPolicyCredentialV3: () => admitPolicyCredentialV3,
232
+ admitPolicyCredentialV4: () => admitPolicyCredentialV4,
226
233
  applyPrefix: () => applyPrefix,
227
234
  bootstrapEncryptionNetworkId: () => import_bootstrap4.bootstrapEncryptionNetworkId,
228
235
  bootstrapSpaceId: () => import_bootstrap4.bootstrapSpaceId,
@@ -233,6 +240,7 @@ __export(index_exports, {
233
240
  buildDecryptInvocation: () => import_sdk_services8.buildDecryptInvocation,
234
241
  buildNetworkId: () => import_sdk_services8.buildNetworkId,
235
242
  buildPolicyCredentialPresentationV3: () => buildPolicyCredentialPresentationV3,
243
+ buildPolicyCredentialPresentationV4: () => buildPolicyCredentialPresentationV4,
236
244
  buildSpaceUri: () => buildSpaceUri,
237
245
  canonicalDigest: () => canonicalDigest,
238
246
  canonicalHashHex: () => import_sdk_services8.canonicalHashHex,
@@ -263,10 +271,12 @@ __export(index_exports, {
263
271
  createAndSignSignedObject: () => createAndSignSignedObject,
264
272
  createCapabilityKeyRegistry: () => createCapabilityKeyRegistry,
265
273
  createDelegatedShareKey: () => createDelegatedShareKey,
274
+ createEmailCredentialRequirement: () => createEmailCredentialRequirement,
266
275
  createHolderBinding: () => createHolderBinding,
267
276
  createInMemoryLocalNodeIdentityStore: () => createInMemoryLocalNodeIdentityStore,
268
277
  createLocalStorageLocalNodeIdentityStore: () => createLocalStorageLocalNodeIdentityStore,
269
278
  createOpenKeyCallbackSigningStrategy: () => createOpenKeyCallbackSigningStrategy,
279
+ createOpenKeyManageKeySigningStrategy: () => createOpenKeyManageKeySigningStrategy,
270
280
  createPolicyEnforcementDelegation: () => createPolicyEnforcementDelegation,
271
281
  createShareArtifact: () => createShareArtifact,
272
282
  createShareArtifactAsync: () => createShareArtifactAsync,
@@ -325,6 +335,7 @@ __export(index_exports, {
325
335
  generateRandomReceiverKey: () => import_sdk_services8.generateRandomReceiverKey,
326
336
  getPolicyRootStatusV3: () => getPolicyRootStatusV3,
327
337
  getTinyCloudDebugLogs: () => import_sdk_services8.getTinyCloudDebugLogs,
338
+ hasTinyCloudManageKeyScope: () => hasTinyCloudManageKeyScope,
328
339
  hexDecode: () => import_sdk_services8.hexDecode,
329
340
  hexEncode: () => import_sdk_services8.hexEncode,
330
341
  holderBindingCanonicalBytes: () => holderBindingCanonicalBytes,
@@ -339,6 +350,7 @@ __export(index_exports, {
339
350
  isNetworkId: () => import_sdk_services8.isNetworkId,
340
351
  isPlausibleOpenKeyActionId: () => isPlausibleOpenKeyActionId,
341
352
  isV2ShareLink: () => isV2ShareLink,
353
+ issuedCredentialEnvelopeFromVerified: () => issuedCredentialEnvelopeFromVerified,
342
354
  jcsCanonicalize: () => jcsCanonicalize,
343
355
  loadManifest: () => loadManifest,
344
356
  locationPayloadForRecord: () => locationPayloadForRecord,
@@ -357,6 +369,8 @@ __export(index_exports, {
357
369
  openWrappedKey: () => import_sdk_services8.openWrappedKey,
358
370
  parseCanonicalNetworkId: () => parseCanonicalNetworkId,
359
371
  parseCanonicalRecapResource: () => parseCanonicalRecapResource,
372
+ parseCanonicalTinyCloudIdentity: () => parseCanonicalTinyCloudIdentity,
373
+ parseCanonicalTinyCloudIdentityClaims: () => parseCanonicalTinyCloudIdentityClaims,
360
374
  parseCompactUcanAuthorization: () => parseCompactUcanAuthorization,
361
375
  parseExpiry: () => parseExpiry,
362
376
  parseNetworkId: () => import_sdk_services8.parseNetworkId,
@@ -379,6 +393,7 @@ __export(index_exports, {
379
393
  projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
380
394
  recapCaveatsEqual: () => recapCaveatsEqual,
381
395
  renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
396
+ requestTinyCloudManageKeyScope: () => requestTinyCloudManageKeyScope,
382
397
  resolveCloudLocation: () => resolveCloudLocation,
383
398
  resolveManifest: () => resolveManifest,
384
399
  resolveManifestKnowledgeRoot: () => resolveManifestKnowledgeRoot,
@@ -398,6 +413,7 @@ __export(index_exports, {
398
413
  signCompactUcanAuthorization: () => signCompactUcanAuthorization,
399
414
  signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
400
415
  signLocationRecord: () => signLocationRecord,
416
+ signPolicyCredentialPresentationV4: () => signPolicyCredentialPresentationV4,
401
417
  signShareEnvelope: () => signShareEnvelope,
402
418
  signedObjectIdFor: () => signedObjectIdFor,
403
419
  submitHostDelegation: () => submitHostDelegation,
@@ -1001,6 +1017,17 @@ function isDigest(value) {
1001
1017
 
1002
1018
  // src/credentials/requirement.ts
1003
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
+ }
1004
1031
  function validateCredentialRequirement(value) {
1005
1032
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
1006
1033
  const raw = value;
@@ -3865,6 +3892,7 @@ async function requestPolicyChallengeV3(input) {
3865
3892
  const response = await fetchFn(new URL("/share/v3/policy/challenges", input.nodeOrigin), {
3866
3893
  method: "POST",
3867
3894
  redirect: "error",
3895
+ signal: input.signal,
3868
3896
  headers: { accept: "application/json", "content-type": "application/json" },
3869
3897
  body: JSON.stringify({ policyCid: input.policyCid, recipientDid: input.recipientDid, requestedCapabilities: input.requestedCapabilities.map(normalizeUnifiedPolicyCapability) })
3870
3898
  });
@@ -4034,6 +4062,11 @@ var POLICY_SESSION_FACT_KEYS = [
4034
4062
  "issuanceAuditDigestHex",
4035
4063
  "remainingRedelegationDepth"
4036
4064
  ];
4065
+ var POLICY_SESSION_V4_AUDIT_FACT_KEYS = [
4066
+ "credentialIdAuditDigestHex",
4067
+ "presentationJtiAuditDigestHex"
4068
+ ];
4069
+ var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
4037
4070
  var KV_ACTIONS = /* @__PURE__ */ new Set([
4038
4071
  "tinycloud.kv/get",
4039
4072
  "tinycloud.kv/list",
@@ -4426,9 +4459,13 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
4426
4459
  if (expectedProofs !== void 0 && (parsed.payload.prf[0] !== expectedProofs[0] || parsed.payload.prf[1] !== expectedProofs[1]))
4427
4460
  throw new Error("policy session UCAN ordered proofs do not match");
4428
4461
  const factKeys = Object.keys(fact);
4429
- 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)
4430
4465
  throw new Error("policy session UCAN fact is incomplete");
4431
- 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)
4432
4469
  throw new Error("policy session UCAN fact is invalid");
4433
4470
  return {
4434
4471
  authorization,
@@ -4487,6 +4524,8 @@ var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
4487
4524
  var POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE = "TinyCloudPolicyCredentialRequirement";
4488
4525
  var POLICY_PRESENTATION_V3_SCHEMA = "xyz.tinycloud.policy/presentation/v3";
4489
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";
4490
4529
  function record(value, label) {
4491
4530
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4492
4531
  throw new Error(`${label} must be an object`);
@@ -4661,6 +4700,97 @@ function envelopeFromVerified(verified) {
4661
4700
  } = verified;
4662
4701
  return envelope;
4663
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
+ }
4664
4794
  async function buildPolicyCredentialPresentationV3(input) {
4665
4795
  const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4666
4796
  const requirement = validateCredentialRequirement(input.requirement);
@@ -4750,7 +4880,8 @@ async function admitPolicyCredentialV3(input) {
4750
4880
  policyCid: input.policyCid,
4751
4881
  recipientDid: input.credential.holderDid,
4752
4882
  requestedCapabilities: input.requestedCapabilities,
4753
- fetch: fetchFn
4883
+ fetch: fetchFn,
4884
+ signal: input.signal
4754
4885
  });
4755
4886
  const presentation = await buildPolicyCredentialPresentationV3({
4756
4887
  ...input,
@@ -4762,6 +4893,7 @@ async function admitPolicyCredentialV3(input) {
4762
4893
  {
4763
4894
  method: "POST",
4764
4895
  redirect: "error",
4896
+ signal: input.signal,
4765
4897
  headers: { accept: "application/json", "content-type": "application/json" },
4766
4898
  body: JSON.stringify({
4767
4899
  policyCid: input.policyCid,
@@ -4790,6 +4922,57 @@ async function admitPolicyCredentialV3(input) {
4790
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)) {
4791
4923
  throw new Error("policy delegation signed binding is invalid");
4792
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();
4793
4976
  return Object.freeze({ challenge, presentation, session });
4794
4977
  }
4795
4978
 
@@ -15367,6 +15550,236 @@ function createOpenKeyCallbackSigningStrategy(options2) {
15367
15550
  }
15368
15551
  var defaultSignStrategy = { type: "auto-sign" };
15369
15552
 
15553
+ // src/authorization/manage-key.ts
15554
+ var TINYCLOUD_MANAGE_KEY_SCOPE = "tinycloud:manage-key";
15555
+ var TINYCLOUD_CANONICAL_IDENTITY_CLAIM = "https://tinycloud.xyz/canonical_identity";
15556
+ function requestTinyCloudManageKeyScope(scopes = []) {
15557
+ const requested = typeof scopes === "string" ? scopes.split(/\s+/u) : scopes;
15558
+ return [
15559
+ ...new Set([...requested, TINYCLOUD_MANAGE_KEY_SCOPE].filter(Boolean))
15560
+ ].sort().join(" ");
15561
+ }
15562
+ function hasTinyCloudManageKeyScope(scopes) {
15563
+ const values = Array.isArray(scopes) ? scopes : typeof scopes === "string" ? scopes.split(/\s+/u) : [];
15564
+ return values.includes(TINYCLOUD_MANAGE_KEY_SCOPE);
15565
+ }
15566
+ var OpenKeyManageKeyError = class extends Error {
15567
+ constructor(code3, message, approvalUrl, retryable = false) {
15568
+ super(message);
15569
+ this.code = code3;
15570
+ this.approvalUrl = approvalUrl;
15571
+ this.retryable = retryable;
15572
+ this.name = "OpenKeyManageKeyError";
15573
+ }
15574
+ };
15575
+ function isRecord2(value) {
15576
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15577
+ }
15578
+ function rejected(message) {
15579
+ return new OpenKeyManageKeyError("MESSAGE_REJECTED", message);
15580
+ }
15581
+ function identityMismatch(message) {
15582
+ return new OpenKeyManageKeyError("IDENTITY_MISMATCH", message);
15583
+ }
15584
+ function parseCanonicalTinyCloudIdentity(value) {
15585
+ if (!isRecord2(value))
15586
+ throw rejected("Canonical TinyCloud identity is missing");
15587
+ const expectedKeys = [
15588
+ "address",
15589
+ "chainId",
15590
+ "did",
15591
+ "keyId",
15592
+ "spaceId",
15593
+ "version"
15594
+ ];
15595
+ const actualKeys = Object.keys(value).sort();
15596
+ if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
15597
+ throw rejected("Canonical TinyCloud identity has an invalid shape");
15598
+ }
15599
+ if (value.version !== "v1")
15600
+ throw rejected("Canonical TinyCloud identity version is unsupported");
15601
+ if (typeof value.keyId !== "string" || value.keyId.trim() === "") {
15602
+ throw rejected("Canonical TinyCloud identity key ID is invalid");
15603
+ }
15604
+ if (typeof value.address !== "string") {
15605
+ throw rejected("Canonical TinyCloud identity address is invalid");
15606
+ }
15607
+ let address;
15608
+ try {
15609
+ address = canonicalizeAddress(value.address);
15610
+ } catch {
15611
+ throw rejected("Canonical TinyCloud identity address is invalid");
15612
+ }
15613
+ if (address !== value.address) {
15614
+ throw rejected(
15615
+ "Canonical TinyCloud identity address must use EIP-55 checksum casing"
15616
+ );
15617
+ }
15618
+ if (typeof value.chainId !== "number" || !Number.isSafeInteger(value.chainId) || value.chainId <= 0) {
15619
+ throw rejected("Canonical TinyCloud identity chain ID is invalid");
15620
+ }
15621
+ const did = pkhDid(address, value.chainId);
15622
+ const spacePrefix = `tinycloud:pkh:eip155:${value.chainId}:${address}:`;
15623
+ if (typeof value.spaceId !== "string" || !value.spaceId.startsWith(spacePrefix)) {
15624
+ throw rejected("Canonical TinyCloud identity space is invalid");
15625
+ }
15626
+ const spaceName = value.spaceId.slice(spacePrefix.length);
15627
+ let spaceId;
15628
+ try {
15629
+ spaceId = makePkhSpaceId(address, value.chainId, spaceName);
15630
+ } catch {
15631
+ throw rejected("Canonical TinyCloud identity space is invalid");
15632
+ }
15633
+ if (value.did !== did || value.spaceId !== spaceId) {
15634
+ throw rejected("Canonical TinyCloud identity does not match its address");
15635
+ }
15636
+ return {
15637
+ version: "v1",
15638
+ keyId: value.keyId,
15639
+ address,
15640
+ chainId: value.chainId,
15641
+ did,
15642
+ spaceId
15643
+ };
15644
+ }
15645
+ function parseCanonicalTinyCloudIdentityClaims(claims) {
15646
+ if (!isRecord2(claims)) throw rejected("OAuth claims are missing");
15647
+ return parseCanonicalTinyCloudIdentity(
15648
+ claims[TINYCLOUD_CANONICAL_IDENTITY_CLAIM]
15649
+ );
15650
+ }
15651
+ function identitiesEqual(left, right) {
15652
+ return left.version === right.version && left.keyId === right.keyId && left.address === right.address && left.chainId === right.chainId && left.did === right.did && left.spaceId === right.spaceId;
15653
+ }
15654
+ function errorForResponse(body, status) {
15655
+ const message = body?.reason ?? body?.error ?? `OpenKey manage-key signing failed with HTTP ${status}`;
15656
+ switch (body?.code ?? body?.error) {
15657
+ case "missing_scope":
15658
+ case "consent_required":
15659
+ return new OpenKeyManageKeyError("CONSENT_REQUIRED", message, body?.approvalUrl);
15660
+ case "client_disabled":
15661
+ case "signing_disabled":
15662
+ case "grant_disabled":
15663
+ return new OpenKeyManageKeyError("GRANT_DISABLED", message, body?.approvalUrl);
15664
+ case "user_exclusive":
15665
+ return new OpenKeyManageKeyError("USER_EXCLUSIVE", message, body?.approvalUrl);
15666
+ case "token_expired":
15667
+ case "token_too_old":
15668
+ case "invalid_token":
15669
+ case "expired_token":
15670
+ return new OpenKeyManageKeyError("TOKEN_EXPIRED", message, body?.approvalUrl);
15671
+ case "canonical_key_unavailable":
15672
+ case "signer_failed":
15673
+ return new OpenKeyManageKeyError(
15674
+ "SIGNER_UNAVAILABLE",
15675
+ "The canonical key signer is temporarily unavailable",
15676
+ void 0,
15677
+ true
15678
+ );
15679
+ default:
15680
+ return new OpenKeyManageKeyError(
15681
+ status === 401 ? "TOKEN_EXPIRED" : "MESSAGE_REJECTED",
15682
+ message,
15683
+ body?.approvalUrl
15684
+ );
15685
+ }
15686
+ }
15687
+ async function resolveToken(token) {
15688
+ const value = typeof token === "function" ? await token() : token;
15689
+ if (typeof value !== "string" || value.trim() === "") {
15690
+ throw new OpenKeyManageKeyError(
15691
+ "CONSENT_REQUIRED",
15692
+ "A manage-key OAuth bearer token is required"
15693
+ );
15694
+ }
15695
+ return value;
15696
+ }
15697
+ function normalizeEndpoint(value) {
15698
+ let endpoint;
15699
+ try {
15700
+ endpoint = new URL(value);
15701
+ } catch {
15702
+ throw rejected("OpenKey manage-key signing endpoint must be an absolute URL");
15703
+ }
15704
+ const rawHttpAuthority = value.match(/^http:\/\/([^/?#]*)/iu)?.[1];
15705
+ const loopback = rawHttpAuthority !== void 0 && /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/u.test(rawHttpAuthority);
15706
+ if (endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && loopback) || endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "") {
15707
+ throw rejected("OpenKey manage-key signing endpoint must use HTTPS or exact loopback HTTP");
15708
+ }
15709
+ return endpoint.href;
15710
+ }
15711
+ function assertSignInRequest(request, identity3) {
15712
+ if (request.type !== "siwe" || request.purpose !== "sign-in" || request.chainId !== identity3.chainId || request.address !== identity3.address || typeof request.message !== "string" || request.message.length === 0) {
15713
+ throw rejected(
15714
+ "Manage-key signing only permits the canonical sign-in SIWE request"
15715
+ );
15716
+ }
15717
+ }
15718
+ function createOpenKeyManageKeySigningStrategy(options2) {
15719
+ const identity3 = parseCanonicalTinyCloudIdentity(options2.identity);
15720
+ const endpoint = normalizeEndpoint(options2.endpoint);
15721
+ return {
15722
+ type: "callback",
15723
+ openKeyAutoSign: true,
15724
+ handler: async (request) => {
15725
+ assertSignInRequest(request, identity3);
15726
+ const fetchImpl = options2.fetch ?? globalThis.fetch;
15727
+ if (!fetchImpl)
15728
+ throw rejected("OpenKey manage-key signing requires fetch");
15729
+ if (!hasTinyCloudManageKeyScope(options2.scopes)) {
15730
+ throw new OpenKeyManageKeyError(
15731
+ "CONSENT_REQUIRED",
15732
+ "The OAuth bearer token was not granted tinycloud:manage-key"
15733
+ );
15734
+ }
15735
+ const token = await resolveToken(options2.token);
15736
+ const response = await fetchImpl(endpoint, {
15737
+ method: "POST",
15738
+ credentials: "omit",
15739
+ headers: {
15740
+ authorization: `Bearer ${token}`,
15741
+ "content-type": "application/json"
15742
+ },
15743
+ body: JSON.stringify({
15744
+ address: request.address,
15745
+ chainId: request.chainId,
15746
+ message: request.message,
15747
+ type: "siwe"
15748
+ })
15749
+ });
15750
+ let body;
15751
+ try {
15752
+ const value = await response.json();
15753
+ body = isRecord2(value) ? value : void 0;
15754
+ } catch {
15755
+ body = void 0;
15756
+ }
15757
+ if (!response.ok || body?.approved !== true)
15758
+ throw errorForResponse(body, response.status);
15759
+ if (typeof body.signature !== "string")
15760
+ throw rejected("OpenKey manage-key signer did not return a signature");
15761
+ const responseIdentity = parseCanonicalTinyCloudIdentity(
15762
+ body.canonicalIdentity
15763
+ );
15764
+ if (!identitiesEqual(identity3, responseIdentity)) {
15765
+ throw identityMismatch(
15766
+ "OpenKey manage-key signer returned a different canonical identity"
15767
+ );
15768
+ }
15769
+ if (!await verifyEip191MessageSignature(
15770
+ request.message,
15771
+ body.signature,
15772
+ identity3.address
15773
+ )) {
15774
+ throw rejected(
15775
+ "OpenKey manage-key signer returned an invalid signature"
15776
+ );
15777
+ }
15778
+ return { approved: true, signature: body.signature };
15779
+ }
15780
+ };
15781
+ }
15782
+
15370
15783
  // src/authorization/spaceCreation.ts
15371
15784
  var AutoApproveSpaceCreationHandler = class {
15372
15785
  /**