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

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-BRyeZas2.d.ts} +47 -6
  3. package/dist/{TinyCloudNode-CVO41O12.d.cts → TinyCloudNode-NrmztjDd.d.cts} +47 -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-D6JMJwor.d.ts} +1 -1
  7. package/dist/{authority-DgVV80bI.d.cts → authority-Z-reu6zL.d.cts} +1 -1
  8. package/dist/core.cjs +336 -29
  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 +330 -24
  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 +304 -40
  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 +293 -27
  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.cjs CHANGED
@@ -58,6 +58,7 @@ __export(core_exports, {
58
58
  PermissionNotInManifestError: () => import_sdk_core14.PermissionNotInManifestError,
59
59
  PrefixedKVService: () => import_sdk_core15.PrefixedKVService,
60
60
  ProtocolMismatchError: () => import_sdk_core22.ProtocolMismatchError,
61
+ RuntimeDelegationRejectedError: () => RuntimeDelegationRejectedError,
61
62
  SECRET_NAME_RE: () => import_sdk_core18.SECRET_NAME_RE,
62
63
  SQLAction: () => import_sdk_core16.SQLAction,
63
64
  SQLService: () => import_sdk_core16.SQLService,
@@ -80,6 +81,7 @@ __export(core_exports, {
80
81
  VersionCheckError: () => import_sdk_core22.VersionCheckError,
81
82
  WasmKeyProvider: () => WasmKeyProvider,
82
83
  activateCompactRuntimeDelegation: () => activateCompactRuntimeDelegation,
84
+ activateValidatedRuntimeDelegation: () => activateValidatedRuntimeDelegation,
83
85
  addressStorageKey: () => import_sdk_core13.addressStorageKey,
84
86
  buildSpaceUri: () => import_sdk_core21.buildSpaceUri,
85
87
  canonicalizeAddress: () => import_sdk_core13.canonicalizeAddress,
@@ -109,6 +111,7 @@ __export(core_exports, {
109
111
  getNodeReplicationLoaders: () => getNodeReplicationLoaders,
110
112
  getTinyCloudDebugLogs: () => import_sdk_core13.getTinyCloudDebugLogs,
111
113
  installTinyCloudDebugGlobals: () => import_sdk_core13.installTinyCloudDebugGlobals,
114
+ isAppKeyHostBound: () => isAppKeyHostBound,
112
115
  isCapabilitySubset: () => import_sdk_core14.isCapabilitySubset,
113
116
  isEvmAddress: () => import_sdk_core13.isEvmAddress,
114
117
  loadManifest: () => import_sdk_core14.loadManifest,
@@ -130,7 +133,8 @@ __export(core_exports, {
130
133
  resourceCapabilitiesToSpaceAbilitiesMap: () => import_sdk_core14.resourceCapabilitiesToSpaceAbilitiesMap,
131
134
  serializeDelegation: () => serializeDelegation,
132
135
  tinyCloudDebugLogger: () => import_sdk_core13.tinyCloudDebugLogger,
133
- validateManifest: () => import_sdk_core14.validateManifest
136
+ validateManifest: () => import_sdk_core14.validateManifest,
137
+ wireOpenKeyAuthorize: () => wireOpenKeyAuthorize
134
138
  });
135
139
  module.exports = __toCommonJS(core_exports);
136
140
  var import_sdk_core12 = require("@tinycloud/sdk-core");
@@ -345,6 +349,44 @@ var FileSessionStorage = class {
345
349
  var import_node_crypto = require("crypto");
346
350
  var import_siwe = require("siwe");
347
351
  var import_sdk_core5 = require("@tinycloud/sdk-core");
352
+ var import_resource = require("@tinycloud/sdk-services/encryption/resource");
353
+ var import_sdk_services2 = require("@tinycloud/sdk-services");
354
+
355
+ // src/authorization/appKeyHostBinding.ts
356
+ var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
357
+ var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
358
+ var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
359
+ function appKeyHostBinding(domain, appId) {
360
+ if (!APP_ID.test(appId)) return false;
361
+ if (domain.endsWith(":")) return false;
362
+ let url;
363
+ try {
364
+ url = new URL(`https://${domain}`);
365
+ } catch {
366
+ return false;
367
+ }
368
+ if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
369
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
370
+ const host = url.hostname;
371
+ if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
372
+ const labels = host.split(".");
373
+ if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
374
+ if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
375
+ const reversed = labels.reverse().join(".");
376
+ return appId === reversed || appId.startsWith(`${reversed}.`);
377
+ }
378
+ function isAppKeyHostBound(keysAppId, origin) {
379
+ if (typeof origin === "string" && !origin.includes("://")) {
380
+ return appKeyHostBinding(origin, keysAppId);
381
+ }
382
+ let url;
383
+ try {
384
+ url = typeof origin === "string" ? new URL(origin) : origin;
385
+ } catch {
386
+ return false;
387
+ }
388
+ return appKeyHostBinding(url.host, keysAppId);
389
+ }
348
390
 
349
391
  // src/replication/authority-sign-in.ts
350
392
  var import_sdk_core2 = require("@tinycloud/sdk-core");
@@ -476,6 +518,7 @@ function cloneAbilitiesMap(actions) {
476
518
  var NodeUserAuthorization = class {
477
519
  constructor(config) {
478
520
  this.activeSessionKeyId = "default";
521
+ this.preparedSessionCounter = 0;
479
522
  /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
480
523
  this.sessionGeneration = 0;
481
524
  /**
@@ -832,6 +875,23 @@ var NodeUserAuthorization = class {
832
875
  );
833
876
  return this._capabilityRequest;
834
877
  }
878
+ async refreshNamedKeyFeatureForSignIn(address, chainId) {
879
+ const request = this.getCapabilityRequest();
880
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
881
+ const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
882
+ if (hasNamedKeys) {
883
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
884
+ await this.refreshNodeInfo();
885
+ }
886
+ }
887
+ /** Manifest key paths that this sign-in cannot grant. */
888
+ get unavailableKeyPaths() {
889
+ const request = this.getCapabilityRequest();
890
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
891
+ return (request?.manifests ?? []).flatMap(
892
+ (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)
893
+ );
894
+ }
835
895
  resolveSpaceName(space, address, chainId) {
836
896
  if (space.startsWith("tinycloud:")) {
837
897
  return space;
@@ -1023,15 +1083,32 @@ var NodeUserAuthorization = class {
1023
1083
  }
1024
1084
  const rawAbilities = {};
1025
1085
  const currentDid = (0, import_sdk_core5.pkhDid)(address, chainId);
1086
+ if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
1087
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
1088
+ for (const manifest of request.manifests) {
1089
+ if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
1090
+ for (const declaration of manifest.keys ?? []) {
1091
+ const path = "path" in declaration ? declaration.path : declaration.prefix;
1092
+ const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
1093
+ for (const ability of declaration.abilities) {
1094
+ addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1026
1099
  const spaceResources = request.resources.filter((entry) => {
1027
1100
  if (entry.service !== import_sdk_core5.ENCRYPTION_PERMISSION_SERVICE) {
1028
1101
  return true;
1029
1102
  }
1103
+ const parsed = entry.path.includes("/") ? (0, import_resource.parseEncryptionResource)(entry.path) : { ...(0, import_sdk_services2.parseNetworkId)(entry.path), kind: "network" };
1104
+ const sharePrefix = parsed.kind === "prefix" && parsed.network === "default" && parsed.prefix === "share" && entry.actions.length === 1 && entry.actions[0] === DECRYPT_ACTION && didPrincipalMatches(parsed.ownerDid, currentDid);
1105
+ if (parsed.kind !== "network" && !sharePrefix) {
1106
+ throw new import_sdk_core5.ManifestValidationError("named keys go in manifest.keys");
1107
+ }
1030
1108
  for (const action of entry.actions) {
1031
1109
  addRawAbility(rawAbilities, entry.path, action);
1032
1110
  }
1033
- if (entry.actions.includes(DECRYPT_ACTION)) {
1034
- const parsed = (0, import_sdk_core5.parseNetworkId)(entry.path);
1111
+ if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
1035
1112
  if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
1036
1113
  addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
1037
1114
  }
@@ -1068,8 +1145,7 @@ var NodeUserAuthorization = class {
1068
1145
  /**
1069
1146
  * Build SIWE overrides from the top-level nonce and siweConfig.
1070
1147
  * - Top-level `nonce` is seeded first so `siweConfig.nonce` wins if both are set.
1071
- * - statement is prepended to the default statement
1072
- * - resources are appended to the default resources
1148
+ * - resources are inserted after WASM preparation, before the final ReCap
1073
1149
  * - uri triggers a warning (overwriting delegation target)
1074
1150
  * - all other fields override directly
1075
1151
  * - per-call nonce overrides siweConfig.nonce when provided
@@ -1087,9 +1163,6 @@ var NodeUserAuthorization = class {
1087
1163
  if (statement) {
1088
1164
  overrides.statement = this.statement ? `${statement} ${this.statement}` : statement;
1089
1165
  }
1090
- if (resources && resources.length > 0) {
1091
- overrides.resources = resources;
1092
- }
1093
1166
  if (uri) {
1094
1167
  console.warn(
1095
1168
  "[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."
@@ -1101,6 +1174,29 @@ var NodeUserAuthorization = class {
1101
1174
  }
1102
1175
  return overrides;
1103
1176
  }
1177
+ prepareSessionWithResources(params) {
1178
+ const resources = this.siweConfig?.resources;
1179
+ if (resources?.some((resource) => typeof resource !== "string" || !/^urn:tinycloud:recipients:[A-Za-z0-9_-]+$/.test(resource))) {
1180
+ throw new Error("SIWE metadata resources must be base64url recipients URIs");
1181
+ }
1182
+ const prepared = this.wasm.prepareSession(params);
1183
+ if (!resources?.length) return prepared;
1184
+ const recapLines = /^- urn:recap:[^\r\n]+$/gim;
1185
+ const recaps = prepared.siwe.match(recapLines);
1186
+ const recap = recaps?.[0];
1187
+ if (recaps?.length !== 1 || !prepared.siwe.endsWith(recap)) {
1188
+ throw new Error("Prepared SIWE must end with exactly one ReCap");
1189
+ }
1190
+ const insertedLines = resources.map((resource) => `- ${resource}`);
1191
+ const siwe = prepared.siwe.slice(0, -recap.length) + insertedLines.join("\n") + "\n" + recap;
1192
+ const expectedFields = (0, import_sdk_core5.extractImmutableSiweFields)(prepared.siwe);
1193
+ expectedFields.nonRecapResources = [expectedFields.nonRecapResources, ...insertedLines].filter(Boolean).join("\n");
1194
+ const actualRecaps = siwe.match(recapLines);
1195
+ if (actualRecaps?.length !== 1 || actualRecaps[0] !== recap || (0, import_sdk_core5.diffImmutableSiweFields)(expectedFields, (0, import_sdk_core5.extractImmutableSiweFields)(siwe)).length) {
1196
+ throw new Error("SIWE metadata insertion changed the prepared message");
1197
+ }
1198
+ return { ...prepared, siwe };
1199
+ }
1104
1200
  /**
1105
1201
  * Add an extension to the authorization flow.
1106
1202
  */
@@ -1348,7 +1444,7 @@ var NodeUserAuthorization = class {
1348
1444
  spaceAbilities[spaceId] = (0, import_sdk_core5.resourceCapabilitiesToAbilitiesMap)(resources);
1349
1445
  }
1350
1446
  const abilities = spaceAbilities[options.spaceId] ?? (0, import_sdk_core5.resourceCapabilitiesToAbilitiesMap)([]);
1351
- const prepared = this.wasm.prepareSession({
1447
+ const prepared = this.prepareSessionWithResources({
1352
1448
  abilities,
1353
1449
  spaceAbilities,
1354
1450
  ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
@@ -1427,6 +1523,8 @@ var NodeUserAuthorization = class {
1427
1523
  this._chainId = chainId;
1428
1524
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
1429
1525
  this.assertSignInCurrent(generation);
1526
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
1527
+ this.assertSignInCurrent(generation);
1430
1528
  const keyId = `session-${Date.now()}`;
1431
1529
  this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
1432
1530
  this.activeSessionKeyId = keyId;
@@ -1439,7 +1537,7 @@ var NodeUserAuthorization = class {
1439
1537
  const spaceId = capabilityPlan.spaceId;
1440
1538
  const now = /* @__PURE__ */ new Date();
1441
1539
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
1442
- const prepared = this.wasm.prepareSession({
1540
+ const prepared = this.prepareSessionWithResources({
1443
1541
  abilities: capabilityPlan.abilities,
1444
1542
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
1445
1543
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -1573,9 +1671,9 @@ var NodeUserAuthorization = class {
1573
1671
  refuseCaveated(this.getCapabilityRequest()?.manifests);
1574
1672
  const address = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
1575
1673
  const chainId = await this.signer.getChainId();
1576
- const keyId = `session-${Date.now()}`;
1577
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
1578
- this.activeSessionKeyId = keyId;
1674
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
1675
+ const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
1676
+ this.sessionManager.createSessionKey(keyId);
1579
1677
  const jwkString = this.sessionManager.jwk(keyId);
1580
1678
  if (!jwkString) {
1581
1679
  throw new Error("Failed to create session key");
@@ -1585,7 +1683,7 @@ var NodeUserAuthorization = class {
1585
1683
  const spaceId = capabilityPlan.spaceId;
1586
1684
  const now = /* @__PURE__ */ new Date();
1587
1685
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
1588
- const prepared = this.wasm.prepareSession({
1686
+ const prepared = this.prepareSessionWithResources({
1589
1687
  abilities: capabilityPlan.abilities,
1590
1688
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
1591
1689
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -1656,6 +1754,7 @@ var NodeUserAuthorization = class {
1656
1754
  await this.sessionStorage.save(address, persistedData);
1657
1755
  this._session = clientSession;
1658
1756
  this._tinyCloudSession = tinyCloudSession;
1757
+ this.activeSessionKeyId = keyId;
1659
1758
  this._address = address;
1660
1759
  this._chainId = chainId;
1661
1760
  await this.refreshNodeInfo();
@@ -1702,6 +1801,11 @@ var NodeUserAuthorization = class {
1702
1801
  * @param options Optional overrides for the OpenKey request.
1703
1802
  */
1704
1803
  async signInWithOpenKey(authorizeFn, options) {
1804
+ const pending = await this.prepareOpenKeySignIn(options);
1805
+ return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
1806
+ }
1807
+ /** Finish address, host and SIWE preparation before an approval click. */
1808
+ async prepareOpenKeySignIn(options) {
1705
1809
  refuseCaveated(this.getCapabilityRequest()?.manifests);
1706
1810
  if (options?.host !== void 0) {
1707
1811
  const overrideHost = options.host.trim();
@@ -1713,7 +1817,7 @@ var NodeUserAuthorization = class {
1713
1817
  const signingAddress = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
1714
1818
  const signingChainId = await this.signer.getChainId();
1715
1819
  await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
1716
- const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
1820
+ const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
1717
1821
  const presentation = this.buildPresentationEnvelope(options?.reason);
1718
1822
  const hostHint = this.primaryTinyCloudHost;
1719
1823
  const originalRequest = {
@@ -1725,12 +1829,32 @@ var NodeUserAuthorization = class {
1725
1829
  keyId: options?.openkeyKeyId,
1726
1830
  presentation
1727
1831
  };
1832
+ return {
1833
+ request: originalRequest,
1834
+ prepared,
1835
+ keyId,
1836
+ jwk,
1837
+ address,
1838
+ chainId,
1839
+ generation: this.sessionGeneration,
1840
+ createdAt: Date.now()
1841
+ };
1842
+ }
1843
+ /** Complete a prepared approval. The caller starts authorization before awaiting this method. */
1844
+ async completeOpenKeySignIn(pending, authorization, options) {
1845
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
1846
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
1847
+ }
1848
+ if (pending.generation !== this.sessionGeneration) {
1849
+ throw new Error("Prepared OpenKey sign-in expired after sign-out");
1850
+ }
1851
+ const { request: originalRequest, prepared, keyId, jwk } = pending;
1728
1852
  let result;
1729
1853
  let signed;
1730
1854
  let signature;
1731
1855
  for (let attempt = 0; ; ) {
1732
1856
  try {
1733
- result ?? (result = await authorizeFn(originalRequest));
1857
+ result ?? (result = await authorization);
1734
1858
  signed = await this.verifyOpenKeyResult(result, prepared);
1735
1859
  signature = result.signature;
1736
1860
  break;
@@ -2151,8 +2275,52 @@ var NodeUserAuthorization = class {
2151
2275
  }
2152
2276
  };
2153
2277
 
2278
+ // src/authorization/openKeyBridge.ts
2279
+ function wireOpenKeyAuthorize(openkey, opts) {
2280
+ return async (input) => {
2281
+ const result = await openkey.authorizeTinyCloud({
2282
+ protocolVersion: 1,
2283
+ siwe: input.siwe,
2284
+ keyId: input.keyId,
2285
+ jwk: input.jwk,
2286
+ host: input.host,
2287
+ // Forward the presentation envelope verbatim — the OpenKey SDK
2288
+ // validates and size-bounds it before use.
2289
+ presentation: input.presentation
2290
+ }, opts);
2291
+ if (result.protocolVersion !== 1) {
2292
+ throw new Error(
2293
+ `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
2294
+ );
2295
+ }
2296
+ if (typeof result.signedMessage !== "string" || !result.signedMessage) {
2297
+ throw new Error(
2298
+ "wireOpenKeyAuthorize: OpenKey returned no signedMessage \u2014 cannot complete a TinyCloud session"
2299
+ );
2300
+ }
2301
+ if (!Array.isArray(result.selectedActionKeys)) {
2302
+ throw new Error(
2303
+ "wireOpenKeyAuthorize: OpenKey returned no selectedActionKeys"
2304
+ );
2305
+ }
2306
+ if (!Array.isArray(result.permissions)) {
2307
+ throw new Error(
2308
+ "wireOpenKeyAuthorize: OpenKey returned no permissions"
2309
+ );
2310
+ }
2311
+ return {
2312
+ protocolVersion: 1,
2313
+ address: result.address,
2314
+ signature: result.signature,
2315
+ signedMessage: result.signedMessage,
2316
+ selectedActionKeys: result.selectedActionKeys,
2317
+ permissions: result.permissions
2318
+ };
2319
+ };
2320
+ }
2321
+
2154
2322
  // src/recordingServiceContext.ts
2155
- var import_sdk_services2 = require("@tinycloud/sdk-services");
2323
+ var import_sdk_services3 = require("@tinycloud/sdk-services");
2156
2324
  var import_requestLog = require("@tinycloud/sdk-services/telemetry/requestLog");
2157
2325
 
2158
2326
  // src/recordingRegistry.ts
@@ -2197,9 +2365,9 @@ function replicationEvent(sample, op) {
2197
2365
  }
2198
2366
  function createServiceContext(config) {
2199
2367
  const { requestSink, ...base } = config;
2200
- return requestSink === void 0 ? new import_sdk_services2.ServiceContext(base) : new RecordingServiceContext(base, requestSink);
2368
+ return requestSink === void 0 ? new import_sdk_services3.ServiceContext(base) : new RecordingServiceContext(base, requestSink);
2201
2369
  }
2202
- var RecordingServiceContext = class extends import_sdk_services2.ServiceContext {
2370
+ var RecordingServiceContext = class extends import_sdk_services3.ServiceContext {
2203
2371
  constructor(config, sink) {
2204
2372
  const pending = {};
2205
2373
  super({
@@ -2793,8 +2961,9 @@ function deserializeDelegation(data) {
2793
2961
  // src/TinyCloudNode.ts
2794
2962
  var import_siwe2 = require("siwe");
2795
2963
  var import_sdk_core11 = require("@tinycloud/sdk-core");
2796
- var import_sdk_services3 = require("@tinycloud/sdk-services");
2964
+ var import_sdk_services4 = require("@tinycloud/sdk-services");
2797
2965
  var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
2966
+ var import_resource2 = require("@tinycloud/sdk-services/encryption/resource");
2798
2967
 
2799
2968
  // src/account/AccountService.ts
2800
2969
  var import_sdk_core7 = require("@tinycloud/sdk-core");
@@ -3271,6 +3440,7 @@ var ROOT_DELEGATION_ACTIONS = [
3271
3440
  import_sdk_core11.DUCKDB.ALL
3272
3441
  ];
3273
3442
  var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core11.EXPIRY.SESSION_MS;
3443
+ var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
3274
3444
  function unifiedRootAttenuation(capabilities) {
3275
3445
  const attenuation = {};
3276
3446
  for (const capability of capabilities) {
@@ -3768,6 +3938,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3768
3938
  constructor(config = {}) {
3769
3939
  this.signer = null;
3770
3940
  this.auth = null;
3941
+ this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
3771
3942
  this.tc = null;
3772
3943
  this._chainId = 1;
3773
3944
  this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
@@ -3802,6 +3973,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3802
3973
  return void 0;
3803
3974
  }
3804
3975
  };
3976
+ this._keysUnavailable = [];
3805
3977
  this._baseVaults = /* @__PURE__ */ new Map();
3806
3978
  this._baseSecrets = /* @__PURE__ */ new Map();
3807
3979
  this._secrets = /* @__PURE__ */ new Map();
@@ -4629,6 +4801,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4629
4801
  this._hooks = void 0;
4630
4802
  this._vault = void 0;
4631
4803
  this._encryption = void 0;
4804
+ this._namedKeyClient = void 0;
4805
+ this._keysUnavailable = [];
4632
4806
  this._baseVaults.clear();
4633
4807
  this._baseSecrets.clear();
4634
4808
  this._secrets.clear();
@@ -4705,6 +4879,37 @@ var _TinyCloudNode = class _TinyCloudNode {
4705
4879
  }
4706
4880
  await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
4707
4881
  }
4882
+ /** Prepare the OpenKey request before the approval click. */
4883
+ async prepareOpenKeySignIn() {
4884
+ if (!this.signer || !this.tc || !this.auth) {
4885
+ throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
4886
+ }
4887
+ await this.signOutInFlight;
4888
+ await this.accountRegistryTail;
4889
+ await this.wasmBindings.ensureInitialized?.();
4890
+ return this.auth.prepareOpenKeySignIn();
4891
+ }
4892
+ /** Start OpenKey authorization synchronously, then complete normal session setup. */
4893
+ signInWithPreparedOpenKey(pending, authorizeFn) {
4894
+ if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
4895
+ throw new Error("Prepared OpenKey sign-in is unavailable");
4896
+ }
4897
+ if (this.consumedOpenKeyPendings.has(pending)) {
4898
+ throw new Error("Prepared OpenKey sign-in was already used");
4899
+ }
4900
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
4901
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
4902
+ }
4903
+ this.consumedOpenKeyPendings.add(pending);
4904
+ const authorization = authorizeFn(pending.request);
4905
+ void authorization.catch(() => void 0);
4906
+ return this.signInWith(() => {
4907
+ if (this._address !== pending.address || this._chainId !== pending.chainId) {
4908
+ throw new Error("OpenKey account changed after sign-in preparation");
4909
+ }
4910
+ return this.auth.completeOpenKeySignIn(pending, authorization);
4911
+ });
4912
+ }
4708
4913
  async signInWith(performSignIn) {
4709
4914
  refuseCaveated(this.auth?.capabilityRequest?.manifests);
4710
4915
  const epoch = this.sessionEpoch;
@@ -4751,6 +4956,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4751
4956
  this._hooks = void 0;
4752
4957
  this._vault = void 0;
4753
4958
  this._encryption = void 0;
4959
+ this._namedKeyClient = void 0;
4960
+ this._keysUnavailable = [];
4754
4961
  this._baseVaults.clear();
4755
4962
  this._baseSecrets.clear();
4756
4963
  this._secrets.clear();
@@ -4803,6 +5010,26 @@ var _TinyCloudNode = class _TinyCloudNode {
4803
5010
  this.assertSessionEpoch(epoch, "Sign-in");
4804
5011
  await this.ensureRequestedEncryptionNetworks();
4805
5012
  this.assertSessionEpoch(epoch, "Sign-in");
5013
+ this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
5014
+ if (this._keysUnavailable.length > 0) {
5015
+ this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
5016
+ }
5017
+ if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
5018
+ for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
5019
+ for (const declaration of manifest.keys ?? []) {
5020
+ if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
5021
+ try {
5022
+ await this.keys.ensureKey(declaration.path);
5023
+ } catch (error) {
5024
+ this._keysUnavailable.push(declaration.path);
5025
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
5026
+ this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
5027
+ }
5028
+ this.assertSessionEpoch(epoch, "Sign-in");
5029
+ }
5030
+ }
5031
+ }
5032
+ }
4806
5033
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
4807
5034
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(import_sdk_core11.SECRETS_SPACE));
4808
5035
  }
@@ -5379,7 +5606,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5379
5606
  if (!this.currentSessionCoversAccountRegistry()) {
5380
5607
  if (!_TinyCloudNode.accountRegistrySkipLogged) {
5381
5608
  _TinyCloudNode.accountRegistrySkipLogged = true;
5382
- import_sdk_services3.tinyCloudDebugLogger.log("registry.skip");
5609
+ import_sdk_services4.tinyCloudDebugLogger.log("registry.skip");
5383
5610
  }
5384
5611
  return;
5385
5612
  }
@@ -5590,7 +5817,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5590
5817
  const networkIds = /* @__PURE__ */ new Set();
5591
5818
  for (const resource of request.resources) {
5592
5819
  if (resource.service === import_sdk_core11.ENCRYPTION_PERMISSION_SERVICE && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
5593
- networkIds.add(resource.path);
5820
+ const parsed = resource.path.includes("/") ? (0, import_resource2.parseEncryptionResource)(resource.path) : { ...(0, import_sdk_core11.parseNetworkId)(resource.path), kind: "network" };
5821
+ if (parsed.kind === "network") networkIds.add(resource.path);
5594
5822
  }
5595
5823
  }
5596
5824
  return [...networkIds];
@@ -6140,6 +6368,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6140
6368
  this._hooks = stagedGraph.hooks;
6141
6369
  this._vault = stagedGraph.vault;
6142
6370
  this._encryption = stagedGraph.encryption;
6371
+ this._namedKeyClient = void 0;
6372
+ this._keysUnavailable = [];
6143
6373
  this._capabilityRegistry = stagedGraph.capabilityRegistry;
6144
6374
  this._keyProvider = stagedGraph.keyProvider;
6145
6375
  this._sharingService = stagedGraph.sharingService;
@@ -6321,6 +6551,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6321
6551
  invoke: graph.invoke,
6322
6552
  invokeAny: graph.invokeAny,
6323
6553
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
6554
+ sessionInvocationSigner: this.sessionInvocationSignerFor(input.serviceSession),
6324
6555
  fetch: graph.fetch
6325
6556
  });
6326
6557
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -6536,6 +6767,31 @@ var _TinyCloudNode = class _TinyCloudNode {
6536
6767
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
6537
6768
  return signBytesWithJwk(bytes, session.jwk);
6538
6769
  }
6770
+ /**
6771
+ * Signs proofless invocations with `session`'s own did:key, for the node's
6772
+ * issued-delegation list and revocation by the delegator. The audience is
6773
+ * this Node's DID; each invocation lives 60 seconds.
6774
+ */
6775
+ sessionInvocationSignerFor(session) {
6776
+ return {
6777
+ did: () => session.verificationMethod.split("#", 1)[0],
6778
+ sign: async (attenuation) => {
6779
+ const now = Math.floor(Date.now() / 1e3);
6780
+ const invocation = await (0, import_sdk_core11.signCompactUcanAuthorization)({
6781
+ issuerDid: session.verificationMethod.split("#", 1)[0],
6782
+ audienceDid: await this.fetchNodeId(),
6783
+ attenuation,
6784
+ facts: [{}],
6785
+ proofs: [],
6786
+ notBefore: now,
6787
+ expiresAt: now + 60,
6788
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
6789
+ sign: (bytes) => signBytesWithJwk(bytes, session.jwk)
6790
+ });
6791
+ return invocation.authorization;
6792
+ }
6793
+ };
6794
+ }
6539
6795
  /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
6540
6796
  async createUnifiedOwnerRoot(input) {
6541
6797
  const ownerDid = this.credentialHolderDid;
@@ -7049,7 +7305,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7049
7305
  );
7050
7306
  graph.assertActive();
7051
7307
  if (!response.ok) {
7052
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services3.parsePermissionHint)({
7308
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services4.parsePermissionHint)({
7053
7309
  service: "tinycloud.encryption",
7054
7310
  path: networkId,
7055
7311
  actions: ["tinycloud.encryption/decrypt"]
@@ -7070,7 +7326,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7070
7326
  graph.assertActive();
7071
7327
  const signed = await this.signRawNetworkAuthorization({
7072
7328
  targetNode: input.targetNode,
7073
- networkId: input.networkId,
7329
+ networkId: input.body.keyPath === void 0 ? input.networkId : `${input.networkId}/${input.body.keyPath}`,
7074
7330
  action: DECRYPT_ACTION2,
7075
7331
  facts: input.facts
7076
7332
  }, { graph, session });
@@ -7243,6 +7499,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7243
7499
  invoke: graph.invoke,
7244
7500
  invokeAny: graph.invokeAny,
7245
7501
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
7502
+ sessionInvocationSigner: this.sessionInvocationSignerFor(serviceSession),
7246
7503
  fetch: graph.fetch
7247
7504
  });
7248
7505
  this._spaceService = new import_sdk_core11.SpaceService({
@@ -7493,7 +7750,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7493
7750
  * Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
7494
7751
  * `POST /revoke`, and return its Authorization header value. The node
7495
7752
  * accepts it when the owner delegated `cid` or owns a space its abilities
7496
- * target, whichever key issued it. Valid for 60 seconds. Requires the
7753
+ * target, whichever key issued it. Valid for 10 minutes. Requires the
7497
7754
  * owner wallet signer.
7498
7755
  */
7499
7756
  async signOwnerRevocation(cid) {
@@ -7509,7 +7766,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7509
7766
  chainId: session.chainId,
7510
7767
  nonce: (0, import_siwe2.generateNonce)(),
7511
7768
  issuedAt: issuedAt.toISOString(),
7512
- expirationTime: new Date(issuedAt.getTime() + 6e4).toISOString()
7769
+ expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
7513
7770
  }).prepareMessage();
7514
7771
  const signature = await this.signer.signMessage(siwe);
7515
7772
  return this.wasmBindings.siweToDelegationHeaders({ siwe, signature }).Authorization;
@@ -7736,6 +7993,40 @@ var _TinyCloudNode = class _TinyCloudNode {
7736
7993
  get encryption() {
7737
7994
  return this.getEncryptionService();
7738
7995
  }
7996
+ /** Named keys bound to the current session. */
7997
+ get keys() {
7998
+ return new Proxy({}, {
7999
+ get: (_target, property) => {
8000
+ if (property === "refreshNodeInfo") {
8001
+ return () => {
8002
+ void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
8003
+ };
8004
+ }
8005
+ if (typeof property !== "string" || ![
8006
+ "getPublicKey",
8007
+ "describeKey",
8008
+ "createKey",
8009
+ "blockKey",
8010
+ "unblockKey",
8011
+ "ensureKey",
8012
+ "encrypt",
8013
+ "decrypt",
8014
+ "decryptMany"
8015
+ ].includes(property)) return void 0;
8016
+ return (...args) => {
8017
+ this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.cjs").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
8018
+ return this._namedKeyClient.then((client) => {
8019
+ const method = client[property];
8020
+ return method.apply(client, args);
8021
+ });
8022
+ };
8023
+ }
8024
+ });
8025
+ }
8026
+ /** Manifest key paths unavailable during the latest `signIn()` attempt. */
8027
+ get keysUnavailable() {
8028
+ return this._keysUnavailable;
8029
+ }
7739
8030
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
7740
8031
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
7741
8032
  return this.fetchEncryptionNetworkAt(
@@ -7863,7 +8154,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7863
8154
  );
7864
8155
  if (result.status !== "ok") {
7865
8156
  if (result.status === "permission_required") {
7866
- const hint = (0, import_sdk_services3.parsePermissionHint)(result.hint);
8157
+ const hint = (0, import_sdk_services4.parsePermissionHint)(result.hint);
7867
8158
  if (hint === void 0) return { status: "read_failed" };
7868
8159
  return {
7869
8160
  status: "permission_required",
@@ -8625,7 +8916,19 @@ var _TinyCloudNode = class _TinyCloudNode {
8625
8916
  "delegateTo requires a non-empty permissions array"
8626
8917
  );
8627
8918
  }
8628
- const expandedEntries = this.expandPermissionEntries(permissions);
8919
+ const expandedEntries = (0, import_sdk_core11.expandDelegationPermissionEntries)(permissions);
8920
+ const namedKeyEntries = expandedEntries.filter((entry) => entry.service === import_sdk_core11.ENCRYPTION_PERMISSION_SERVICE && entry.path.includes("/"));
8921
+ if (namedKeyEntries.length > 0) {
8922
+ if (expandedEntries.some((entry) => entry.service === import_sdk_core11.ENCRYPTION_PERMISSION_SERVICE && !entry.path.includes("/"))) {
8923
+ throw new import_sdk_core11.ManifestValidationError("named-key delegation cannot include bare-network permissions");
8924
+ }
8925
+ if (options?.forceWalletSign) {
8926
+ throw new import_sdk_core11.ManifestValidationError("named-key delegation requires a signed session grant");
8927
+ }
8928
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
8929
+ const { subset, missing } = this.signedCapabilitySubset(namedKeyEntries, granted);
8930
+ if (!subset) throw new import_sdk_core11.PermissionNotInManifestError(missing, granted);
8931
+ }
8629
8932
  const now = /* @__PURE__ */ new Date();
8630
8933
  const expiryMs = resolveExpiryMs(options?.expiry);
8631
8934
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -10041,6 +10344,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
10041
10344
  PermissionNotInManifestError,
10042
10345
  PrefixedKVService,
10043
10346
  ProtocolMismatchError,
10347
+ RuntimeDelegationRejectedError,
10044
10348
  SECRET_NAME_RE,
10045
10349
  SQLAction,
10046
10350
  SQLService,
@@ -10063,6 +10367,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
10063
10367
  VersionCheckError,
10064
10368
  WasmKeyProvider,
10065
10369
  activateCompactRuntimeDelegation,
10370
+ activateValidatedRuntimeDelegation,
10066
10371
  addressStorageKey,
10067
10372
  buildSpaceUri,
10068
10373
  canonicalizeAddress,
@@ -10092,6 +10397,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
10092
10397
  getNodeReplicationLoaders,
10093
10398
  getTinyCloudDebugLogs,
10094
10399
  installTinyCloudDebugGlobals,
10400
+ isAppKeyHostBound,
10095
10401
  isCapabilitySubset,
10096
10402
  isEvmAddress,
10097
10403
  loadManifest,
@@ -10113,6 +10419,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
10113
10419
  resourceCapabilitiesToSpaceAbilitiesMap,
10114
10420
  serializeDelegation,
10115
10421
  tinyCloudDebugLogger,
10116
- validateManifest
10422
+ validateManifest,
10423
+ wireOpenKeyAuthorize
10117
10424
  });
10118
10425
  //# sourceMappingURL=core.cjs.map