@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/index.js CHANGED
@@ -17099,6 +17099,9 @@ function assertValidReplicationConfig(replication) {
17099
17099
  if (replication?.syncScope !== void 0 && replication.syncScope !== "prefixes") {
17100
17100
  throw new TypeError('replication.syncScope must be "prefixes"');
17101
17101
  }
17102
+ if (replication?.contentFetch !== void 0 && replication.contentFetch !== "eager" && replication.contentFetch !== "lazy") {
17103
+ throw new TypeError('replication.contentFetch must be "eager" or "lazy"');
17104
+ }
17102
17105
  if (replication === void 0 || !replication.enabled) return;
17103
17106
  if (replication.storage === void 0 || replication.storage === null) {
17104
17107
  throw new TypeError("replication.enabled requires replication.storage");
@@ -17151,6 +17154,7 @@ function resolvedOptions(options) {
17151
17154
  maxStalenessMs: options.maxStalenessMs ?? 12e4,
17152
17155
  staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5e3,
17153
17156
  allowSecrets: options.allowSecrets === true,
17157
+ contentFetch: options.contentFetch ?? "eager",
17154
17158
  verify: options.verify === true
17155
17159
  };
17156
17160
  }
@@ -18686,6 +18690,7 @@ function deserializeDelegation(data) {
18686
18690
 
18687
18691
  // src/TinyCloudNode.ts
18688
18692
  import { generateNonce, SiweMessage as SiweMessage3 } from "siwe";
18693
+ import { didKeyFromEd25519PublicKey } from "@tinycloud/share-envelope";
18689
18694
 
18690
18695
  // src/replication/module-registry.ts
18691
18696
  var nodeLoaders;
@@ -18734,10 +18739,13 @@ import {
18734
18739
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
18735
18740
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
18736
18741
  PermissionNotInManifestError,
18742
+ ManifestValidationError as ManifestValidationError2,
18737
18743
  SessionExpiredError,
18738
18744
  canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
18739
18745
  recapCaveatsEqual as recapCaveatsEqual2,
18740
18746
  expandPermissionEntries as expandPermissionEntriesCore,
18747
+ expandRuntimePermissionEntries,
18748
+ expandDelegationPermissionEntries,
18741
18749
  isCapabilitySubset as isCapabilitySubset2,
18742
18750
  isExplicitOnlyAction,
18743
18751
  parseRecapCapabilities,
@@ -18768,6 +18776,7 @@ import {
18768
18776
  withoutShareLink,
18769
18777
  verifyEip191MessageSignature,
18770
18778
  signCompactUcanRootAuthorization,
18779
+ signCompactUcanAuthorization,
18771
18780
  markDelegationTerminal,
18772
18781
  isStorageFullError,
18773
18782
  STORAGE_FULL_MESSAGE,
@@ -18783,9 +18792,9 @@ import {
18783
18792
  tinyCloudDebugLogger
18784
18793
  } from "@tinycloud/sdk-services";
18785
18794
  import { RequestLog, observeReplicaReads, readOnlyRequestLog } from "@tinycloud/sdk-services/telemetry/requestLog";
18795
+ import { parseEncryptionResource as parseEncryptionResource2 } from "@tinycloud/sdk-services/encryption/resource";
18786
18796
 
18787
18797
  // src/authorization/NodeUserAuthorization.ts
18788
- init_authority_sign_in();
18789
18798
  import { createHash } from "crypto";
18790
18799
  import { SiweMessage } from "siwe";
18791
18800
  import {
@@ -18794,10 +18803,10 @@ import {
18794
18803
  activateSessionWithHost,
18795
18804
  checkNodeInfo,
18796
18805
  AutoApproveSpaceCreationHandler,
18806
+ ManifestValidationError,
18797
18807
  DEFAULT_MANIFEST_SPACE,
18798
18808
  ENCRYPTION_PERMISSION_SERVICE,
18799
18809
  composeManifestRequest,
18800
- parseNetworkId,
18801
18810
  principalDidEquals as principalDidEquals2,
18802
18811
  resourceCapabilitiesToAbilitiesMap,
18803
18812
  resourceCapabilitiesToSpaceAbilitiesMap,
@@ -18825,6 +18834,47 @@ import {
18825
18834
  HOOKS,
18826
18835
  ENCRYPTION
18827
18836
  } from "@tinycloud/sdk-core";
18837
+ import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
18838
+ import { parseNetworkId } from "@tinycloud/sdk-services";
18839
+
18840
+ // src/authorization/appKeyHostBinding.ts
18841
+ var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
18842
+ var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
18843
+ var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
18844
+ function appKeyHostBinding(domain, appId) {
18845
+ if (!APP_ID.test(appId)) return false;
18846
+ if (domain.endsWith(":")) return false;
18847
+ let url;
18848
+ try {
18849
+ url = new URL(`https://${domain}`);
18850
+ } catch {
18851
+ return false;
18852
+ }
18853
+ if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
18854
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
18855
+ const host = url.hostname;
18856
+ if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
18857
+ const labels = host.split(".");
18858
+ if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
18859
+ if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
18860
+ const reversed = labels.reverse().join(".");
18861
+ return appId === reversed || appId.startsWith(`${reversed}.`);
18862
+ }
18863
+ function isAppKeyHostBound(keysAppId, origin) {
18864
+ if (typeof origin === "string" && !origin.includes("://")) {
18865
+ return appKeyHostBinding(origin, keysAppId);
18866
+ }
18867
+ let url;
18868
+ try {
18869
+ url = typeof origin === "string" ? new URL(origin) : origin;
18870
+ } catch {
18871
+ return false;
18872
+ }
18873
+ return appKeyHostBinding(url.host, keysAppId);
18874
+ }
18875
+
18876
+ // src/authorization/NodeUserAuthorization.ts
18877
+ init_authority_sign_in();
18828
18878
 
18829
18879
  // src/authorization/caveatRefusal.ts
18830
18880
  import {
@@ -18982,6 +19032,7 @@ function cloneAbilitiesMap(actions) {
18982
19032
  var NodeUserAuthorization = class {
18983
19033
  constructor(config) {
18984
19034
  this.activeSessionKeyId = "default";
19035
+ this.preparedSessionCounter = 0;
18985
19036
  /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
18986
19037
  this.sessionGeneration = 0;
18987
19038
  /**
@@ -19338,6 +19389,23 @@ var NodeUserAuthorization = class {
19338
19389
  );
19339
19390
  return this._capabilityRequest;
19340
19391
  }
19392
+ async refreshNamedKeyFeatureForSignIn(address, chainId) {
19393
+ const request = this.getCapabilityRequest();
19394
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19395
+ const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
19396
+ if (hasNamedKeys) {
19397
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
19398
+ await this.refreshNodeInfo();
19399
+ }
19400
+ }
19401
+ /** Manifest key paths that this sign-in cannot grant. */
19402
+ get unavailableKeyPaths() {
19403
+ const request = this.getCapabilityRequest();
19404
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19405
+ return (request?.manifests ?? []).flatMap(
19406
+ (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)
19407
+ );
19408
+ }
19341
19409
  resolveSpaceName(space, address, chainId) {
19342
19410
  if (space.startsWith("tinycloud:")) {
19343
19411
  return space;
@@ -19529,15 +19597,32 @@ var NodeUserAuthorization = class {
19529
19597
  }
19530
19598
  const rawAbilities = {};
19531
19599
  const currentDid = pkhDid(address, chainId);
19600
+ if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
19601
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19602
+ for (const manifest of request.manifests) {
19603
+ if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
19604
+ for (const declaration of manifest.keys ?? []) {
19605
+ const path = "path" in declaration ? declaration.path : declaration.prefix;
19606
+ const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
19607
+ for (const ability of declaration.abilities) {
19608
+ addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
19609
+ }
19610
+ }
19611
+ }
19612
+ }
19532
19613
  const spaceResources = request.resources.filter((entry) => {
19533
19614
  if (entry.service !== ENCRYPTION_PERMISSION_SERVICE) {
19534
19615
  return true;
19535
19616
  }
19617
+ const parsed = entry.path.includes("/") ? parseEncryptionResource(entry.path) : { ...parseNetworkId(entry.path), kind: "network" };
19618
+ const sharePrefix = parsed.kind === "prefix" && parsed.network === "default" && parsed.prefix === "share" && entry.actions.length === 1 && entry.actions[0] === DECRYPT_ACTION && didPrincipalMatches(parsed.ownerDid, currentDid);
19619
+ if (parsed.kind !== "network" && !sharePrefix) {
19620
+ throw new ManifestValidationError("named keys go in manifest.keys");
19621
+ }
19536
19622
  for (const action of entry.actions) {
19537
19623
  addRawAbility(rawAbilities, entry.path, action);
19538
19624
  }
19539
- if (entry.actions.includes(DECRYPT_ACTION)) {
19540
- const parsed = parseNetworkId(entry.path);
19625
+ if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
19541
19626
  if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
19542
19627
  addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
19543
19628
  }
@@ -19574,8 +19659,7 @@ var NodeUserAuthorization = class {
19574
19659
  /**
19575
19660
  * Build SIWE overrides from the top-level nonce and siweConfig.
19576
19661
  * - Top-level `nonce` is seeded first so `siweConfig.nonce` wins if both are set.
19577
- * - statement is prepended to the default statement
19578
- * - resources are appended to the default resources
19662
+ * - resources are inserted after WASM preparation, before the final ReCap
19579
19663
  * - uri triggers a warning (overwriting delegation target)
19580
19664
  * - all other fields override directly
19581
19665
  * - per-call nonce overrides siweConfig.nonce when provided
@@ -19593,9 +19677,6 @@ var NodeUserAuthorization = class {
19593
19677
  if (statement) {
19594
19678
  overrides.statement = this.statement ? `${statement} ${this.statement}` : statement;
19595
19679
  }
19596
- if (resources && resources.length > 0) {
19597
- overrides.resources = resources;
19598
- }
19599
19680
  if (uri) {
19600
19681
  console.warn(
19601
19682
  "[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."
@@ -19607,6 +19688,29 @@ var NodeUserAuthorization = class {
19607
19688
  }
19608
19689
  return overrides;
19609
19690
  }
19691
+ prepareSessionWithResources(params) {
19692
+ const resources = this.siweConfig?.resources;
19693
+ if (resources?.some((resource) => typeof resource !== "string" || !/^urn:tinycloud:recipients:[A-Za-z0-9_-]+$/.test(resource))) {
19694
+ throw new Error("SIWE metadata resources must be base64url recipients URIs");
19695
+ }
19696
+ const prepared = this.wasm.prepareSession(params);
19697
+ if (!resources?.length) return prepared;
19698
+ const recapLines = /^- urn:recap:[^\r\n]+$/gim;
19699
+ const recaps = prepared.siwe.match(recapLines);
19700
+ const recap = recaps?.[0];
19701
+ if (recaps?.length !== 1 || !prepared.siwe.endsWith(recap)) {
19702
+ throw new Error("Prepared SIWE must end with exactly one ReCap");
19703
+ }
19704
+ const insertedLines = resources.map((resource) => `- ${resource}`);
19705
+ const siwe = prepared.siwe.slice(0, -recap.length) + insertedLines.join("\n") + "\n" + recap;
19706
+ const expectedFields = extractImmutableSiweFields(prepared.siwe);
19707
+ expectedFields.nonRecapResources = [expectedFields.nonRecapResources, ...insertedLines].filter(Boolean).join("\n");
19708
+ const actualRecaps = siwe.match(recapLines);
19709
+ if (actualRecaps?.length !== 1 || actualRecaps[0] !== recap || diffImmutableSiweFields(expectedFields, extractImmutableSiweFields(siwe)).length) {
19710
+ throw new Error("SIWE metadata insertion changed the prepared message");
19711
+ }
19712
+ return { ...prepared, siwe };
19713
+ }
19610
19714
  /**
19611
19715
  * Add an extension to the authorization flow.
19612
19716
  */
@@ -19854,7 +19958,7 @@ var NodeUserAuthorization = class {
19854
19958
  spaceAbilities[spaceId] = resourceCapabilitiesToAbilitiesMap(resources);
19855
19959
  }
19856
19960
  const abilities = spaceAbilities[options.spaceId] ?? resourceCapabilitiesToAbilitiesMap([]);
19857
- const prepared = this.wasm.prepareSession({
19961
+ const prepared = this.prepareSessionWithResources({
19858
19962
  abilities,
19859
19963
  spaceAbilities,
19860
19964
  ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
@@ -19933,8 +20037,10 @@ var NodeUserAuthorization = class {
19933
20037
  this._chainId = chainId;
19934
20038
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
19935
20039
  this.assertSignInCurrent(generation);
19936
- const keyId = `session-${Date.now()}`;
19937
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
20040
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20041
+ this.assertSignInCurrent(generation);
20042
+ const keyId = `session-${Date.now()}-${Math.random().toString(36).slice(2)}`;
20043
+ this.sessionManager.createSessionKey(keyId);
19938
20044
  this.activeSessionKeyId = keyId;
19939
20045
  const jwkString = this.sessionManager.jwk(keyId);
19940
20046
  if (!jwkString) {
@@ -19945,7 +20051,7 @@ var NodeUserAuthorization = class {
19945
20051
  const spaceId = capabilityPlan.spaceId;
19946
20052
  const now = /* @__PURE__ */ new Date();
19947
20053
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
19948
- const prepared = this.wasm.prepareSession({
20054
+ const prepared = this.prepareSessionWithResources({
19949
20055
  abilities: capabilityPlan.abilities,
19950
20056
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
19951
20057
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -20079,9 +20185,9 @@ var NodeUserAuthorization = class {
20079
20185
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20080
20186
  const address = canonicalizeAddress(await this.signer.getAddress());
20081
20187
  const chainId = await this.signer.getChainId();
20082
- const keyId = `session-${Date.now()}`;
20083
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
20084
- this.activeSessionKeyId = keyId;
20188
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20189
+ const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
20190
+ this.sessionManager.createSessionKey(keyId);
20085
20191
  const jwkString = this.sessionManager.jwk(keyId);
20086
20192
  if (!jwkString) {
20087
20193
  throw new Error("Failed to create session key");
@@ -20091,7 +20197,7 @@ var NodeUserAuthorization = class {
20091
20197
  const spaceId = capabilityPlan.spaceId;
20092
20198
  const now = /* @__PURE__ */ new Date();
20093
20199
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
20094
- const prepared = this.wasm.prepareSession({
20200
+ const prepared = this.prepareSessionWithResources({
20095
20201
  abilities: capabilityPlan.abilities,
20096
20202
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
20097
20203
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -20162,6 +20268,7 @@ var NodeUserAuthorization = class {
20162
20268
  await this.sessionStorage.save(address, persistedData);
20163
20269
  this._session = clientSession;
20164
20270
  this._tinyCloudSession = tinyCloudSession;
20271
+ this.activeSessionKeyId = keyId;
20165
20272
  this._address = address;
20166
20273
  this._chainId = chainId;
20167
20274
  await this.refreshNodeInfo();
@@ -20208,6 +20315,11 @@ var NodeUserAuthorization = class {
20208
20315
  * @param options Optional overrides for the OpenKey request.
20209
20316
  */
20210
20317
  async signInWithOpenKey(authorizeFn, options) {
20318
+ const pending = await this.prepareOpenKeySignIn(options);
20319
+ return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
20320
+ }
20321
+ /** Finish address, host and SIWE preparation before an approval click. */
20322
+ async prepareOpenKeySignIn(options) {
20211
20323
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20212
20324
  if (options?.host !== void 0) {
20213
20325
  const overrideHost = options.host.trim();
@@ -20219,7 +20331,7 @@ var NodeUserAuthorization = class {
20219
20331
  const signingAddress = canonicalizeAddress(await this.signer.getAddress());
20220
20332
  const signingChainId = await this.signer.getChainId();
20221
20333
  await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
20222
- const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
20334
+ const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
20223
20335
  const presentation = this.buildPresentationEnvelope(options?.reason);
20224
20336
  const hostHint = this.primaryTinyCloudHost;
20225
20337
  const originalRequest = {
@@ -20231,12 +20343,32 @@ var NodeUserAuthorization = class {
20231
20343
  keyId: options?.openkeyKeyId,
20232
20344
  presentation
20233
20345
  };
20346
+ return {
20347
+ request: originalRequest,
20348
+ prepared,
20349
+ keyId,
20350
+ jwk,
20351
+ address,
20352
+ chainId,
20353
+ generation: this.sessionGeneration,
20354
+ createdAt: Date.now()
20355
+ };
20356
+ }
20357
+ /** Complete a prepared approval. The caller starts authorization before awaiting this method. */
20358
+ async completeOpenKeySignIn(pending, authorization, options) {
20359
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
20360
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
20361
+ }
20362
+ if (pending.generation !== this.sessionGeneration) {
20363
+ throw new Error("Prepared OpenKey sign-in expired after sign-out");
20364
+ }
20365
+ const { request: originalRequest, prepared, keyId, jwk } = pending;
20234
20366
  let result;
20235
20367
  let signed2;
20236
20368
  let signature2;
20237
20369
  for (let attempt = 0; ; ) {
20238
20370
  try {
20239
- result ?? (result = await authorizeFn(originalRequest));
20371
+ result ?? (result = await authorization);
20240
20372
  signed2 = await this.verifyOpenKeyResult(result, prepared);
20241
20373
  signature2 = result.signature;
20242
20374
  break;
@@ -21147,6 +21279,7 @@ var ROOT_DELEGATION_ACTIONS = [
21147
21279
  DUCKDB2.ALL
21148
21280
  ];
21149
21281
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
21282
+ var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
21150
21283
  function unifiedRootAttenuation(capabilities) {
21151
21284
  const attenuation = {};
21152
21285
  for (const capability of capabilities) {
@@ -21644,6 +21777,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21644
21777
  constructor(config = {}) {
21645
21778
  this.signer = null;
21646
21779
  this.auth = null;
21780
+ this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
21647
21781
  this.tc = null;
21648
21782
  this._chainId = 1;
21649
21783
  this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
@@ -21678,6 +21812,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21678
21812
  return void 0;
21679
21813
  }
21680
21814
  };
21815
+ this._keysUnavailable = [];
21681
21816
  this._baseVaults = /* @__PURE__ */ new Map();
21682
21817
  this._baseSecrets = /* @__PURE__ */ new Map();
21683
21818
  this._secrets = /* @__PURE__ */ new Map();
@@ -22505,6 +22640,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22505
22640
  this._hooks = void 0;
22506
22641
  this._vault = void 0;
22507
22642
  this._encryption = void 0;
22643
+ this._namedKeyClient = void 0;
22644
+ this._keysUnavailable = [];
22508
22645
  this._baseVaults.clear();
22509
22646
  this._baseSecrets.clear();
22510
22647
  this._secrets.clear();
@@ -22581,6 +22718,37 @@ var _TinyCloudNode = class _TinyCloudNode {
22581
22718
  }
22582
22719
  await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
22583
22720
  }
22721
+ /** Prepare the OpenKey request before the approval click. */
22722
+ async prepareOpenKeySignIn() {
22723
+ if (!this.signer || !this.tc || !this.auth) {
22724
+ throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
22725
+ }
22726
+ await this.signOutInFlight;
22727
+ await this.accountRegistryTail;
22728
+ await this.wasmBindings.ensureInitialized?.();
22729
+ return this.auth.prepareOpenKeySignIn();
22730
+ }
22731
+ /** Start OpenKey authorization synchronously, then complete normal session setup. */
22732
+ signInWithPreparedOpenKey(pending, authorizeFn) {
22733
+ if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
22734
+ throw new Error("Prepared OpenKey sign-in is unavailable");
22735
+ }
22736
+ if (this.consumedOpenKeyPendings.has(pending)) {
22737
+ throw new Error("Prepared OpenKey sign-in was already used");
22738
+ }
22739
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
22740
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
22741
+ }
22742
+ this.consumedOpenKeyPendings.add(pending);
22743
+ const authorization = authorizeFn(pending.request);
22744
+ void authorization.catch(() => void 0);
22745
+ return this.signInWith(() => {
22746
+ if (this._address !== pending.address || this._chainId !== pending.chainId) {
22747
+ throw new Error("OpenKey account changed after sign-in preparation");
22748
+ }
22749
+ return this.auth.completeOpenKeySignIn(pending, authorization);
22750
+ });
22751
+ }
22584
22752
  async signInWith(performSignIn) {
22585
22753
  refuseCaveated(this.auth?.capabilityRequest?.manifests);
22586
22754
  const epoch = this.sessionEpoch;
@@ -22627,6 +22795,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22627
22795
  this._hooks = void 0;
22628
22796
  this._vault = void 0;
22629
22797
  this._encryption = void 0;
22798
+ this._namedKeyClient = void 0;
22799
+ this._keysUnavailable = [];
22630
22800
  this._baseVaults.clear();
22631
22801
  this._baseSecrets.clear();
22632
22802
  this._secrets.clear();
@@ -22679,6 +22849,26 @@ var _TinyCloudNode = class _TinyCloudNode {
22679
22849
  this.assertSessionEpoch(epoch, "Sign-in");
22680
22850
  await this.ensureRequestedEncryptionNetworks();
22681
22851
  this.assertSessionEpoch(epoch, "Sign-in");
22852
+ this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
22853
+ if (this._keysUnavailable.length > 0) {
22854
+ this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
22855
+ }
22856
+ if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
22857
+ for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
22858
+ for (const declaration of manifest.keys ?? []) {
22859
+ if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
22860
+ try {
22861
+ await this.keys.ensureKey(declaration.path);
22862
+ } catch (error) {
22863
+ this._keysUnavailable.push(declaration.path);
22864
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
22865
+ this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
22866
+ }
22867
+ this.assertSessionEpoch(epoch, "Sign-in");
22868
+ }
22869
+ }
22870
+ }
22871
+ }
22682
22872
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
22683
22873
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
22684
22874
  }
@@ -23466,7 +23656,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23466
23656
  const networkIds = /* @__PURE__ */ new Set();
23467
23657
  for (const resource of request.resources) {
23468
23658
  if (resource.service === ENCRYPTION_PERMISSION_SERVICE2 && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
23469
- networkIds.add(resource.path);
23659
+ const parsed = resource.path.includes("/") ? parseEncryptionResource2(resource.path) : { ...parseNetworkId2(resource.path), kind: "network" };
23660
+ if (parsed.kind === "network") networkIds.add(resource.path);
23470
23661
  }
23471
23662
  }
23472
23663
  return [...networkIds];
@@ -24016,6 +24207,8 @@ var _TinyCloudNode = class _TinyCloudNode {
24016
24207
  this._hooks = stagedGraph.hooks;
24017
24208
  this._vault = stagedGraph.vault;
24018
24209
  this._encryption = stagedGraph.encryption;
24210
+ this._namedKeyClient = void 0;
24211
+ this._keysUnavailable = [];
24019
24212
  this._capabilityRegistry = stagedGraph.capabilityRegistry;
24020
24213
  this._keyProvider = stagedGraph.keyProvider;
24021
24214
  this._sharingService = stagedGraph.sharingService;
@@ -24197,6 +24390,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24197
24390
  invoke: graph.invoke,
24198
24391
  invokeAny: graph.invokeAny,
24199
24392
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
24393
+ sessionInvocationSigner: this.sessionInvocationSignerFor(input.serviceSession),
24200
24394
  fetch: graph.fetch
24201
24395
  });
24202
24396
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -24412,6 +24606,31 @@ var _TinyCloudNode = class _TinyCloudNode {
24412
24606
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
24413
24607
  return signBytesWithJwk(bytes, session.jwk);
24414
24608
  }
24609
+ /**
24610
+ * Signs proofless invocations with `session`'s own did:key, for the node's
24611
+ * issued-delegation list and revocation by the delegator. The audience is
24612
+ * this Node's DID; each invocation lives 60 seconds.
24613
+ */
24614
+ sessionInvocationSignerFor(session) {
24615
+ return {
24616
+ did: () => session.verificationMethod.split("#", 1)[0],
24617
+ sign: async (attenuation) => {
24618
+ const now = Math.floor(Date.now() / 1e3);
24619
+ const invocation = await signCompactUcanAuthorization({
24620
+ issuerDid: session.verificationMethod.split("#", 1)[0],
24621
+ audienceDid: await this.fetchNodeId(),
24622
+ attenuation,
24623
+ facts: [{}],
24624
+ proofs: [],
24625
+ notBefore: now,
24626
+ expiresAt: now + 60,
24627
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
24628
+ sign: (bytes) => signBytesWithJwk(bytes, session.jwk)
24629
+ });
24630
+ return invocation.authorization;
24631
+ }
24632
+ };
24633
+ }
24415
24634
  /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
24416
24635
  async createUnifiedOwnerRoot(input) {
24417
24636
  const ownerDid = this.credentialHolderDid;
@@ -24946,7 +25165,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24946
25165
  graph.assertActive();
24947
25166
  const signed2 = await this.signRawNetworkAuthorization({
24948
25167
  targetNode: input.targetNode,
24949
- networkId: input.networkId,
25168
+ networkId: input.body.keyPath === void 0 ? input.networkId : `${input.networkId}/${input.body.keyPath}`,
24950
25169
  action: DECRYPT_ACTION2,
24951
25170
  facts: input.facts
24952
25171
  }, { graph, session });
@@ -25119,6 +25338,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25119
25338
  invoke: graph.invoke,
25120
25339
  invokeAny: graph.invokeAny,
25121
25340
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
25341
+ sessionInvocationSigner: this.sessionInvocationSignerFor(serviceSession),
25122
25342
  fetch: graph.fetch
25123
25343
  });
25124
25344
  this._spaceService = new SpaceService({
@@ -25369,7 +25589,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25369
25589
  * Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
25370
25590
  * `POST /revoke`, and return its Authorization header value. The node
25371
25591
  * accepts it when the owner delegated `cid` or owns a space its abilities
25372
- * target, whichever key issued it. Valid for 60 seconds. Requires the
25592
+ * target, whichever key issued it. Valid for 10 minutes. Requires the
25373
25593
  * owner wallet signer.
25374
25594
  */
25375
25595
  async signOwnerRevocation(cid) {
@@ -25385,7 +25605,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25385
25605
  chainId: session.chainId,
25386
25606
  nonce: generateNonce(),
25387
25607
  issuedAt: issuedAt.toISOString(),
25388
- expirationTime: new Date(issuedAt.getTime() + 6e4).toISOString()
25608
+ expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
25389
25609
  }).prepareMessage();
25390
25610
  const signature2 = await this.signer.signMessage(siwe);
25391
25611
  return this.wasmBindings.siweToDelegationHeaders({ siwe, signature: signature2 }).Authorization;
@@ -25612,6 +25832,40 @@ var _TinyCloudNode = class _TinyCloudNode {
25612
25832
  get encryption() {
25613
25833
  return this.getEncryptionService();
25614
25834
  }
25835
+ /** Named keys bound to the current session. */
25836
+ get keys() {
25837
+ return new Proxy({}, {
25838
+ get: (_target, property) => {
25839
+ if (property === "refreshNodeInfo") {
25840
+ return () => {
25841
+ void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
25842
+ };
25843
+ }
25844
+ if (typeof property !== "string" || ![
25845
+ "getPublicKey",
25846
+ "describeKey",
25847
+ "createKey",
25848
+ "blockKey",
25849
+ "unblockKey",
25850
+ "ensureKey",
25851
+ "encrypt",
25852
+ "decrypt",
25853
+ "decryptMany"
25854
+ ].includes(property)) return void 0;
25855
+ return (...args) => {
25856
+ this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.js").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
25857
+ return this._namedKeyClient.then((client) => {
25858
+ const method = client[property];
25859
+ return method.apply(client, args);
25860
+ });
25861
+ };
25862
+ }
25863
+ });
25864
+ }
25865
+ /** Manifest key paths unavailable during the latest `signIn()` attempt. */
25866
+ get keysUnavailable() {
25867
+ return this._keysUnavailable;
25868
+ }
25615
25869
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
25616
25870
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
25617
25871
  return this.fetchEncryptionNetworkAt(
@@ -25868,7 +26122,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25868
26122
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
25869
26123
  return false;
25870
26124
  }
25871
- const expanded = this.expandPermissionEntries(permissions);
26125
+ const expanded = expandRuntimePermissionEntries(permissions, pkhDid2(session.address, session.chainId));
25872
26126
  if (this.sessionCoversPermissionEntries(session, expanded)) {
25873
26127
  return true;
25874
26128
  }
@@ -25888,7 +26142,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25888
26142
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
25889
26143
  return [];
25890
26144
  }
25891
- const expanded = this.expandPermissionEntries(permissions);
26145
+ const expanded = expandRuntimePermissionEntries(permissions, pkhDid2(session.address, session.chainId));
25892
26146
  return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
25893
26147
  (grant) => grant.delegation
25894
26148
  );
@@ -26063,7 +26317,28 @@ var _TinyCloudNode = class _TinyCloudNode {
26063
26317
  throw new SessionExpiredError(sessionExpiry);
26064
26318
  }
26065
26319
  }
26066
- const expanded = this.expandPermissionEntries(permissions);
26320
+ const expanded = expandRuntimePermissionEntries(permissions, pkhDid2(session.address, session.chainId));
26321
+ const bindsShareKey = expanded.some((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.actions.includes("tinycloud.encryption/key.create"));
26322
+ const captured = bindsShareKey ? { ...session, jwk: { ...options?.expectedSessionKey ?? session.jwk } } : session;
26323
+ const assertCapturedKey = bindsShareKey ? () => {
26324
+ const live = session.jwk;
26325
+ const key2 = captured.jwk;
26326
+ 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 !== key2.kty || live?.crv !== key2.crv || live?.x !== key2.x || live?.y !== key2.y || live?.d !== key2.d) {
26327
+ throw new Error("Runtime permission request cancelled because the session key changed.");
26328
+ }
26329
+ } : void 0;
26330
+ const sessionKeyDid = bindsShareKey ? this.sessionDid : void 0;
26331
+ if (bindsShareKey) {
26332
+ const key2 = captured.jwk;
26333
+ if (key2.kty !== "OKP" || key2.crv !== "Ed25519" || typeof key2.x !== "string") {
26334
+ throw new Error("Runtime permission session key does not match its signed identity.");
26335
+ }
26336
+ const keyDid = didKeyFromEd25519PublicKey(base64UrlDecode(key2.x));
26337
+ if (!didPrincipalMatches2(keyDid, sessionKeyDid) || !didPrincipalMatches2(keyDid, captured.verificationMethod) || !didPrincipalMatches2(keyDid, new SiweMessage3(session.siwe).uri)) {
26338
+ throw new Error("Runtime permission session key does not match its signed identity.");
26339
+ }
26340
+ assertCapturedKey();
26341
+ }
26067
26342
  if (this.sessionCoversPermissionEntries(session, expanded)) {
26068
26343
  return [];
26069
26344
  }
@@ -26113,13 +26388,13 @@ var _TinyCloudNode = class _TinyCloudNode {
26113
26388
  return this.wasmBindings.prepareSession({
26114
26389
  abilities,
26115
26390
  ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
26116
- address: this.wasmBindings.ensureEip55(session.address),
26117
- chainId: session.chainId,
26391
+ address: this.wasmBindings.ensureEip55(captured.address),
26392
+ chainId: captured.chainId,
26118
26393
  domain: this.siweDomain,
26119
26394
  issuedAt: now.toISOString(),
26120
26395
  expirationTime: expiresAt.toISOString(),
26121
26396
  spaceId,
26122
- jwk: session.jwk
26397
+ jwk: captured.jwk
26123
26398
  });
26124
26399
  } catch (error) {
26125
26400
  if (rawForDelegation.some((entry) => this.isRawRevocationPermissionEntry(entry)) && /invalid ReCap resource URI/i.test(error instanceof Error ? error.message : String(error))) {
@@ -26131,8 +26406,13 @@ var _TinyCloudNode = class _TinyCloudNode {
26131
26406
  throw error;
26132
26407
  }
26133
26408
  })();
26409
+ if (bindsShareKey && !didPrincipalMatches2(new SiweMessage3(prepared.siwe).uri, sessionKeyDid)) {
26410
+ throw new Error("Prepared runtime permission targets a different session key.");
26411
+ }
26412
+ assertCapturedKey?.();
26134
26413
  const signature2 = await this.signer.signMessage(prepared.siwe);
26135
26414
  graph.assertActive();
26415
+ assertCapturedKey?.();
26136
26416
  const delegatedSession = this.wasmBindings.completeSessionSetup({
26137
26417
  ...prepared,
26138
26418
  signature: signature2
@@ -26161,18 +26441,19 @@ var _TinyCloudNode = class _TinyCloudNode {
26161
26441
  delegatedSession,
26162
26442
  delegatedEntries,
26163
26443
  spaceId,
26164
- session,
26444
+ captured,
26165
26445
  expiresAt,
26166
26446
  { siwe: prepared.siwe, signature: signature2 }
26167
26447
  );
26168
26448
  graph.assertActive();
26449
+ assertCapturedKey?.();
26169
26450
  this.runtimePermissionGrants.push({
26170
26451
  session: {
26171
26452
  delegationHeader: delegatedSession.delegationHeader,
26172
26453
  delegationCid: delegatedSession.delegationCid,
26173
26454
  spaceId,
26174
- verificationMethod: session.verificationMethod,
26175
- jwk: session.jwk
26455
+ verificationMethod: captured.verificationMethod,
26456
+ jwk: captured.jwk
26176
26457
  },
26177
26458
  delegation,
26178
26459
  operations: this.permissionOperations(delegatedEntries, spaceId),
@@ -26501,7 +26782,19 @@ var _TinyCloudNode = class _TinyCloudNode {
26501
26782
  "delegateTo requires a non-empty permissions array"
26502
26783
  );
26503
26784
  }
26504
- const expandedEntries = this.expandPermissionEntries(permissions);
26785
+ const expandedEntries = expandDelegationPermissionEntries(permissions);
26786
+ const namedKeyEntries = expandedEntries.filter((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.path.includes("/"));
26787
+ if (namedKeyEntries.length > 0) {
26788
+ if (expandedEntries.some((entry) => entry.service === ENCRYPTION_PERMISSION_SERVICE2 && !entry.path.includes("/"))) {
26789
+ throw new ManifestValidationError2("named-key delegation cannot include bare-network permissions");
26790
+ }
26791
+ if (options?.forceWalletSign) {
26792
+ throw new ManifestValidationError2("named-key delegation requires a signed session grant");
26793
+ }
26794
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
26795
+ const { subset, missing } = this.signedCapabilitySubset(namedKeyEntries, granted);
26796
+ if (!subset) throw new PermissionNotInManifestError(missing, granted);
26797
+ }
26505
26798
  const now = /* @__PURE__ */ new Date();
26506
26799
  const expiryMs = resolveExpiryMs(options?.expiry);
26507
26800
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -28051,7 +28344,7 @@ var FileSessionStorage = class {
28051
28344
  };
28052
28345
 
28053
28346
  // src/authorization/openKeyBridge.ts
28054
- function wireOpenKeyAuthorize(openkey) {
28347
+ function wireOpenKeyAuthorize(openkey, opts) {
28055
28348
  return async (input) => {
28056
28349
  const result = await openkey.authorizeTinyCloud({
28057
28350
  protocolVersion: 1,
@@ -28062,7 +28355,7 @@ function wireOpenKeyAuthorize(openkey) {
28062
28355
  // Forward the presentation envelope verbatim — the OpenKey SDK
28063
28356
  // validates and size-bounds it before use.
28064
28357
  presentation: input.presentation
28065
- });
28358
+ }, opts);
28066
28359
  if (result.protocolVersion !== 1) {
28067
28360
  throw new Error(
28068
28361
  `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
@@ -28104,7 +28397,7 @@ import {
28104
28397
  CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError3,
28105
28398
  PermissionNotInManifestError as PermissionNotInManifestError2,
28106
28399
  SessionExpiredError as SessionExpiredError2,
28107
- ManifestValidationError,
28400
+ ManifestValidationError as ManifestValidationError3,
28108
28401
  composeManifestRequest as composeManifestRequest2,
28109
28402
  resolveManifest as resolveManifest2,
28110
28403
  validateManifest,
@@ -29249,7 +29542,8 @@ var SqliteReplicaHandle = class {
29249
29542
  options.signal.addEventListener("abort", onAbort, { once: true });
29250
29543
  const engine = new (__privateGet(this, _replica)).Replica({
29251
29544
  store: __privateGet(this, _store),
29252
- transportFor
29545
+ transportFor,
29546
+ ...this.spec.contentFetch === void 0 ? {} : { contentFetch: this.spec.contentFetch }
29253
29547
  });
29254
29548
  const running = engine.sync({ signal: abort.signal });
29255
29549
  const registration = { promise: running, abort: () => abort.abort() };
@@ -30768,7 +31062,7 @@ export {
30768
31062
  InvalidRestoredSessionError,
30769
31063
  KVService4 as KVService,
30770
31064
  MAX_SECRET_SCOPE_LENGTH,
30771
- ManifestValidationError,
31065
+ ManifestValidationError3 as ManifestValidationError,
30772
31066
  MemorySessionStorage,
30773
31067
  NETWORK_NAME_PATTERN,
30774
31068
  NetworkIdError,
@@ -30865,6 +31159,7 @@ export {
30865
31159
  hexDecode,
30866
31160
  hexEncode,
30867
31161
  installTinyCloudDebugGlobals,
31162
+ isAppKeyHostBound,
30868
31163
  isCapabilitySubset4 as isCapabilitySubset,
30869
31164
  isEvmAddress,
30870
31165
  isNetworkId,