@tinycloud/node-sdk 3.8.0-beta.2 → 3.8.0-beta.21

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.
Files changed (44) hide show
  1. package/README.md +6 -0
  2. package/dist/{TinyCloudNode-B6VOBrCb.d.ts → TinyCloudNode-DaD5vJhL.d.cts} +49 -6
  3. package/dist/{TinyCloudNode-CVO41O12.d.cts → TinyCloudNode-k3TbBwwj.d.ts} +49 -6
  4. package/dist/{WasmKeyProvider-5Ee7fcAw.d.cts → WasmKeyProvider-BKCPRC-c.d.cts} +21 -1
  5. package/dist/{WasmKeyProvider-5Ee7fcAw.d.ts → WasmKeyProvider-BKCPRC-c.d.ts} +21 -1
  6. package/dist/{authority-DGi1hIMK.d.ts → authority-C_fWaxAu.d.ts} +1 -1
  7. package/dist/{authority-DgVV80bI.d.cts → authority-DJdbZM6k.d.cts} +1 -1
  8. package/dist/core.cjs +375 -40
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/core.d.cts +5 -4
  11. package/dist/core.d.ts +5 -4
  12. package/dist/core.js +370 -35
  13. package/dist/core.js.map +1 -1
  14. package/dist/credentials-core.d.cts +1 -1
  15. package/dist/credentials-core.d.ts +1 -1
  16. package/dist/credentials.d.cts +1 -1
  17. package/dist/credentials.d.ts +1 -1
  18. package/dist/{delegation-0LKZkmP2.d.cts → delegation-Ckc3UETl.d.cts} +1 -1
  19. package/dist/{delegation-0LKZkmP2.d.ts → delegation-Ckc3UETl.d.ts} +1 -1
  20. package/dist/encryption-keys.d.cts +2 -2
  21. package/dist/encryption-keys.d.ts +2 -2
  22. package/dist/index.cjs +343 -51
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +4 -3
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.js +333 -38
  27. package/dist/index.js.map +1 -1
  28. package/dist/replication/authority.cjs +3 -0
  29. package/dist/replication/authority.cjs.map +1 -1
  30. package/dist/replication/authority.d.cts +4 -3
  31. package/dist/replication/authority.d.ts +4 -3
  32. package/dist/replication/authority.js +3 -0
  33. package/dist/replication/authority.js.map +1 -1
  34. package/dist/replication/runtime.cjs +1 -0
  35. package/dist/replication/runtime.cjs.map +1 -1
  36. package/dist/replication/runtime.js +1 -0
  37. package/dist/replication/runtime.js.map +1 -1
  38. package/dist/share-receive.cjs +775 -0
  39. package/dist/share-receive.cjs.map +1 -0
  40. package/dist/share-receive.d.cts +32 -0
  41. package/dist/share-receive.d.ts +32 -0
  42. package/dist/share-receive.js +777 -0
  43. package/dist/share-receive.js.map +1 -0
  44. package/package.json +11 -4
package/dist/core.js CHANGED
@@ -242,10 +242,10 @@ import {
242
242
  activateSessionWithHost,
243
243
  checkNodeInfo,
244
244
  AutoApproveSpaceCreationHandler,
245
+ ManifestValidationError,
245
246
  DEFAULT_MANIFEST_SPACE,
246
247
  ENCRYPTION_PERMISSION_SERVICE,
247
248
  composeManifestRequest,
248
- parseNetworkId,
249
249
  principalDidEquals,
250
250
  resourceCapabilitiesToAbilitiesMap,
251
251
  resourceCapabilitiesToSpaceAbilitiesMap,
@@ -273,6 +273,44 @@ import {
273
273
  HOOKS,
274
274
  ENCRYPTION
275
275
  } from "@tinycloud/sdk-core";
276
+ import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
277
+ import { parseNetworkId } from "@tinycloud/sdk-services";
278
+
279
+ // src/authorization/appKeyHostBinding.ts
280
+ var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
281
+ var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
282
+ var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
283
+ function appKeyHostBinding(domain, appId) {
284
+ if (!APP_ID.test(appId)) return false;
285
+ if (domain.endsWith(":")) return false;
286
+ let url;
287
+ try {
288
+ url = new URL(`https://${domain}`);
289
+ } catch {
290
+ return false;
291
+ }
292
+ if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
293
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
294
+ const host = url.hostname;
295
+ if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
296
+ const labels = host.split(".");
297
+ if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
298
+ if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
299
+ const reversed = labels.reverse().join(".");
300
+ return appId === reversed || appId.startsWith(`${reversed}.`);
301
+ }
302
+ function isAppKeyHostBound(keysAppId, origin) {
303
+ if (typeof origin === "string" && !origin.includes("://")) {
304
+ return appKeyHostBinding(origin, keysAppId);
305
+ }
306
+ let url;
307
+ try {
308
+ url = typeof origin === "string" ? new URL(origin) : origin;
309
+ } catch {
310
+ return false;
311
+ }
312
+ return appKeyHostBinding(url.host, keysAppId);
313
+ }
276
314
 
277
315
  // src/replication/authority-sign-in.ts
278
316
  import {
@@ -410,6 +448,7 @@ function cloneAbilitiesMap(actions) {
410
448
  var NodeUserAuthorization = class {
411
449
  constructor(config) {
412
450
  this.activeSessionKeyId = "default";
451
+ this.preparedSessionCounter = 0;
413
452
  /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
414
453
  this.sessionGeneration = 0;
415
454
  /**
@@ -766,6 +805,23 @@ var NodeUserAuthorization = class {
766
805
  );
767
806
  return this._capabilityRequest;
768
807
  }
808
+ async refreshNamedKeyFeatureForSignIn(address, chainId) {
809
+ const request = this.getCapabilityRequest();
810
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
811
+ const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
812
+ if (hasNamedKeys) {
813
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
814
+ await this.refreshNodeInfo();
815
+ }
816
+ }
817
+ /** Manifest key paths that this sign-in cannot grant. */
818
+ get unavailableKeyPaths() {
819
+ const request = this.getCapabilityRequest();
820
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
821
+ return (request?.manifests ?? []).flatMap(
822
+ (manifest) => this._nodeFeatures.includes("encryption-named-keys-v1") && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id) ? [] : (manifest.keys ?? []).map((entry) => "path" in entry ? entry.path : entry.prefix)
823
+ );
824
+ }
769
825
  resolveSpaceName(space, address, chainId) {
770
826
  if (space.startsWith("tinycloud:")) {
771
827
  return space;
@@ -957,15 +1013,32 @@ var NodeUserAuthorization = class {
957
1013
  }
958
1014
  const rawAbilities = {};
959
1015
  const currentDid = pkhDid(address, chainId);
1016
+ if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
1017
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
1018
+ for (const manifest of request.manifests) {
1019
+ if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
1020
+ for (const declaration of manifest.keys ?? []) {
1021
+ const path = "path" in declaration ? declaration.path : declaration.prefix;
1022
+ const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
1023
+ for (const ability of declaration.abilities) {
1024
+ addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
1025
+ }
1026
+ }
1027
+ }
1028
+ }
960
1029
  const spaceResources = request.resources.filter((entry) => {
961
1030
  if (entry.service !== ENCRYPTION_PERMISSION_SERVICE) {
962
1031
  return true;
963
1032
  }
1033
+ const parsed = entry.path.includes("/") ? parseEncryptionResource(entry.path) : { ...parseNetworkId(entry.path), kind: "network" };
1034
+ const sharePrefix = parsed.kind === "prefix" && parsed.network === "default" && parsed.prefix === "share" && entry.actions.length === 1 && entry.actions[0] === DECRYPT_ACTION && didPrincipalMatches(parsed.ownerDid, currentDid);
1035
+ if (parsed.kind !== "network" && !sharePrefix) {
1036
+ throw new ManifestValidationError("named keys go in manifest.keys");
1037
+ }
964
1038
  for (const action of entry.actions) {
965
1039
  addRawAbility(rawAbilities, entry.path, action);
966
1040
  }
967
- if (entry.actions.includes(DECRYPT_ACTION)) {
968
- const parsed = parseNetworkId(entry.path);
1041
+ if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
969
1042
  if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
970
1043
  addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
971
1044
  }
@@ -1002,8 +1075,7 @@ var NodeUserAuthorization = class {
1002
1075
  /**
1003
1076
  * Build SIWE overrides from the top-level nonce and siweConfig.
1004
1077
  * - Top-level `nonce` is seeded first so `siweConfig.nonce` wins if both are set.
1005
- * - statement is prepended to the default statement
1006
- * - resources are appended to the default resources
1078
+ * - resources are inserted after WASM preparation, before the final ReCap
1007
1079
  * - uri triggers a warning (overwriting delegation target)
1008
1080
  * - all other fields override directly
1009
1081
  * - per-call nonce overrides siweConfig.nonce when provided
@@ -1021,9 +1093,6 @@ var NodeUserAuthorization = class {
1021
1093
  if (statement) {
1022
1094
  overrides.statement = this.statement ? `${statement} ${this.statement}` : statement;
1023
1095
  }
1024
- if (resources && resources.length > 0) {
1025
- overrides.resources = resources;
1026
- }
1027
1096
  if (uri) {
1028
1097
  console.warn(
1029
1098
  "[tinycloud] siweConfig.uri is overwriting the delegation target URI. This may break delegation chain validation if the URI does not match the session key DID."
@@ -1035,6 +1104,29 @@ var NodeUserAuthorization = class {
1035
1104
  }
1036
1105
  return overrides;
1037
1106
  }
1107
+ prepareSessionWithResources(params) {
1108
+ const resources = this.siweConfig?.resources;
1109
+ if (resources?.some((resource) => typeof resource !== "string" || !/^urn:tinycloud:recipients:[A-Za-z0-9_-]+$/.test(resource))) {
1110
+ throw new Error("SIWE metadata resources must be base64url recipients URIs");
1111
+ }
1112
+ const prepared = this.wasm.prepareSession(params);
1113
+ if (!resources?.length) return prepared;
1114
+ const recapLines = /^- urn:recap:[^\r\n]+$/gim;
1115
+ const recaps = prepared.siwe.match(recapLines);
1116
+ const recap = recaps?.[0];
1117
+ if (recaps?.length !== 1 || !prepared.siwe.endsWith(recap)) {
1118
+ throw new Error("Prepared SIWE must end with exactly one ReCap");
1119
+ }
1120
+ const insertedLines = resources.map((resource) => `- ${resource}`);
1121
+ const siwe = prepared.siwe.slice(0, -recap.length) + insertedLines.join("\n") + "\n" + recap;
1122
+ const expectedFields = extractImmutableSiweFields(prepared.siwe);
1123
+ expectedFields.nonRecapResources = [expectedFields.nonRecapResources, ...insertedLines].filter(Boolean).join("\n");
1124
+ const actualRecaps = siwe.match(recapLines);
1125
+ if (actualRecaps?.length !== 1 || actualRecaps[0] !== recap || diffImmutableSiweFields(expectedFields, extractImmutableSiweFields(siwe)).length) {
1126
+ throw new Error("SIWE metadata insertion changed the prepared message");
1127
+ }
1128
+ return { ...prepared, siwe };
1129
+ }
1038
1130
  /**
1039
1131
  * Add an extension to the authorization flow.
1040
1132
  */
@@ -1282,7 +1374,7 @@ var NodeUserAuthorization = class {
1282
1374
  spaceAbilities[spaceId] = resourceCapabilitiesToAbilitiesMap(resources);
1283
1375
  }
1284
1376
  const abilities = spaceAbilities[options.spaceId] ?? resourceCapabilitiesToAbilitiesMap([]);
1285
- const prepared = this.wasm.prepareSession({
1377
+ const prepared = this.prepareSessionWithResources({
1286
1378
  abilities,
1287
1379
  spaceAbilities,
1288
1380
  ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
@@ -1361,8 +1453,10 @@ var NodeUserAuthorization = class {
1361
1453
  this._chainId = chainId;
1362
1454
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
1363
1455
  this.assertSignInCurrent(generation);
1364
- const keyId = `session-${Date.now()}`;
1365
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
1456
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
1457
+ this.assertSignInCurrent(generation);
1458
+ const keyId = `session-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1459
+ this.sessionManager.createSessionKey(keyId);
1366
1460
  this.activeSessionKeyId = keyId;
1367
1461
  const jwkString = this.sessionManager.jwk(keyId);
1368
1462
  if (!jwkString) {
@@ -1373,7 +1467,7 @@ var NodeUserAuthorization = class {
1373
1467
  const spaceId = capabilityPlan.spaceId;
1374
1468
  const now = /* @__PURE__ */ new Date();
1375
1469
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
1376
- const prepared = this.wasm.prepareSession({
1470
+ const prepared = this.prepareSessionWithResources({
1377
1471
  abilities: capabilityPlan.abilities,
1378
1472
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
1379
1473
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -1507,9 +1601,9 @@ var NodeUserAuthorization = class {
1507
1601
  refuseCaveated(this.getCapabilityRequest()?.manifests);
1508
1602
  const address = canonicalizeAddress(await this.signer.getAddress());
1509
1603
  const chainId = await this.signer.getChainId();
1510
- const keyId = `session-${Date.now()}`;
1511
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
1512
- this.activeSessionKeyId = keyId;
1604
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
1605
+ const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
1606
+ this.sessionManager.createSessionKey(keyId);
1513
1607
  const jwkString = this.sessionManager.jwk(keyId);
1514
1608
  if (!jwkString) {
1515
1609
  throw new Error("Failed to create session key");
@@ -1519,7 +1613,7 @@ var NodeUserAuthorization = class {
1519
1613
  const spaceId = capabilityPlan.spaceId;
1520
1614
  const now = /* @__PURE__ */ new Date();
1521
1615
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
1522
- const prepared = this.wasm.prepareSession({
1616
+ const prepared = this.prepareSessionWithResources({
1523
1617
  abilities: capabilityPlan.abilities,
1524
1618
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
1525
1619
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -1590,6 +1684,7 @@ var NodeUserAuthorization = class {
1590
1684
  await this.sessionStorage.save(address, persistedData);
1591
1685
  this._session = clientSession;
1592
1686
  this._tinyCloudSession = tinyCloudSession;
1687
+ this.activeSessionKeyId = keyId;
1593
1688
  this._address = address;
1594
1689
  this._chainId = chainId;
1595
1690
  await this.refreshNodeInfo();
@@ -1636,6 +1731,11 @@ var NodeUserAuthorization = class {
1636
1731
  * @param options Optional overrides for the OpenKey request.
1637
1732
  */
1638
1733
  async signInWithOpenKey(authorizeFn, options) {
1734
+ const pending = await this.prepareOpenKeySignIn(options);
1735
+ return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
1736
+ }
1737
+ /** Finish address, host and SIWE preparation before an approval click. */
1738
+ async prepareOpenKeySignIn(options) {
1639
1739
  refuseCaveated(this.getCapabilityRequest()?.manifests);
1640
1740
  if (options?.host !== void 0) {
1641
1741
  const overrideHost = options.host.trim();
@@ -1647,7 +1747,7 @@ var NodeUserAuthorization = class {
1647
1747
  const signingAddress = canonicalizeAddress(await this.signer.getAddress());
1648
1748
  const signingChainId = await this.signer.getChainId();
1649
1749
  await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
1650
- const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
1750
+ const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
1651
1751
  const presentation = this.buildPresentationEnvelope(options?.reason);
1652
1752
  const hostHint = this.primaryTinyCloudHost;
1653
1753
  const originalRequest = {
@@ -1659,12 +1759,32 @@ var NodeUserAuthorization = class {
1659
1759
  keyId: options?.openkeyKeyId,
1660
1760
  presentation
1661
1761
  };
1762
+ return {
1763
+ request: originalRequest,
1764
+ prepared,
1765
+ keyId,
1766
+ jwk,
1767
+ address,
1768
+ chainId,
1769
+ generation: this.sessionGeneration,
1770
+ createdAt: Date.now()
1771
+ };
1772
+ }
1773
+ /** Complete a prepared approval. The caller starts authorization before awaiting this method. */
1774
+ async completeOpenKeySignIn(pending, authorization, options) {
1775
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
1776
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
1777
+ }
1778
+ if (pending.generation !== this.sessionGeneration) {
1779
+ throw new Error("Prepared OpenKey sign-in expired after sign-out");
1780
+ }
1781
+ const { request: originalRequest, prepared, keyId, jwk } = pending;
1662
1782
  let result;
1663
1783
  let signed;
1664
1784
  let signature;
1665
1785
  for (let attempt = 0; ; ) {
1666
1786
  try {
1667
- result ?? (result = await authorizeFn(originalRequest));
1787
+ result ?? (result = await authorization);
1668
1788
  signed = await this.verifyOpenKeyResult(result, prepared);
1669
1789
  signature = result.signature;
1670
1790
  break;
@@ -2085,6 +2205,50 @@ var NodeUserAuthorization = class {
2085
2205
  }
2086
2206
  };
2087
2207
 
2208
+ // src/authorization/openKeyBridge.ts
2209
+ function wireOpenKeyAuthorize(openkey, opts) {
2210
+ return async (input) => {
2211
+ const result = await openkey.authorizeTinyCloud({
2212
+ protocolVersion: 1,
2213
+ siwe: input.siwe,
2214
+ keyId: input.keyId,
2215
+ jwk: input.jwk,
2216
+ host: input.host,
2217
+ // Forward the presentation envelope verbatim — the OpenKey SDK
2218
+ // validates and size-bounds it before use.
2219
+ presentation: input.presentation
2220
+ }, opts);
2221
+ if (result.protocolVersion !== 1) {
2222
+ throw new Error(
2223
+ `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
2224
+ );
2225
+ }
2226
+ if (typeof result.signedMessage !== "string" || !result.signedMessage) {
2227
+ throw new Error(
2228
+ "wireOpenKeyAuthorize: OpenKey returned no signedMessage \u2014 cannot complete a TinyCloud session"
2229
+ );
2230
+ }
2231
+ if (!Array.isArray(result.selectedActionKeys)) {
2232
+ throw new Error(
2233
+ "wireOpenKeyAuthorize: OpenKey returned no selectedActionKeys"
2234
+ );
2235
+ }
2236
+ if (!Array.isArray(result.permissions)) {
2237
+ throw new Error(
2238
+ "wireOpenKeyAuthorize: OpenKey returned no permissions"
2239
+ );
2240
+ }
2241
+ return {
2242
+ protocolVersion: 1,
2243
+ address: result.address,
2244
+ signature: result.signature,
2245
+ signedMessage: result.signedMessage,
2246
+ selectedActionKeys: result.selectedActionKeys,
2247
+ permissions: result.permissions
2248
+ };
2249
+ };
2250
+ }
2251
+
2088
2252
  // src/recordingServiceContext.ts
2089
2253
  import {
2090
2254
  ServiceContext
@@ -2736,6 +2900,7 @@ function deserializeDelegation(data) {
2736
2900
 
2737
2901
  // src/TinyCloudNode.ts
2738
2902
  import { generateNonce, SiweMessage as SiweMessage3 } from "siwe";
2903
+ import { didKeyFromEd25519PublicKey } from "@tinycloud/share-envelope";
2739
2904
  import {
2740
2905
  TinyCloud,
2741
2906
  checkNodeInfo as checkNodeInfo2,
@@ -2770,10 +2935,13 @@ import {
2770
2935
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
2771
2936
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
2772
2937
  PermissionNotInManifestError,
2938
+ ManifestValidationError as ManifestValidationError2,
2773
2939
  SessionExpiredError,
2774
2940
  canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
2775
2941
  recapCaveatsEqual as recapCaveatsEqual2,
2776
2942
  expandPermissionEntries as expandPermissionEntriesCore,
2943
+ expandRuntimePermissionEntries,
2944
+ expandDelegationPermissionEntries,
2777
2945
  isCapabilitySubset as isCapabilitySubset2,
2778
2946
  isExplicitOnlyAction,
2779
2947
  parseRecapCapabilities,
@@ -2804,6 +2972,7 @@ import {
2804
2972
  withoutShareLink,
2805
2973
  verifyEip191MessageSignature,
2806
2974
  signCompactUcanRootAuthorization,
2975
+ signCompactUcanAuthorization,
2807
2976
  markDelegationTerminal,
2808
2977
  isStorageFullError,
2809
2978
  STORAGE_FULL_MESSAGE,
@@ -2819,6 +2988,7 @@ import {
2819
2988
  tinyCloudDebugLogger
2820
2989
  } from "@tinycloud/sdk-services";
2821
2990
  import { RequestLog, observeReplicaReads, readOnlyRequestLog } from "@tinycloud/sdk-services/telemetry/requestLog";
2991
+ import { parseEncryptionResource as parseEncryptionResource2 } from "@tinycloud/sdk-services/encryption/resource";
2822
2992
 
2823
2993
  // src/account/AccountService.ts
2824
2994
  import { AccountService } from "@tinycloud/sdk-core";
@@ -3310,6 +3480,7 @@ var ROOT_DELEGATION_ACTIONS = [
3310
3480
  DUCKDB2.ALL
3311
3481
  ];
3312
3482
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
3483
+ var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
3313
3484
  function unifiedRootAttenuation(capabilities) {
3314
3485
  const attenuation = {};
3315
3486
  for (const capability of capabilities) {
@@ -3807,6 +3978,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3807
3978
  constructor(config = {}) {
3808
3979
  this.signer = null;
3809
3980
  this.auth = null;
3981
+ this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
3810
3982
  this.tc = null;
3811
3983
  this._chainId = 1;
3812
3984
  this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
@@ -3841,6 +4013,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3841
4013
  return void 0;
3842
4014
  }
3843
4015
  };
4016
+ this._keysUnavailable = [];
3844
4017
  this._baseVaults = /* @__PURE__ */ new Map();
3845
4018
  this._baseSecrets = /* @__PURE__ */ new Map();
3846
4019
  this._secrets = /* @__PURE__ */ new Map();
@@ -4668,6 +4841,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4668
4841
  this._hooks = void 0;
4669
4842
  this._vault = void 0;
4670
4843
  this._encryption = void 0;
4844
+ this._namedKeyClient = void 0;
4845
+ this._keysUnavailable = [];
4671
4846
  this._baseVaults.clear();
4672
4847
  this._baseSecrets.clear();
4673
4848
  this._secrets.clear();
@@ -4744,6 +4919,37 @@ var _TinyCloudNode = class _TinyCloudNode {
4744
4919
  }
4745
4920
  await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
4746
4921
  }
4922
+ /** Prepare the OpenKey request before the approval click. */
4923
+ async prepareOpenKeySignIn() {
4924
+ if (!this.signer || !this.tc || !this.auth) {
4925
+ throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
4926
+ }
4927
+ await this.signOutInFlight;
4928
+ await this.accountRegistryTail;
4929
+ await this.wasmBindings.ensureInitialized?.();
4930
+ return this.auth.prepareOpenKeySignIn();
4931
+ }
4932
+ /** Start OpenKey authorization synchronously, then complete normal session setup. */
4933
+ signInWithPreparedOpenKey(pending, authorizeFn) {
4934
+ if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
4935
+ throw new Error("Prepared OpenKey sign-in is unavailable");
4936
+ }
4937
+ if (this.consumedOpenKeyPendings.has(pending)) {
4938
+ throw new Error("Prepared OpenKey sign-in was already used");
4939
+ }
4940
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
4941
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
4942
+ }
4943
+ this.consumedOpenKeyPendings.add(pending);
4944
+ const authorization = authorizeFn(pending.request);
4945
+ void authorization.catch(() => void 0);
4946
+ return this.signInWith(() => {
4947
+ if (this._address !== pending.address || this._chainId !== pending.chainId) {
4948
+ throw new Error("OpenKey account changed after sign-in preparation");
4949
+ }
4950
+ return this.auth.completeOpenKeySignIn(pending, authorization);
4951
+ });
4952
+ }
4747
4953
  async signInWith(performSignIn) {
4748
4954
  refuseCaveated(this.auth?.capabilityRequest?.manifests);
4749
4955
  const epoch = this.sessionEpoch;
@@ -4790,6 +4996,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4790
4996
  this._hooks = void 0;
4791
4997
  this._vault = void 0;
4792
4998
  this._encryption = void 0;
4999
+ this._namedKeyClient = void 0;
5000
+ this._keysUnavailable = [];
4793
5001
  this._baseVaults.clear();
4794
5002
  this._baseSecrets.clear();
4795
5003
  this._secrets.clear();
@@ -4842,6 +5050,26 @@ var _TinyCloudNode = class _TinyCloudNode {
4842
5050
  this.assertSessionEpoch(epoch, "Sign-in");
4843
5051
  await this.ensureRequestedEncryptionNetworks();
4844
5052
  this.assertSessionEpoch(epoch, "Sign-in");
5053
+ this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
5054
+ if (this._keysUnavailable.length > 0) {
5055
+ this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
5056
+ }
5057
+ if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
5058
+ for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
5059
+ for (const declaration of manifest.keys ?? []) {
5060
+ if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
5061
+ try {
5062
+ await this.keys.ensureKey(declaration.path);
5063
+ } catch (error) {
5064
+ this._keysUnavailable.push(declaration.path);
5065
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
5066
+ this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
5067
+ }
5068
+ this.assertSessionEpoch(epoch, "Sign-in");
5069
+ }
5070
+ }
5071
+ }
5072
+ }
4845
5073
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
4846
5074
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
4847
5075
  }
@@ -5629,7 +5857,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5629
5857
  const networkIds = /* @__PURE__ */ new Set();
5630
5858
  for (const resource of request.resources) {
5631
5859
  if (resource.service === ENCRYPTION_PERMISSION_SERVICE2 && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
5632
- networkIds.add(resource.path);
5860
+ const parsed = resource.path.includes("/") ? parseEncryptionResource2(resource.path) : { ...parseNetworkId2(resource.path), kind: "network" };
5861
+ if (parsed.kind === "network") networkIds.add(resource.path);
5633
5862
  }
5634
5863
  }
5635
5864
  return [...networkIds];
@@ -6179,6 +6408,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6179
6408
  this._hooks = stagedGraph.hooks;
6180
6409
  this._vault = stagedGraph.vault;
6181
6410
  this._encryption = stagedGraph.encryption;
6411
+ this._namedKeyClient = void 0;
6412
+ this._keysUnavailable = [];
6182
6413
  this._capabilityRegistry = stagedGraph.capabilityRegistry;
6183
6414
  this._keyProvider = stagedGraph.keyProvider;
6184
6415
  this._sharingService = stagedGraph.sharingService;
@@ -6360,6 +6591,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6360
6591
  invoke: graph.invoke,
6361
6592
  invokeAny: graph.invokeAny,
6362
6593
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
6594
+ sessionInvocationSigner: this.sessionInvocationSignerFor(input.serviceSession),
6363
6595
  fetch: graph.fetch
6364
6596
  });
6365
6597
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -6575,6 +6807,31 @@ var _TinyCloudNode = class _TinyCloudNode {
6575
6807
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
6576
6808
  return signBytesWithJwk(bytes, session.jwk);
6577
6809
  }
6810
+ /**
6811
+ * Signs proofless invocations with `session`'s own did:key, for the node's
6812
+ * issued-delegation list and revocation by the delegator. The audience is
6813
+ * this Node's DID; each invocation lives 60 seconds.
6814
+ */
6815
+ sessionInvocationSignerFor(session) {
6816
+ return {
6817
+ did: () => session.verificationMethod.split("#", 1)[0],
6818
+ sign: async (attenuation) => {
6819
+ const now = Math.floor(Date.now() / 1e3);
6820
+ const invocation = await signCompactUcanAuthorization({
6821
+ issuerDid: session.verificationMethod.split("#", 1)[0],
6822
+ audienceDid: await this.fetchNodeId(),
6823
+ attenuation,
6824
+ facts: [{}],
6825
+ proofs: [],
6826
+ notBefore: now,
6827
+ expiresAt: now + 60,
6828
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
6829
+ sign: (bytes) => signBytesWithJwk(bytes, session.jwk)
6830
+ });
6831
+ return invocation.authorization;
6832
+ }
6833
+ };
6834
+ }
6578
6835
  /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
6579
6836
  async createUnifiedOwnerRoot(input) {
6580
6837
  const ownerDid = this.credentialHolderDid;
@@ -7109,7 +7366,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7109
7366
  graph.assertActive();
7110
7367
  const signed = await this.signRawNetworkAuthorization({
7111
7368
  targetNode: input.targetNode,
7112
- networkId: input.networkId,
7369
+ networkId: input.body.keyPath === void 0 ? input.networkId : `${input.networkId}/${input.body.keyPath}`,
7113
7370
  action: DECRYPT_ACTION2,
7114
7371
  facts: input.facts
7115
7372
  }, { graph, session });
@@ -7282,6 +7539,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7282
7539
  invoke: graph.invoke,
7283
7540
  invokeAny: graph.invokeAny,
7284
7541
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
7542
+ sessionInvocationSigner: this.sessionInvocationSignerFor(serviceSession),
7285
7543
  fetch: graph.fetch
7286
7544
  });
7287
7545
  this._spaceService = new SpaceService({
@@ -7532,7 +7790,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7532
7790
  * Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
7533
7791
  * `POST /revoke`, and return its Authorization header value. The node
7534
7792
  * accepts it when the owner delegated `cid` or owns a space its abilities
7535
- * target, whichever key issued it. Valid for 60 seconds. Requires the
7793
+ * target, whichever key issued it. Valid for 10 minutes. Requires the
7536
7794
  * owner wallet signer.
7537
7795
  */
7538
7796
  async signOwnerRevocation(cid) {
@@ -7548,7 +7806,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7548
7806
  chainId: session.chainId,
7549
7807
  nonce: generateNonce(),
7550
7808
  issuedAt: issuedAt.toISOString(),
7551
- expirationTime: new Date(issuedAt.getTime() + 6e4).toISOString()
7809
+ expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
7552
7810
  }).prepareMessage();
7553
7811
  const signature = await this.signer.signMessage(siwe);
7554
7812
  return this.wasmBindings.siweToDelegationHeaders({ siwe, signature }).Authorization;
@@ -7775,6 +8033,40 @@ var _TinyCloudNode = class _TinyCloudNode {
7775
8033
  get encryption() {
7776
8034
  return this.getEncryptionService();
7777
8035
  }
8036
+ /** Named keys bound to the current session. */
8037
+ get keys() {
8038
+ return new Proxy({}, {
8039
+ get: (_target, property) => {
8040
+ if (property === "refreshNodeInfo") {
8041
+ return () => {
8042
+ void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
8043
+ };
8044
+ }
8045
+ if (typeof property !== "string" || ![
8046
+ "getPublicKey",
8047
+ "describeKey",
8048
+ "createKey",
8049
+ "blockKey",
8050
+ "unblockKey",
8051
+ "ensureKey",
8052
+ "encrypt",
8053
+ "decrypt",
8054
+ "decryptMany"
8055
+ ].includes(property)) return void 0;
8056
+ return (...args) => {
8057
+ this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.js").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
8058
+ return this._namedKeyClient.then((client) => {
8059
+ const method = client[property];
8060
+ return method.apply(client, args);
8061
+ });
8062
+ };
8063
+ }
8064
+ });
8065
+ }
8066
+ /** Manifest key paths unavailable during the latest `signIn()` attempt. */
8067
+ get keysUnavailable() {
8068
+ return this._keysUnavailable;
8069
+ }
7778
8070
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
7779
8071
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
7780
8072
  return this.fetchEncryptionNetworkAt(
@@ -8031,7 +8323,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8031
8323
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
8032
8324
  return false;
8033
8325
  }
8034
- const expanded = this.expandPermissionEntries(permissions);
8326
+ const expanded = expandRuntimePermissionEntries(permissions, pkhDid2(session.address, session.chainId));
8035
8327
  if (this.sessionCoversPermissionEntries(session, expanded)) {
8036
8328
  return true;
8037
8329
  }
@@ -8051,7 +8343,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8051
8343
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
8052
8344
  return [];
8053
8345
  }
8054
- const expanded = this.expandPermissionEntries(permissions);
8346
+ const expanded = expandRuntimePermissionEntries(permissions, pkhDid2(session.address, session.chainId));
8055
8347
  return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
8056
8348
  (grant) => grant.delegation
8057
8349
  );
@@ -8226,7 +8518,28 @@ var _TinyCloudNode = class _TinyCloudNode {
8226
8518
  throw new SessionExpiredError(sessionExpiry);
8227
8519
  }
8228
8520
  }
8229
- const expanded = this.expandPermissionEntries(permissions);
8521
+ const expanded = expandRuntimePermissionEntries(permissions, pkhDid2(session.address, session.chainId));
8522
+ const bindsShareKey = expanded.some((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.actions.includes("tinycloud.encryption/key.create"));
8523
+ const captured = bindsShareKey ? { ...session, jwk: { ...options?.expectedSessionKey ?? session.jwk } } : session;
8524
+ const assertCapturedKey = bindsShareKey ? () => {
8525
+ const live = session.jwk;
8526
+ const key = captured.jwk;
8527
+ if (this.currentTinyCloudSession() !== session || session.verificationMethod !== captured.verificationMethod || session.delegationCid !== captured.delegationCid || session.spaceId !== captured.spaceId || session.address !== captured.address || session.chainId !== captured.chainId || live?.kty !== key.kty || live?.crv !== key.crv || live?.x !== key.x || live?.y !== key.y || live?.d !== key.d) {
8528
+ throw new Error("Runtime permission request cancelled because the session key changed.");
8529
+ }
8530
+ } : void 0;
8531
+ const sessionKeyDid = bindsShareKey ? this.sessionDid : void 0;
8532
+ if (bindsShareKey) {
8533
+ const key = captured.jwk;
8534
+ if (key.kty !== "OKP" || key.crv !== "Ed25519" || typeof key.x !== "string") {
8535
+ throw new Error("Runtime permission session key does not match its signed identity.");
8536
+ }
8537
+ const keyDid = didKeyFromEd25519PublicKey(base64UrlDecode(key.x));
8538
+ if (!didPrincipalMatches2(keyDid, sessionKeyDid) || !didPrincipalMatches2(keyDid, captured.verificationMethod) || !didPrincipalMatches2(keyDid, new SiweMessage3(session.siwe).uri)) {
8539
+ throw new Error("Runtime permission session key does not match its signed identity.");
8540
+ }
8541
+ assertCapturedKey();
8542
+ }
8230
8543
  if (this.sessionCoversPermissionEntries(session, expanded)) {
8231
8544
  return [];
8232
8545
  }
@@ -8276,13 +8589,13 @@ var _TinyCloudNode = class _TinyCloudNode {
8276
8589
  return this.wasmBindings.prepareSession({
8277
8590
  abilities,
8278
8591
  ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
8279
- address: this.wasmBindings.ensureEip55(session.address),
8280
- chainId: session.chainId,
8592
+ address: this.wasmBindings.ensureEip55(captured.address),
8593
+ chainId: captured.chainId,
8281
8594
  domain: this.siweDomain,
8282
8595
  issuedAt: now.toISOString(),
8283
8596
  expirationTime: expiresAt.toISOString(),
8284
8597
  spaceId,
8285
- jwk: session.jwk
8598
+ jwk: captured.jwk
8286
8599
  });
8287
8600
  } catch (error) {
8288
8601
  if (rawForDelegation.some((entry) => this.isRawRevocationPermissionEntry(entry)) && /invalid ReCap resource URI/i.test(error instanceof Error ? error.message : String(error))) {
@@ -8294,8 +8607,13 @@ var _TinyCloudNode = class _TinyCloudNode {
8294
8607
  throw error;
8295
8608
  }
8296
8609
  })();
8610
+ if (bindsShareKey && !didPrincipalMatches2(new SiweMessage3(prepared.siwe).uri, sessionKeyDid)) {
8611
+ throw new Error("Prepared runtime permission targets a different session key.");
8612
+ }
8613
+ assertCapturedKey?.();
8297
8614
  const signature = await this.signer.signMessage(prepared.siwe);
8298
8615
  graph.assertActive();
8616
+ assertCapturedKey?.();
8299
8617
  const delegatedSession = this.wasmBindings.completeSessionSetup({
8300
8618
  ...prepared,
8301
8619
  signature
@@ -8324,18 +8642,19 @@ var _TinyCloudNode = class _TinyCloudNode {
8324
8642
  delegatedSession,
8325
8643
  delegatedEntries,
8326
8644
  spaceId,
8327
- session,
8645
+ captured,
8328
8646
  expiresAt,
8329
8647
  { siwe: prepared.siwe, signature }
8330
8648
  );
8331
8649
  graph.assertActive();
8650
+ assertCapturedKey?.();
8332
8651
  this.runtimePermissionGrants.push({
8333
8652
  session: {
8334
8653
  delegationHeader: delegatedSession.delegationHeader,
8335
8654
  delegationCid: delegatedSession.delegationCid,
8336
8655
  spaceId,
8337
- verificationMethod: session.verificationMethod,
8338
- jwk: session.jwk
8656
+ verificationMethod: captured.verificationMethod,
8657
+ jwk: captured.jwk
8339
8658
  },
8340
8659
  delegation,
8341
8660
  operations: this.permissionOperations(delegatedEntries, spaceId),
@@ -8664,7 +8983,19 @@ var _TinyCloudNode = class _TinyCloudNode {
8664
8983
  "delegateTo requires a non-empty permissions array"
8665
8984
  );
8666
8985
  }
8667
- const expandedEntries = this.expandPermissionEntries(permissions);
8986
+ const expandedEntries = expandDelegationPermissionEntries(permissions);
8987
+ const namedKeyEntries = expandedEntries.filter((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.path.includes("/"));
8988
+ if (namedKeyEntries.length > 0) {
8989
+ if (expandedEntries.some((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && !entry.path.includes("/"))) {
8990
+ throw new ManifestValidationError2("named-key delegation cannot include bare-network permissions");
8991
+ }
8992
+ if (options?.forceWalletSign) {
8993
+ throw new ManifestValidationError2("named-key delegation requires a signed session grant");
8994
+ }
8995
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
8996
+ const { subset, missing } = this.signedCapabilitySubset(namedKeyEntries, granted);
8997
+ if (!subset) throw new PermissionNotInManifestError(missing, granted);
8998
+ }
8668
8999
  const now = /* @__PURE__ */ new Date();
8669
9000
  const expiryMs = resolveExpiryMs(options?.expiry);
8670
9001
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -10049,7 +10380,7 @@ import {
10049
10380
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError3,
10050
10381
  PermissionNotInManifestError as PermissionNotInManifestError2,
10051
10382
  SessionExpiredError as SessionExpiredError2,
10052
- ManifestValidationError,
10383
+ ManifestValidationError as ManifestValidationError3,
10053
10384
  composeManifestRequest as composeManifestRequest2,
10054
10385
  resolveManifest as resolveManifest2,
10055
10386
  validateManifest,
@@ -10134,12 +10465,13 @@ export {
10134
10465
  IdentityParseError,
10135
10466
  InvalidRestoredSessionError,
10136
10467
  KVService3 as KVService,
10137
- ManifestValidationError,
10468
+ ManifestValidationError3 as ManifestValidationError,
10138
10469
  MemorySessionStorage,
10139
10470
  NodeUserAuthorization,
10140
10471
  PermissionNotInManifestError2 as PermissionNotInManifestError,
10141
10472
  PrefixedKVService,
10142
10473
  ProtocolMismatchError2 as ProtocolMismatchError,
10474
+ RuntimeDelegationRejectedError,
10143
10475
  SECRET_NAME_RE,
10144
10476
  SQLAction,
10145
10477
  SQLService3 as SQLService,
@@ -10162,6 +10494,7 @@ export {
10162
10494
  VersionCheckError,
10163
10495
  WasmKeyProvider,
10164
10496
  activateCompactRuntimeDelegation,
10497
+ activateValidatedRuntimeDelegation,
10165
10498
  addressStorageKey,
10166
10499
  buildSpaceUri,
10167
10500
  canonicalizeAddress3 as canonicalizeAddress,
@@ -10191,6 +10524,7 @@ export {
10191
10524
  getNodeReplicationLoaders,
10192
10525
  getTinyCloudDebugLogs,
10193
10526
  installTinyCloudDebugGlobals,
10527
+ isAppKeyHostBound,
10194
10528
  isCapabilitySubset3 as isCapabilitySubset,
10195
10529
  isEvmAddress,
10196
10530
  loadManifest,
@@ -10212,6 +10546,7 @@ export {
10212
10546
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
10213
10547
  serializeDelegation,
10214
10548
  tinyCloudDebugLogger2 as tinyCloudDebugLogger,
10215
- validateManifest
10549
+ validateManifest,
10550
+ wireOpenKeyAuthorize
10216
10551
  };
10217
10552
  //# sourceMappingURL=core.js.map