mindoodb 0.0.3 → 0.0.5

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 (214) hide show
  1. package/README-server.md +1030 -0
  2. package/README.md +14 -26
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +27 -0
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  5. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +270 -121
  6. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  7. package/dist/appendonlystores/network/HttpTransport.d.ts +15 -0
  8. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  9. package/dist/appendonlystores/network/HttpTransport.js +235 -10
  10. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  11. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +5 -0
  12. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  13. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +40 -4
  14. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  15. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts +10 -3
  16. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts.map +1 -1
  17. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js +138 -13
  18. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js.map +1 -1
  19. package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts +21 -0
  20. package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts.map +1 -0
  21. package/dist/browser/cache/IndexedDBLocalCacheStore.js +106 -0
  22. package/dist/browser/cache/IndexedDBLocalCacheStore.js.map +1 -0
  23. package/dist/browser/index.d.ts +3 -0
  24. package/dist/browser/index.d.ts.map +1 -1
  25. package/dist/browser/index.js +9 -1
  26. package/dist/browser/index.js.map +1 -1
  27. package/dist/core/BaseMindooDB.d.ts +108 -13
  28. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  29. package/dist/core/BaseMindooDB.js +1268 -292
  30. package/dist/core/BaseMindooDB.js.map +1 -1
  31. package/dist/core/BaseMindooTenant.d.ts +31 -1
  32. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  33. package/dist/core/BaseMindooTenant.js +212 -129
  34. package/dist/core/BaseMindooTenant.js.map +1 -1
  35. package/dist/core/BaseMindooTenantDirectory.d.ts +13 -10
  36. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  37. package/dist/core/BaseMindooTenantDirectory.js +134 -27
  38. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  39. package/dist/core/BaseMindooTenantFactory.d.ts +5 -1
  40. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  41. package/dist/core/BaseMindooTenantFactory.js +46 -69
  42. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  43. package/dist/core/MindooDBServerAdmin.d.ts +241 -0
  44. package/dist/core/MindooDBServerAdmin.d.ts.map +1 -0
  45. package/dist/core/MindooDBServerAdmin.js +401 -0
  46. package/dist/core/MindooDBServerAdmin.js.map +1 -0
  47. package/dist/core/appendonlystores/AttachmentReadPlanner.d.ts +22 -0
  48. package/dist/core/appendonlystores/AttachmentReadPlanner.d.ts.map +1 -0
  49. package/dist/core/appendonlystores/AttachmentReadPlanner.js +124 -0
  50. package/dist/core/appendonlystores/AttachmentReadPlanner.js.map +1 -0
  51. package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts +10 -4
  52. package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts.map +1 -1
  53. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js +44 -9
  54. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js.map +1 -1
  55. package/dist/core/appendonlystores/MaterializationPlanner.d.ts +84 -0
  56. package/dist/core/appendonlystores/MaterializationPlanner.d.ts.map +1 -0
  57. package/dist/core/appendonlystores/MaterializationPlanner.js +326 -0
  58. package/dist/core/appendonlystores/MaterializationPlanner.js.map +1 -0
  59. package/dist/core/appendonlystores/network/NetworkTransport.d.ts +33 -0
  60. package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -1
  61. package/dist/core/appendonlystores/network/types.d.ts +35 -0
  62. package/dist/core/appendonlystores/network/types.d.ts.map +1 -1
  63. package/dist/core/appendonlystores/network/types.js +2 -0
  64. package/dist/core/appendonlystores/network/types.js.map +1 -1
  65. package/dist/core/appendonlystores/types.d.ts +174 -3
  66. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  67. package/dist/core/cache/CacheManager.d.ts +75 -0
  68. package/dist/core/cache/CacheManager.d.ts.map +1 -0
  69. package/dist/core/cache/CacheManager.js +101 -0
  70. package/dist/core/cache/CacheManager.js.map +1 -0
  71. package/dist/core/cache/EncryptedLocalCacheStore.d.ts +41 -0
  72. package/dist/core/cache/EncryptedLocalCacheStore.d.ts.map +1 -0
  73. package/dist/core/cache/EncryptedLocalCacheStore.js +120 -0
  74. package/dist/core/cache/EncryptedLocalCacheStore.js.map +1 -0
  75. package/dist/core/cache/LocalCacheStore.d.ts +50 -0
  76. package/dist/core/cache/LocalCacheStore.d.ts.map +1 -0
  77. package/dist/core/cache/LocalCacheStore.js +39 -0
  78. package/dist/core/cache/LocalCacheStore.js.map +1 -0
  79. package/dist/core/crypto/privateKeyEncryption.d.ts +5 -0
  80. package/dist/core/crypto/privateKeyEncryption.d.ts.map +1 -0
  81. package/dist/core/crypto/privateKeyEncryption.js +87 -0
  82. package/dist/core/crypto/privateKeyEncryption.js.map +1 -0
  83. package/dist/core/index.d.ts +13 -6
  84. package/dist/core/index.d.ts.map +1 -1
  85. package/dist/core/index.js +13 -1
  86. package/dist/core/index.js.map +1 -1
  87. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +4 -1
  88. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
  89. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +39 -11
  90. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
  91. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts +1 -0
  92. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts.map +1 -1
  93. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js +3 -0
  94. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js.map +1 -1
  95. package/dist/core/indexing/virtualviews/VirtualView.d.ts +43 -0
  96. package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
  97. package/dist/core/indexing/virtualviews/VirtualView.js +173 -0
  98. package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
  99. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +7 -0
  100. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
  101. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +28 -0
  102. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
  103. package/dist/core/keys/KeyBag.d.ts +38 -14
  104. package/dist/core/keys/KeyBag.d.ts.map +1 -1
  105. package/dist/core/keys/KeyBag.js +102 -76
  106. package/dist/core/keys/KeyBag.js.map +1 -1
  107. package/dist/core/keys/KeyContext.d.ts +3 -3
  108. package/dist/core/keys/KeyContext.d.ts.map +1 -1
  109. package/dist/core/keys/KeyContext.js +10 -4
  110. package/dist/core/keys/KeyContext.js.map +1 -1
  111. package/dist/core/types.d.ts +341 -22
  112. package/dist/core/types.d.ts.map +1 -1
  113. package/dist/core/types.js +7 -1
  114. package/dist/core/types.js.map +1 -1
  115. package/dist/core/utils/idGeneration.d.ts.map +1 -1
  116. package/dist/core/utils/idGeneration.js +37 -3
  117. package/dist/core/utils/idGeneration.js.map +1 -1
  118. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts +31 -7
  119. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -1
  120. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +139 -17
  121. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -1
  122. package/dist/node/attachments/writeAttachmentToFile.d.ts +15 -0
  123. package/dist/node/attachments/writeAttachmentToFile.d.ts.map +1 -0
  124. package/dist/node/attachments/writeAttachmentToFile.js +73 -0
  125. package/dist/node/attachments/writeAttachmentToFile.js.map +1 -0
  126. package/dist/node/cache/FileSystemLocalCacheStore.d.ts +17 -0
  127. package/dist/node/cache/FileSystemLocalCacheStore.d.ts.map +1 -0
  128. package/dist/node/cache/FileSystemLocalCacheStore.js +128 -0
  129. package/dist/node/cache/FileSystemLocalCacheStore.js.map +1 -0
  130. package/dist/node/cli/change-identity-password.d.ts +7 -0
  131. package/dist/node/cli/change-identity-password.d.ts.map +1 -0
  132. package/dist/node/cli/change-identity-password.js +67 -0
  133. package/dist/node/cli/change-identity-password.js.map +1 -0
  134. package/dist/node/cli/cli-utils.d.ts +16 -0
  135. package/dist/node/cli/cli-utils.d.ts.map +1 -0
  136. package/dist/node/cli/cli-utils.js +113 -0
  137. package/dist/node/cli/cli-utils.js.map +1 -0
  138. package/dist/node/cli/identity-export-public.d.ts +4 -0
  139. package/dist/node/cli/identity-export-public.d.ts.map +1 -0
  140. package/dist/node/cli/identity-export-public.js +55 -0
  141. package/dist/node/cli/identity-export-public.js.map +1 -0
  142. package/dist/node/cli/identity-info.d.ts +4 -0
  143. package/dist/node/cli/identity-info.d.ts.map +1 -0
  144. package/dist/node/cli/identity-info.js +57 -0
  145. package/dist/node/cli/identity-info.js.map +1 -0
  146. package/dist/node/index.d.ts +1 -0
  147. package/dist/node/index.d.ts.map +1 -1
  148. package/dist/node/index.js +4 -1
  149. package/dist/node/index.js.map +1 -1
  150. package/dist/node/server/CapabilityMatcher.d.ts +45 -0
  151. package/dist/node/server/CapabilityMatcher.d.ts.map +1 -0
  152. package/dist/node/server/CapabilityMatcher.js +117 -0
  153. package/dist/node/server/CapabilityMatcher.js.map +1 -0
  154. package/dist/node/server/MindooDBServer.d.ts +94 -0
  155. package/dist/node/server/MindooDBServer.d.ts.map +1 -0
  156. package/dist/node/server/MindooDBServer.js +1256 -0
  157. package/dist/node/server/MindooDBServer.js.map +1 -0
  158. package/dist/node/server/ServerSync.d.ts +41 -0
  159. package/dist/node/server/ServerSync.d.ts.map +1 -0
  160. package/dist/node/server/ServerSync.js +187 -0
  161. package/dist/node/server/ServerSync.js.map +1 -0
  162. package/dist/node/server/StoreFactory.d.ts +45 -0
  163. package/dist/node/server/StoreFactory.d.ts.map +1 -0
  164. package/dist/node/server/StoreFactory.js +89 -0
  165. package/dist/node/server/StoreFactory.js.map +1 -0
  166. package/dist/node/server/SystemAdminAuth.d.ts +65 -0
  167. package/dist/node/server/SystemAdminAuth.d.ts.map +1 -0
  168. package/dist/node/server/SystemAdminAuth.js +200 -0
  169. package/dist/node/server/SystemAdminAuth.js.map +1 -0
  170. package/dist/node/server/SystemIpAllowlist.d.ts +21 -0
  171. package/dist/node/server/SystemIpAllowlist.d.ts.map +1 -0
  172. package/dist/node/server/SystemIpAllowlist.js +210 -0
  173. package/dist/node/server/SystemIpAllowlist.js.map +1 -0
  174. package/dist/node/server/TenantManager.d.ts +100 -0
  175. package/dist/node/server/TenantManager.d.ts.map +1 -0
  176. package/dist/node/server/TenantManager.js +615 -0
  177. package/dist/node/server/TenantManager.js.map +1 -0
  178. package/dist/node/server/add-to-network.d.ts +20 -0
  179. package/dist/node/server/add-to-network.d.ts.map +1 -0
  180. package/dist/node/server/add-to-network.js +275 -0
  181. package/dist/node/server/add-to-network.js.map +1 -0
  182. package/dist/node/server/config.d.ts +51 -0
  183. package/dist/node/server/config.d.ts.map +1 -0
  184. package/dist/node/server/config.js +218 -0
  185. package/dist/node/server/config.js.map +1 -0
  186. package/dist/node/server/resolveServerPassword.d.ts +7 -0
  187. package/dist/node/server/resolveServerPassword.d.ts.map +1 -0
  188. package/dist/node/server/resolveServerPassword.js +25 -0
  189. package/dist/node/server/resolveServerPassword.js.map +1 -0
  190. package/dist/node/server/server.d.ts +22 -0
  191. package/dist/node/server/server.d.ts.map +1 -0
  192. package/dist/node/server/server.js +225 -0
  193. package/dist/node/server/server.js.map +1 -0
  194. package/dist/node/server/serverinit.d.ts +21 -0
  195. package/dist/node/server/serverinit.d.ts.map +1 -0
  196. package/dist/node/server/serverinit.js +317 -0
  197. package/dist/node/server/serverinit.js.map +1 -0
  198. package/dist/node/server/types.d.ts +167 -0
  199. package/dist/node/server/types.d.ts.map +1 -0
  200. package/dist/node/server/types.js +24 -0
  201. package/dist/node/server/types.js.map +1 -0
  202. package/dist/node/server/validation.d.ts +47 -0
  203. package/dist/node/server/validation.d.ts.map +1 -0
  204. package/dist/node/server/validation.js +114 -0
  205. package/dist/node/server/validation.js.map +1 -0
  206. package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts +48 -0
  207. package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts.map +1 -0
  208. package/dist/reactnative/cache/MMKVLocalCacheStore.js +113 -0
  209. package/dist/reactnative/cache/MMKVLocalCacheStore.js.map +1 -0
  210. package/dist/reactnative/index.d.ts +1 -0
  211. package/dist/reactnative/index.d.ts.map +1 -1
  212. package/dist/reactnative/index.js +4 -1
  213. package/dist/reactnative/index.js.map +1 -1
  214. package/package.json +15 -3
@@ -5,9 +5,13 @@ const types_1 = require("./types");
5
5
  const BaseMindooDB_1 = require("./BaseMindooDB");
6
6
  const BaseMindooTenantDirectory_1 = require("./BaseMindooTenantDirectory");
7
7
  const MindooDocSigner_1 = require("./crypto/MindooDocSigner");
8
+ const RSAEncryption_1 = require("./crypto/RSAEncryption");
9
+ const privateKeyEncryption_1 = require("./crypto/privateKeyEncryption");
8
10
  const errors_1 = require("./errors");
9
11
  const logging_1 = require("./logging");
10
12
  const MindooURI_1 = require("./uri/MindooURI");
13
+ const EncryptedLocalCacheStore_1 = require("./cache/EncryptedLocalCacheStore");
14
+ const CacheManager_1 = require("./cache/CacheManager");
11
15
  /**
12
16
  * BaseMindooTenant is a platform-agnostic implementation of MindooTenant
13
17
  * that works in both browser and server environments.
@@ -23,9 +27,12 @@ const MindooURI_1 = require("./uri/MindooURI");
23
27
  * - PBKDF2 key derivation (for password-based key derivation)
24
28
  */
25
29
  class BaseMindooTenant {
26
- constructor(factory, tenantId, administrationPublicKey, administrationEncryptionPublicKey, currentUser, currentUserPassword, keyBag, storeFactory, cryptoAdapter, logger, additionalTrustedKeys) {
30
+ constructor(factory, tenantId, administrationPublicKey, administrationEncryptionPublicKey, currentUser, currentUserPassword, keyBag, storeFactory, cryptoAdapter, logger, additionalTrustedKeys, localCacheStore) {
27
31
  this.databaseCache = new Map();
28
32
  this.directoryCache = null;
33
+ this.remoteStoreCache = new Map();
34
+ // Local cache support
35
+ this.cacheManager = null;
29
36
  this.factory = factory;
30
37
  this.tenantId = tenantId;
31
38
  this.administrationPublicKey = administrationPublicKey;
@@ -47,6 +54,11 @@ class BaseMindooTenant {
47
54
  this.logger =
48
55
  logger ||
49
56
  new logging_1.MindooLogger((0, logging_1.getDefaultLogLevel)(), `Tenant:${tenantId}`, true);
57
+ // Set up cache if a local cache store is provided
58
+ if (localCacheStore) {
59
+ const encryptedStore = new EncryptedLocalCacheStore_1.EncryptedLocalCacheStore(localCacheStore, currentUserPassword, this.cryptoAdapter, this.logger.createChild("EncryptedCache"));
60
+ this.cacheManager = new CacheManager_1.CacheManager(encryptedStore, undefined, this.logger.createChild("CacheManager"));
61
+ }
50
62
  }
51
63
  /**
52
64
  * Initialize the tenant.
@@ -57,6 +69,22 @@ class BaseMindooTenant {
57
69
  const keyCount = (await this.keyBag.listKeys()).length;
58
70
  this.logger.info(`Tenant initialized with ${keyCount} keys in KeyBag`);
59
71
  }
72
+ /**
73
+ * Get the CacheManager for this tenant (null if caching is disabled).
74
+ */
75
+ getCacheManager() {
76
+ return this.cacheManager;
77
+ }
78
+ /**
79
+ * Flush and dispose the cache manager.
80
+ * Should be called when the tenant is being closed.
81
+ */
82
+ async disposeCacheManager() {
83
+ if (this.cacheManager) {
84
+ await this.cacheManager.dispose();
85
+ this.cacheManager = null;
86
+ }
87
+ }
60
88
  getCryptoAdapter() {
61
89
  return this.cryptoAdapter;
62
90
  }
@@ -100,9 +128,9 @@ class BaseMindooTenant {
100
128
  }
101
129
  else {
102
130
  this.logger.debug(`Resolving tenant encryption key from KeyBag`);
103
- const tenantKey = await this.keyBag.get("tenant", this.tenantId);
131
+ const tenantKey = await this.keyBag.get("doc", this.tenantId, types_1.DEFAULT_TENANT_KEY_ID);
104
132
  if (!tenantKey) {
105
- throw new errors_1.SymmetricKeyNotFoundError(`tenant:${this.tenantId}`);
133
+ throw new errors_1.SymmetricKeyNotFoundError(`doc:${this.tenantId}:${types_1.DEFAULT_TENANT_KEY_ID}`);
106
134
  }
107
135
  symmetricKey = tenantKey;
108
136
  this.decryptedTenantKeyCache = symmetricKey;
@@ -112,7 +140,7 @@ class BaseMindooTenant {
112
140
  else {
113
141
  this.logger.debug(`Getting named key from KeyBag: ${decryptionKeyId}`);
114
142
  // Get the decrypted key from KeyBag
115
- const decryptedKey = await this.keyBag.get("doc", decryptionKeyId);
143
+ const decryptedKey = await this.keyBag.get("doc", this.tenantId, decryptionKeyId);
116
144
  if (!decryptedKey) {
117
145
  throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
118
146
  }
@@ -210,9 +238,9 @@ class BaseMindooTenant {
210
238
  symmetricKey = this.decryptedTenantKeyCache;
211
239
  }
212
240
  else {
213
- const tenantKey = await this.keyBag.get("tenant", this.tenantId);
241
+ const tenantKey = await this.keyBag.get("doc", this.tenantId, types_1.DEFAULT_TENANT_KEY_ID);
214
242
  if (!tenantKey) {
215
- throw new errors_1.SymmetricKeyNotFoundError(`tenant:${this.tenantId}`);
243
+ throw new errors_1.SymmetricKeyNotFoundError(`doc:${this.tenantId}:${types_1.DEFAULT_TENANT_KEY_ID}`);
216
244
  }
217
245
  symmetricKey = tenantKey;
218
246
  this.decryptedTenantKeyCache = symmetricKey;
@@ -220,7 +248,7 @@ class BaseMindooTenant {
220
248
  }
221
249
  else {
222
250
  // Get the decrypted key from KeyBag
223
- const decryptedKey = await this.keyBag.get("doc", decryptionKeyId);
251
+ const decryptedKey = await this.keyBag.get("doc", this.tenantId, decryptionKeyId);
224
252
  if (!decryptedKey) {
225
253
  throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
226
254
  }
@@ -326,12 +354,31 @@ class BaseMindooTenant {
326
354
  }
327
355
  return this.directoryCache;
328
356
  }
357
+ async assertCurrentUserCanOpenDB(id) {
358
+ if (id === "directory") {
359
+ return;
360
+ }
361
+ const currentUser = await this.getCurrentUserId();
362
+ if (currentUser.userSigningPublicKey === this.getAdministrationPublicKey()) {
363
+ return;
364
+ }
365
+ const directory = await this.openDirectory();
366
+ const registeredUser = await directory.getUserPublicKeys(currentUser.username);
367
+ const hasMatchingGrant = registeredUser?.signingPublicKey === currentUser.userSigningPublicKey &&
368
+ registeredUser?.encryptionPublicKey === currentUser.userEncryptionPublicKey;
369
+ if (hasMatchingGrant) {
370
+ return;
371
+ }
372
+ this.logger.warn(`Denied database open for ungranted user ${currentUser.username} on database ${id}`);
373
+ throw new Error(`User "${currentUser.username}" does not have tenant access yet; the tenant admin must grant access first.`);
374
+ }
329
375
  async openDB(id, options) {
330
376
  // Enforce admin-only mode for directory database - this is a security invariant
331
377
  // The directory database must only accept entries signed by the admin key
332
378
  const effectiveOptions = id === "directory"
333
379
  ? { ...options, adminOnlyDb: true }
334
380
  : options ?? {};
381
+ await this.assertCurrentUserCanOpenDB(id);
335
382
  // Return cached database if it exists
336
383
  const cached = this.databaseCache.get(id);
337
384
  if (cached) {
@@ -342,11 +389,14 @@ class BaseMindooTenant {
342
389
  return cached;
343
390
  }
344
391
  // Extract store options and DB-specific options
345
- const { adminOnlyDb, attachmentConfig, ...storeOptions } = effectiveOptions;
392
+ const { adminOnlyDb, attachmentConfig, documentCacheConfig, snapshotConfig, performanceCallback, ...storeOptions } = effectiveOptions;
346
393
  // Create the database stores using the factory
347
394
  const { docStore, attachmentStore } = this.storeFactory.createStore(id, storeOptions);
348
395
  const dbLogger = this.logger.createChild("BaseMindooDB");
349
- const db = new BaseMindooDB_1.BaseMindooDB(this, docStore, attachmentStore, attachmentConfig, adminOnlyDb ?? false, dbLogger);
396
+ const db = new BaseMindooDB_1.BaseMindooDB(this, docStore, attachmentStore, attachmentConfig, documentCacheConfig, snapshotConfig, adminOnlyDb ?? false, dbLogger, performanceCallback);
397
+ if (this.cacheManager) {
398
+ db.setCacheManager(this.cacheManager);
399
+ }
350
400
  await db.initialize();
351
401
  // Cache the database for future use
352
402
  this.databaseCache.set(id, db);
@@ -384,12 +434,12 @@ class BaseMindooTenant {
384
434
  };
385
435
  await directory.registerUser(publicUserId, options.adminSigningKey, options.adminPassword);
386
436
  // 2. Export the tenant key encrypted with the share password
387
- const encryptedTenantKey = await this.keyBag.encryptAndExportKey("tenant", this.tenantId, options.sharePassword);
437
+ const encryptedTenantKey = await this.keyBag.encryptAndExportKey("doc", this.tenantId, types_1.DEFAULT_TENANT_KEY_ID, options.sharePassword);
388
438
  if (!encryptedTenantKey) {
389
439
  throw new Error(`Failed to export tenant key for tenant "${this.tenantId}"`);
390
440
  }
391
441
  // 3. Export the $publicinfos key encrypted with the share password
392
- const encryptedPublicInfosKey = await this.keyBag.encryptAndExportKey("doc", types_1.PUBLIC_INFOS_KEY_ID, options.sharePassword);
442
+ const encryptedPublicInfosKey = await this.keyBag.encryptAndExportKey("doc", this.tenantId, types_1.PUBLIC_INFOS_KEY_ID, options.sharePassword);
393
443
  if (!encryptedPublicInfosKey) {
394
444
  throw new Error(`Failed to export $publicinfos key`);
395
445
  }
@@ -415,23 +465,59 @@ class BaseMindooTenant {
415
465
  }
416
466
  /**
417
467
  * Publish (register) this tenant on a MindooDB server.
468
+ *
469
+ * When `systemAdminUser` + `systemAdminPassword` are provided the method
470
+ * performs a challenge/response handshake against `/system/auth/*` to
471
+ * obtain a JWT, then calls `POST /system/tenants/:tenantId`.
418
472
  */
419
473
  async publishToServer(serverUrl, options) {
420
474
  console.log(`[publishToServer] Publishing tenant "${this.tenantId}" to server: ${serverUrl}`);
421
475
  this.logger.info(`Publishing tenant "${this.tenantId}" to server: ${serverUrl}`);
476
+ const baseUrl = serverUrl.replace(/\/$/, "");
422
477
  // Export the $publicinfos key so the server can read the directory DB
423
- const publicInfosKeyBytes = await this.keyBag.get("doc", types_1.PUBLIC_INFOS_KEY_ID);
478
+ const publicInfosKeyBytes = await this.keyBag.get("doc", this.tenantId, types_1.PUBLIC_INFOS_KEY_ID);
424
479
  if (!publicInfosKeyBytes) {
425
480
  throw new Error(`Cannot publish to server: $publicinfos key not found in KeyBag`);
426
481
  }
427
- const publicInfosKeyBase64 = this.uint8ArrayToBase64(publicInfosKeyBytes);
428
- // Build the registration request
482
+ // Build the registration request body
429
483
  const requestBody = {
430
- tenantId: this.tenantId,
431
484
  adminSigningPublicKey: this.administrationPublicKey,
432
485
  adminEncryptionPublicKey: this.administrationEncryptionPublicKey,
433
- publicInfosKey: publicInfosKeyBase64,
434
486
  };
487
+ try {
488
+ const serverInfoResponse = await fetch(`${baseUrl}/.well-known/mindoodb-server-info`, {
489
+ headers: {
490
+ Accept: "application/json",
491
+ },
492
+ });
493
+ let serverInfoBody = null;
494
+ try {
495
+ serverInfoBody = await serverInfoResponse.json();
496
+ }
497
+ catch {
498
+ serverInfoBody = null;
499
+ }
500
+ if (!serverInfoResponse.ok) {
501
+ throw new Error(typeof serverInfoBody?.error === "string"
502
+ ? serverInfoBody.error
503
+ : `Could not read ${baseUrl}/.well-known/mindoodb-server-info (HTTP ${serverInfoResponse.status}).`);
504
+ }
505
+ if (!serverInfoBody
506
+ || typeof serverInfoBody !== "object"
507
+ || Array.isArray(serverInfoBody)
508
+ || typeof serverInfoBody.encryptionPublicKey !== "string") {
509
+ throw new Error("The server returned an invalid .well-known payload.");
510
+ }
511
+ const rsaEncryption = new RSAEncryption_1.RSAEncryption(this.cryptoAdapter);
512
+ requestBody.encryptedPublicInfosKey = this.uint8ArrayToBase64(await rsaEncryption.encrypt(publicInfosKeyBytes, serverInfoBody.encryptionPublicKey));
513
+ }
514
+ catch (error) {
515
+ console.warn("[publishToServer] Falling back to raw publicInfosKey transport:", error);
516
+ requestBody.publicInfosKey = this.uint8ArrayToBase64(publicInfosKeyBytes);
517
+ }
518
+ if (options?.adminUsername) {
519
+ requestBody.adminUsername = options.adminUsername;
520
+ }
435
521
  // Add users if provided
436
522
  if (options?.registerUsers && options.registerUsers.length > 0) {
437
523
  requestBody.users = options.registerUsers.map((u) => ({
@@ -440,15 +526,16 @@ class BaseMindooTenant {
440
526
  encryptionPublicKey: u.userEncryptionPublicKey,
441
527
  }));
442
528
  }
443
- // Build headers
529
+ // Authenticate as system admin via challenge/response
444
530
  const headers = {
445
531
  "Content-Type": "application/json",
446
532
  };
447
- if (options?.adminApiKey) {
448
- headers["x-api-key"] = options.adminApiKey;
533
+ if (options?.systemAdminUser && options?.systemAdminPassword) {
534
+ const token = await this.authenticateAsSystemAdmin(baseUrl, options.systemAdminUser, options.systemAdminPassword);
535
+ headers["Authorization"] = `Bearer ${token}`;
449
536
  }
450
- // POST to the server's admin register-tenant endpoint
451
- const url = `${serverUrl.replace(/\/$/, "")}/admin/register-tenant`;
537
+ // POST to /system/tenants/:tenantId
538
+ const url = `${baseUrl}/system/tenants/${encodeURIComponent(this.tenantId)}`;
452
539
  const response = await fetch(url, {
453
540
  method: "POST",
454
541
  headers,
@@ -461,31 +548,90 @@ class BaseMindooTenant {
461
548
  console.log(`[publishToServer] ✓ Tenant "${this.tenantId}" published to server successfully`);
462
549
  this.logger.info(`Tenant "${this.tenantId}" published to server successfully`);
463
550
  }
551
+ /**
552
+ * Perform system admin challenge/response auth and return a JWT token.
553
+ */
554
+ async authenticateAsSystemAdmin(baseUrl, adminUser, adminPassword) {
555
+ const subtle = this.cryptoAdapter.getSubtle();
556
+ // Decrypt the admin's signing private key
557
+ const signingKeyBuffer = await this.decryptPrivateKey(adminUser.userSigningKeyPair.privateKey, adminPassword, "signing");
558
+ const signingKey = await subtle.importKey("pkcs8", signingKeyBuffer, { name: "Ed25519" }, false, ["sign"]);
559
+ // Step 1: Request challenge from server
560
+ const challengeRes = await fetch(`${baseUrl}/system/auth/challenge`, {
561
+ method: "POST",
562
+ headers: { "Content-Type": "application/json" },
563
+ body: JSON.stringify({
564
+ username: adminUser.username,
565
+ publicsignkey: adminUser.userSigningKeyPair.publicKey,
566
+ }),
567
+ });
568
+ if (!challengeRes.ok) {
569
+ const errorBody = await challengeRes.text();
570
+ throw new Error(`System admin challenge failed (HTTP ${challengeRes.status}): ${errorBody}`);
571
+ }
572
+ const { challenge } = (await challengeRes.json());
573
+ // Step 2: Sign the challenge
574
+ const messageBytes = new TextEncoder().encode(challenge);
575
+ const signatureBuffer = await subtle.sign({ name: "Ed25519" }, signingKey, messageBytes);
576
+ const signatureBase64 = this.uint8ArrayToBase64(new Uint8Array(signatureBuffer));
577
+ // Step 3: Authenticate
578
+ const authRes = await fetch(`${baseUrl}/system/auth/authenticate`, {
579
+ method: "POST",
580
+ headers: { "Content-Type": "application/json" },
581
+ body: JSON.stringify({ challenge, signature: signatureBase64 }),
582
+ });
583
+ if (!authRes.ok) {
584
+ const errorBody = await authRes.text();
585
+ throw new Error(`System admin authentication failed (HTTP ${authRes.status}): ${errorBody}`);
586
+ }
587
+ const result = (await authRes.json());
588
+ if (!result.success || !result.token) {
589
+ throw new Error(`System admin authentication failed: ${result.error || "unknown error"}`);
590
+ }
591
+ return result.token;
592
+ }
464
593
  /**
465
594
  * Create a remote store connected to a MindooDB server, ready for sync.
466
595
  */
467
596
  async connectToServer(serverUrl, dbId) {
468
597
  console.log(`[connectToServer] Connecting to server: ${serverUrl}, db: ${dbId}`);
469
598
  this.logger.info(`Connecting to server: ${serverUrl}, db: ${dbId}`);
470
- // Lazy-import network modules to avoid circular dependencies and keep core lightweight
471
- const { HttpTransport } = await import("../appendonlystores/network/HttpTransport.js");
472
- const { ClientNetworkContentAddressedStore } = await import("../appendonlystores/network/ClientNetworkContentAddressedStore.js");
473
- // Create the HTTP transport
474
- const baseUrl = `${serverUrl.replace(/\/$/, "")}/${this.tenantId}`;
475
- const transport = new HttpTransport({
476
- baseUrl,
477
- tenantId: this.tenantId,
478
- dbId,
479
- }, this.logger.createChild("HttpTransport"));
480
- // Get the current user's decrypted signing key
481
- const signingKey = await this.getDecryptedSigningKey();
482
- // Get the current user's decrypted RSA encryption private key (for decrypting entries)
483
- const decryptedEncryptionKey = await this.getDecryptedEncryptionKey();
484
- // Create the client network store
485
- const store = new ClientNetworkContentAddressedStore(dbId, transport, this.cryptoAdapter, this.currentUser.username, signingKey, decryptedEncryptionKey, this.logger.createChild(`ClientNetworkStore:${dbId}`));
486
- console.log(`[connectToServer] ✓ Connected to server for db "${dbId}"`);
487
- this.logger.info(`Connected to server for db "${dbId}"`);
488
- return store;
599
+ const normalizedServerUrl = serverUrl.replace(/\/$/, "");
600
+ const cacheKey = `${normalizedServerUrl}::${dbId}`;
601
+ const cachedStore = this.remoteStoreCache.get(cacheKey);
602
+ if (cachedStore) {
603
+ this.logger.debug(`Reusing cached remote store for ${normalizedServerUrl}, db: ${dbId}`);
604
+ return cachedStore;
605
+ }
606
+ const remoteStorePromise = (async () => {
607
+ // Lazy-import network modules to avoid circular dependencies and keep core lightweight
608
+ const { HttpTransport } = await import("../appendonlystores/network/HttpTransport.js");
609
+ const { ClientNetworkContentAddressedStore } = await import("../appendonlystores/network/ClientNetworkContentAddressedStore.js");
610
+ // Create the HTTP transport
611
+ const baseUrl = `${normalizedServerUrl}/${this.tenantId}`;
612
+ const transport = new HttpTransport({
613
+ baseUrl,
614
+ tenantId: this.tenantId,
615
+ dbId,
616
+ }, this.logger.createChild("HttpTransport"));
617
+ // Get the current user's decrypted signing key
618
+ const signingKey = await this.getDecryptedSigningKey();
619
+ // Get the current user's decrypted RSA encryption private key (for decrypting entries)
620
+ const decryptedEncryptionKey = await this.getDecryptedEncryptionKey();
621
+ // Create the client network store
622
+ const store = new ClientNetworkContentAddressedStore(dbId, transport, this.cryptoAdapter, this.currentUser.username, signingKey, decryptedEncryptionKey, this.logger.createChild(`ClientNetworkStore:${dbId}`));
623
+ console.log(`[connectToServer] ✓ Connected to server for db "${dbId}"`);
624
+ this.logger.info(`Connected to server for db "${dbId}"`);
625
+ return store;
626
+ })();
627
+ this.remoteStoreCache.set(cacheKey, remoteStorePromise);
628
+ try {
629
+ return await remoteStorePromise;
630
+ }
631
+ catch (error) {
632
+ this.remoteStoreCache.delete(cacheKey);
633
+ throw error;
634
+ }
489
635
  }
490
636
  /**
491
637
  * Internal method to get the decrypted signing key for the current user.
@@ -538,104 +684,22 @@ class BaseMindooTenant {
538
684
  this.logger.debug(`decryptPrivateKey: Starting decryption with saltString: ${saltString}`);
539
685
  this.logger.debug(`decryptPrivateKey: Password length: ${password.length}`);
540
686
  this.logger.debug(`decryptPrivateKey: EncryptedKey iterations: ${encryptedKey.iterations}`);
541
- const subtle = this.cryptoAdapter.getSubtle();
542
- // Decode base64 strings
543
- this.logger.debug(`decryptPrivateKey: Decoding base64 strings`);
544
- let ciphertext;
545
- let iv;
546
- let tag;
547
- let saltBytes;
687
+ this.logger.debug(`decryptPrivateKey: Delegating PBKDF2 + AES-GCM work to shared helper`);
548
688
  try {
549
- ciphertext = this.base64ToUint8Array(encryptedKey.ciphertext);
550
- iv = this.base64ToUint8Array(encryptedKey.iv);
551
- tag = this.base64ToUint8Array(encryptedKey.tag);
552
- saltBytes = this.base64ToUint8Array(encryptedKey.salt);
553
- this.logger.debug(`decryptPrivateKey: Decoded - ciphertext: ${ciphertext.length} bytes, iv: ${iv.length} bytes, tag: ${tag.length} bytes, salt: ${saltBytes.length} bytes`);
554
- }
555
- catch (error) {
556
- this.logger.error(`decryptPrivateKey: Error decoding base64:`, error);
557
- throw error;
558
- }
559
- // Derive key from password using PBKDF2
560
- // Combine the stored salt bytes with the salt string for additional security
561
- // This ensures different key types (signing, encryption, etc.) use different derived keys
562
- // even if they share the same password
563
- this.logger.debug(`decryptPrivateKey: Combining salt with saltString`);
564
- const saltStringBytes = new TextEncoder().encode(saltString);
565
- const combinedSalt = new Uint8Array(saltBytes.length + saltStringBytes.length);
566
- combinedSalt.set(saltBytes);
567
- combinedSalt.set(saltStringBytes, saltBytes.length);
568
- this.logger.debug(`decryptPrivateKey: Combined salt length: ${combinedSalt.length} bytes`);
569
- this.logger.debug(`decryptPrivateKey: Importing password key for PBKDF2`);
570
- let passwordKey;
571
- try {
572
- passwordKey = await subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"]);
573
- this.logger.debug(`decryptPrivateKey: Successfully imported password key`);
574
- }
575
- catch (error) {
576
- this.logger.error(`decryptPrivateKey: Error importing password key:`, error);
577
- throw error;
578
- }
579
- this.logger.debug(`decryptPrivateKey: Deriving key with PBKDF2 (iterations: ${encryptedKey.iterations})`);
580
- let derivedKey;
581
- try {
582
- derivedKey = await subtle.deriveKey({
583
- name: "PBKDF2",
584
- salt: combinedSalt,
585
- iterations: encryptedKey.iterations,
586
- hash: "SHA-256",
587
- }, passwordKey, {
588
- name: "AES-GCM",
589
- length: 256,
590
- }, false, ["decrypt"]);
591
- this.logger.debug(`decryptPrivateKey: Successfully derived key`);
592
- }
593
- catch (error) {
594
- this.logger.error(`decryptPrivateKey: Error deriving key:`, error);
595
- throw error;
596
- }
597
- // Combine ciphertext and tag (GCM authentication tag is separate)
598
- // AES-GCM expects the tag to be appended to the ciphertext
599
- this.logger.debug(`decryptPrivateKey: Combining ciphertext and tag`);
600
- const encryptedData = new Uint8Array(ciphertext.length + tag.length);
601
- encryptedData.set(ciphertext);
602
- encryptedData.set(tag, ciphertext.length);
603
- this.logger.debug(`decryptPrivateKey: Combined encrypted data length: ${encryptedData.length} bytes`);
604
- // Decrypt the private key
605
- this.logger.debug(`decryptPrivateKey: Decrypting with AES-GCM`);
606
- this.logger.debug(`decryptPrivateKey: IV length: ${iv.length} bytes (expected: 12 for AES-GCM)`);
607
- this.logger.debug(`decryptPrivateKey: Tag length: ${tag.length} bytes (expected: 16 for 128-bit tag)`);
608
- this.logger.debug(`decryptPrivateKey: Ciphertext length: ${ciphertext.length} bytes`);
609
- let decrypted;
610
- try {
611
- // Create a new ArrayBuffer to ensure we have the correct buffer without any offset issues
612
- const encryptedBuffer = new Uint8Array(encryptedData).buffer;
613
- this.logger.debug(`decryptPrivateKey: Encrypted buffer size: ${encryptedBuffer.byteLength} bytes`);
614
- decrypted = await subtle.decrypt({
615
- name: "AES-GCM",
616
- iv: new Uint8Array(iv), // Create a new Uint8Array to ensure proper buffer
617
- tagLength: 128,
618
- }, derivedKey, encryptedBuffer);
689
+ const decrypted = await (0, privateKeyEncryption_1.decryptPrivateKey)(this.cryptoAdapter, encryptedKey, password, saltString);
619
690
  this.logger.debug(`decryptPrivateKey: Successfully decrypted, result length: ${decrypted.byteLength} bytes`);
691
+ return decrypted;
620
692
  }
621
693
  catch (error) {
622
694
  this.logger.error(`decryptPrivateKey: Error during decryption:`, error);
623
695
  this.logger.error(`decryptPrivateKey: Error details:`, {
624
696
  errorName: error instanceof Error ? error.name : typeof error,
625
697
  errorMessage: error instanceof Error ? error.message : String(error),
626
- ivLength: iv.length,
627
- tagLength: tag.length,
628
- ciphertextLength: ciphertext.length,
629
- encryptedDataLength: encryptedData.length,
630
698
  iterations: encryptedKey.iterations,
631
- saltString: saltString,
632
- encryptedDataByteOffset: encryptedData.byteOffset,
633
- encryptedDataByteLength: encryptedData.byteLength,
634
- encryptedDataBufferLength: encryptedData.buffer.byteLength,
699
+ saltString,
635
700
  });
636
701
  throw error;
637
702
  }
638
- return decrypted;
639
703
  }
640
704
  /**
641
705
  * Helper method to convert base64 string to Uint8Array
@@ -682,6 +746,25 @@ class BaseMindooTenant {
682
746
  }
683
747
  return bytes.buffer;
684
748
  }
749
+ /**
750
+ * Check whether the symmetric key for the given key ID is available in the KeyBag.
751
+ * This is a lightweight probe that does not perform any crypto operations.
752
+ *
753
+ * @param decryptionKeyId The key ID ("default" or a named key ID)
754
+ * @returns true if the key can be resolved, false otherwise
755
+ */
756
+ async hasDecryptionKey(decryptionKeyId) {
757
+ try {
758
+ await this.getSymmetricKey(decryptionKeyId);
759
+ return true;
760
+ }
761
+ catch (error) {
762
+ if (error instanceof errors_1.SymmetricKeyNotFoundError) {
763
+ return false;
764
+ }
765
+ throw error;
766
+ }
767
+ }
685
768
  /**
686
769
  * Get the symmetric key for a given key ID.
687
770
  * This is a protected helper method that can be used by subclasses.
@@ -695,9 +778,9 @@ class BaseMindooTenant {
695
778
  if (this.decryptedTenantKeyCache) {
696
779
  return this.decryptedTenantKeyCache;
697
780
  }
698
- const tenantKey = await this.keyBag.get("tenant", this.tenantId);
781
+ const tenantKey = await this.keyBag.get("doc", this.tenantId, types_1.DEFAULT_TENANT_KEY_ID);
699
782
  if (!tenantKey) {
700
- throw new errors_1.SymmetricKeyNotFoundError(`tenant:${this.tenantId}`);
783
+ throw new errors_1.SymmetricKeyNotFoundError(`doc:${this.tenantId}:${types_1.DEFAULT_TENANT_KEY_ID}`);
701
784
  }
702
785
  const symmetricKey = tenantKey;
703
786
  this.decryptedTenantKeyCache = symmetricKey;
@@ -705,7 +788,7 @@ class BaseMindooTenant {
705
788
  }
706
789
  else {
707
790
  // Get the decrypted key from KeyBag
708
- const decryptedKey = await this.keyBag.get("doc", decryptionKeyId);
791
+ const decryptedKey = await this.keyBag.get("doc", this.tenantId, decryptionKeyId);
709
792
  if (!decryptedKey) {
710
793
  throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
711
794
  }