mindoodb 0.0.23 → 0.0.32

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 (340) hide show
  1. package/README-server.md +9 -1
  2. package/README.md +5 -3
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +1 -1
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  5. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +24 -5
  6. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  7. package/dist/appendonlystores/network/HttpTransport.d.ts +14 -2
  8. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  9. package/dist/appendonlystores/network/HttpTransport.js +124 -27
  10. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  11. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +159 -4
  12. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  13. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +334 -25
  14. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  15. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts +9 -0
  16. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts.map +1 -1
  17. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js +77 -5
  18. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js.map +1 -1
  19. package/dist/browser/index.mjs +103 -0
  20. package/dist/core/BaseMindooDB.d.ts +697 -4
  21. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  22. package/dist/core/BaseMindooDB.js +3273 -180
  23. package/dist/core/BaseMindooDB.js.map +1 -1
  24. package/dist/core/BaseMindooTenant.d.ts +192 -1
  25. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  26. package/dist/core/BaseMindooTenant.js +633 -88
  27. package/dist/core/BaseMindooTenant.js.map +1 -1
  28. package/dist/core/BaseMindooTenantDirectory.d.ts +770 -10
  29. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  30. package/dist/core/BaseMindooTenantDirectory.js +2715 -292
  31. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  32. package/dist/core/BaseMindooTenantFactory.d.ts +2 -0
  33. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  34. package/dist/core/BaseMindooTenantFactory.js +20 -0
  35. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  36. package/dist/core/accesscontrol/AccessDeniedError.d.ts +22 -0
  37. package/dist/core/accesscontrol/AccessDeniedError.d.ts.map +1 -0
  38. package/dist/core/accesscontrol/AccessDeniedError.js +28 -0
  39. package/dist/core/accesscontrol/AccessDeniedError.js.map +1 -0
  40. package/dist/core/accesscontrol/DirectoryStateNode.d.ts +220 -0
  41. package/dist/core/accesscontrol/DirectoryStateNode.d.ts.map +1 -0
  42. package/dist/core/accesscontrol/DirectoryStateNode.js +297 -0
  43. package/dist/core/accesscontrol/DirectoryStateNode.js.map +1 -0
  44. package/dist/core/accesscontrol/DirectoryTimeTravelIndex.d.ts +118 -0
  45. package/dist/core/accesscontrol/DirectoryTimeTravelIndex.d.ts.map +1 -0
  46. package/dist/core/accesscontrol/DirectoryTimeTravelIndex.js +173 -0
  47. package/dist/core/accesscontrol/DirectoryTimeTravelIndex.js.map +1 -0
  48. package/dist/core/accesscontrol/directoryProjection.d.ts +26 -0
  49. package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -0
  50. package/dist/core/accesscontrol/directoryProjection.js +174 -0
  51. package/dist/core/accesscontrol/directoryProjection.js.map +1 -0
  52. package/dist/core/accesscontrol/evaluate.d.ts +77 -0
  53. package/dist/core/accesscontrol/evaluate.d.ts.map +1 -0
  54. package/dist/core/accesscontrol/evaluate.js +206 -0
  55. package/dist/core/accesscontrol/evaluate.js.map +1 -0
  56. package/dist/core/accesscontrol/grantKeys.d.ts +113 -0
  57. package/dist/core/accesscontrol/grantKeys.d.ts.map +1 -0
  58. package/dist/core/accesscontrol/grantKeys.js +302 -0
  59. package/dist/core/accesscontrol/grantKeys.js.map +1 -0
  60. package/dist/core/accesscontrol/keyBagReconciler.d.ts +22 -0
  61. package/dist/core/accesscontrol/keyBagReconciler.d.ts.map +1 -0
  62. package/dist/core/accesscontrol/keyBagReconciler.js +3 -0
  63. package/dist/core/accesscontrol/keyBagReconciler.js.map +1 -0
  64. package/dist/core/accesscontrol/materializationGuard.d.ts +89 -0
  65. package/dist/core/accesscontrol/materializationGuard.d.ts.map +1 -0
  66. package/dist/core/accesscontrol/materializationGuard.js +69 -0
  67. package/dist/core/accesscontrol/materializationGuard.js.map +1 -0
  68. package/dist/core/accesscontrol/quarantine.d.ts +65 -0
  69. package/dist/core/accesscontrol/quarantine.d.ts.map +1 -0
  70. package/dist/core/accesscontrol/quarantine.js +79 -0
  71. package/dist/core/accesscontrol/quarantine.js.map +1 -0
  72. package/dist/core/accesscontrol/receiptValidation.d.ts +113 -0
  73. package/dist/core/accesscontrol/receiptValidation.d.ts.map +1 -0
  74. package/dist/core/accesscontrol/receiptValidation.js +142 -0
  75. package/dist/core/accesscontrol/receiptValidation.js.map +1 -0
  76. package/dist/core/accesscontrol/timestamp/Ed25519WitnessProvider.d.ts +31 -0
  77. package/dist/core/accesscontrol/timestamp/Ed25519WitnessProvider.d.ts.map +1 -0
  78. package/dist/core/accesscontrol/timestamp/Ed25519WitnessProvider.js +66 -0
  79. package/dist/core/accesscontrol/timestamp/Ed25519WitnessProvider.js.map +1 -0
  80. package/dist/core/accesscontrol/timestamp/TimestampProvider.d.ts +79 -0
  81. package/dist/core/accesscontrol/timestamp/TimestampProvider.d.ts.map +1 -0
  82. package/dist/core/accesscontrol/timestamp/TimestampProvider.js +22 -0
  83. package/dist/core/accesscontrol/timestamp/TimestampProvider.js.map +1 -0
  84. package/dist/core/accesscontrol/types.d.ts +825 -0
  85. package/dist/core/accesscontrol/types.d.ts.map +1 -0
  86. package/dist/core/accesscontrol/types.js +534 -0
  87. package/dist/core/accesscontrol/types.js.map +1 -0
  88. package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts +9 -0
  89. package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts.map +1 -1
  90. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js +54 -0
  91. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js.map +1 -1
  92. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +38 -2
  93. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  94. package/dist/core/appendonlystores/network/AuthenticationService.js +199 -26
  95. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  96. package/dist/core/appendonlystores/network/NetworkTransport.d.ts +17 -3
  97. package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -1
  98. package/dist/core/appendonlystores/network/types.d.ts +58 -4
  99. package/dist/core/appendonlystores/network/types.d.ts.map +1 -1
  100. package/dist/core/appendonlystores/network/types.js +4 -0
  101. package/dist/core/appendonlystores/network/types.js.map +1 -1
  102. package/dist/core/appendonlystores/scanUtils.d.ts +27 -0
  103. package/dist/core/appendonlystores/scanUtils.d.ts.map +1 -0
  104. package/dist/core/appendonlystores/scanUtils.js +87 -0
  105. package/dist/core/appendonlystores/scanUtils.js.map +1 -0
  106. package/dist/core/appendonlystores/types.d.ts +23 -2
  107. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  108. package/dist/core/cache/CacheManager.d.ts +13 -3
  109. package/dist/core/cache/CacheManager.d.ts.map +1 -1
  110. package/dist/core/cache/CacheManager.js +7 -4
  111. package/dist/core/cache/CacheManager.js.map +1 -1
  112. package/dist/core/crypto/EntrySignature.d.ts +137 -0
  113. package/dist/core/crypto/EntrySignature.d.ts.map +1 -0
  114. package/dist/core/crypto/EntrySignature.js +210 -0
  115. package/dist/core/crypto/EntrySignature.js.map +1 -0
  116. package/dist/core/crypto/MindooDocSigner.d.ts.map +1 -1
  117. package/dist/core/crypto/MindooDocSigner.js +6 -2
  118. package/dist/core/crypto/MindooDocSigner.js.map +1 -1
  119. package/dist/core/crypto/WitnessReceipt.d.ts +137 -0
  120. package/dist/core/crypto/WitnessReceipt.d.ts.map +1 -0
  121. package/dist/core/crypto/WitnessReceipt.js +182 -0
  122. package/dist/core/crypto/WitnessReceipt.js.map +1 -0
  123. package/dist/core/crypto/encryptedFields.d.ts +27 -0
  124. package/dist/core/crypto/encryptedFields.d.ts.map +1 -0
  125. package/dist/core/crypto/encryptedFields.js +58 -0
  126. package/dist/core/crypto/encryptedFields.js.map +1 -0
  127. package/dist/core/crypto/pbkdf2Iterations.d.ts +12 -0
  128. package/dist/core/crypto/pbkdf2Iterations.d.ts.map +1 -1
  129. package/dist/core/crypto/pbkdf2Iterations.js +23 -0
  130. package/dist/core/crypto/pbkdf2Iterations.js.map +1 -1
  131. package/dist/core/crypto/privateKeyEncryption.d.ts.map +1 -1
  132. package/dist/core/crypto/privateKeyEncryption.js +3 -1
  133. package/dist/core/crypto/privateKeyEncryption.js.map +1 -1
  134. package/dist/core/databaseIdValidation.d.ts +1 -1
  135. package/dist/core/databaseIdValidation.d.ts.map +1 -1
  136. package/dist/core/databaseIdValidation.js +4 -4
  137. package/dist/core/databaseIdValidation.js.map +1 -1
  138. package/dist/core/errors.d.ts +26 -0
  139. package/dist/core/errors.d.ts.map +1 -1
  140. package/dist/core/errors.js +42 -1
  141. package/dist/core/errors.js.map +1 -1
  142. package/dist/core/expressions/builder.d.ts +102 -0
  143. package/dist/core/expressions/builder.d.ts.map +1 -0
  144. package/dist/core/expressions/builder.js +249 -0
  145. package/dist/core/expressions/builder.js.map +1 -0
  146. package/dist/core/expressions/evaluateExpression.d.ts +78 -0
  147. package/dist/core/expressions/evaluateExpression.d.ts.map +1 -0
  148. package/dist/core/expressions/evaluateExpression.js +386 -0
  149. package/dist/core/expressions/evaluateExpression.js.map +1 -0
  150. package/dist/core/expressions/formulaSource.d.ts +24 -0
  151. package/dist/core/expressions/formulaSource.d.ts.map +1 -0
  152. package/dist/core/expressions/formulaSource.js +765 -0
  153. package/dist/core/expressions/formulaSource.js.map +1 -0
  154. package/dist/core/expressions/index.d.ts +8 -0
  155. package/dist/core/expressions/index.d.ts.map +1 -0
  156. package/dist/core/expressions/index.js +46 -0
  157. package/dist/core/expressions/index.js.map +1 -0
  158. package/dist/core/expressions/metadata.d.ts +23 -0
  159. package/dist/core/expressions/metadata.d.ts.map +1 -0
  160. package/dist/core/expressions/metadata.js +191 -0
  161. package/dist/core/expressions/metadata.js.map +1 -0
  162. package/dist/core/expressions/types.d.ts +164 -0
  163. package/dist/core/expressions/types.d.ts.map +1 -0
  164. package/dist/core/expressions/types.js +3 -0
  165. package/dist/core/expressions/types.js.map +1 -0
  166. package/dist/core/extraction/types.d.ts +47 -0
  167. package/dist/core/extraction/types.d.ts.map +1 -0
  168. package/dist/core/extraction/types.js +42 -0
  169. package/dist/core/extraction/types.js.map +1 -0
  170. package/dist/core/index.d.ts +13 -3
  171. package/dist/core/index.d.ts.map +1 -1
  172. package/dist/core/index.js +113 -4
  173. package/dist/core/index.js.map +1 -1
  174. package/dist/core/indexing/createViewDataProvider.d.ts +84 -0
  175. package/dist/core/indexing/createViewDataProvider.d.ts.map +1 -0
  176. package/dist/core/indexing/createViewDataProvider.js +139 -0
  177. package/dist/core/indexing/createViewDataProvider.js.map +1 -0
  178. package/dist/core/indexing/fulltext/DocumentFullTextIndex.d.ts +180 -0
  179. package/dist/core/indexing/fulltext/DocumentFullTextIndex.d.ts.map +1 -0
  180. package/dist/core/indexing/fulltext/DocumentFullTextIndex.js +548 -0
  181. package/dist/core/indexing/fulltext/DocumentFullTextIndex.js.map +1 -0
  182. package/dist/core/indexing/fulltext/SearchEngineAdapter.d.ts +69 -0
  183. package/dist/core/indexing/fulltext/SearchEngineAdapter.d.ts.map +1 -0
  184. package/dist/core/indexing/fulltext/SearchEngineAdapter.js +183 -0
  185. package/dist/core/indexing/fulltext/SearchEngineAdapter.js.map +1 -0
  186. package/dist/core/indexing/fulltext/extractFulltextText.d.ts +34 -0
  187. package/dist/core/indexing/fulltext/extractFulltextText.d.ts.map +1 -0
  188. package/dist/core/indexing/fulltext/extractFulltextText.js +129 -0
  189. package/dist/core/indexing/fulltext/extractFulltextText.js.map +1 -0
  190. package/dist/core/indexing/fulltext/index.d.ts +5 -0
  191. package/dist/core/indexing/fulltext/index.d.ts.map +1 -0
  192. package/dist/core/indexing/fulltext/index.js +20 -0
  193. package/dist/core/indexing/fulltext/index.js.map +1 -0
  194. package/dist/core/indexing/fulltext/types.d.ts +168 -0
  195. package/dist/core/indexing/fulltext/types.d.ts.map +1 -0
  196. package/dist/core/indexing/fulltext/types.js +89 -0
  197. package/dist/core/indexing/fulltext/types.js.map +1 -0
  198. package/dist/core/indexing/summary/DocumentSummaryStore.d.ts +137 -0
  199. package/dist/core/indexing/summary/DocumentSummaryStore.d.ts.map +1 -0
  200. package/dist/core/indexing/summary/DocumentSummaryStore.js +438 -0
  201. package/dist/core/indexing/summary/DocumentSummaryStore.js.map +1 -0
  202. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts +72 -0
  203. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts.map +1 -0
  204. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js +221 -0
  205. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js.map +1 -0
  206. package/dist/core/indexing/summary/extractSummaryFields.d.ts +75 -0
  207. package/dist/core/indexing/summary/extractSummaryFields.d.ts.map +1 -0
  208. package/dist/core/indexing/summary/extractSummaryFields.js +266 -0
  209. package/dist/core/indexing/summary/extractSummaryFields.js.map +1 -0
  210. package/dist/core/indexing/summary/index.d.ts +5 -0
  211. package/dist/core/indexing/summary/index.d.ts.map +1 -0
  212. package/dist/core/indexing/summary/index.js +23 -0
  213. package/dist/core/indexing/summary/index.js.map +1 -0
  214. package/dist/core/indexing/summary/types.d.ts +119 -0
  215. package/dist/core/indexing/summary/types.d.ts.map +1 -0
  216. package/dist/core/indexing/summary/types.js +81 -0
  217. package/dist/core/indexing/summary/types.js.map +1 -0
  218. package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.d.ts +47 -1
  219. package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.d.ts.map +1 -1
  220. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +24 -8
  221. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
  222. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +96 -41
  223. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
  224. package/dist/core/indexing/virtualviews/ViewEntrySortKey.d.ts +2 -0
  225. package/dist/core/indexing/virtualviews/ViewEntrySortKey.d.ts.map +1 -1
  226. package/dist/core/indexing/virtualviews/ViewEntrySortKey.js +8 -3
  227. package/dist/core/indexing/virtualviews/ViewEntrySortKey.js.map +1 -1
  228. package/dist/core/indexing/virtualviews/VirtualView.d.ts +111 -5
  229. package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
  230. package/dist/core/indexing/virtualviews/VirtualView.js +266 -75
  231. package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
  232. package/dist/core/indexing/virtualviews/VirtualViewColumn.d.ts +12 -0
  233. package/dist/core/indexing/virtualviews/VirtualViewColumn.d.ts.map +1 -1
  234. package/dist/core/indexing/virtualviews/VirtualViewColumn.js +1 -0
  235. package/dist/core/indexing/virtualviews/VirtualViewColumn.js.map +1 -1
  236. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +12 -0
  237. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
  238. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +66 -5
  239. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
  240. package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts +50 -6
  241. package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts.map +1 -1
  242. package/dist/core/indexing/virtualviews/VirtualViewFactory.js +54 -12
  243. package/dist/core/indexing/virtualviews/VirtualViewFactory.js.map +1 -1
  244. package/dist/core/indexing/virtualviews/index.d.ts +3 -3
  245. package/dist/core/indexing/virtualviews/index.d.ts.map +1 -1
  246. package/dist/core/indexing/virtualviews/index.js.map +1 -1
  247. package/dist/core/indexing/virtualviews/types.d.ts +6 -0
  248. package/dist/core/indexing/virtualviews/types.d.ts.map +1 -1
  249. package/dist/core/indexing/virtualviews/types.js +11 -3
  250. package/dist/core/indexing/virtualviews/types.js.map +1 -1
  251. package/dist/core/keys/KeyBag.d.ts +25 -0
  252. package/dist/core/keys/KeyBag.d.ts.map +1 -1
  253. package/dist/core/keys/KeyBag.js +37 -1
  254. package/dist/core/keys/KeyBag.js.map +1 -1
  255. package/dist/core/query/executeQuery.d.ts +18 -0
  256. package/dist/core/query/executeQuery.d.ts.map +1 -0
  257. package/dist/core/query/executeQuery.js +313 -0
  258. package/dist/core/query/executeQuery.js.map +1 -0
  259. package/dist/core/query/index.d.ts +5 -0
  260. package/dist/core/query/index.d.ts.map +1 -0
  261. package/dist/core/query/index.js +14 -0
  262. package/dist/core/query/index.js.map +1 -0
  263. package/dist/core/query/queryLive.d.ts +27 -0
  264. package/dist/core/query/queryLive.d.ts.map +1 -0
  265. package/dist/core/query/queryLive.js +104 -0
  266. package/dist/core/query/queryLive.js.map +1 -0
  267. package/dist/core/query/queryView.d.ts +156 -0
  268. package/dist/core/query/queryView.d.ts.map +1 -0
  269. package/dist/core/query/queryView.js +266 -0
  270. package/dist/core/query/queryView.js.map +1 -0
  271. package/dist/core/query/types.d.ts +121 -0
  272. package/dist/core/query/types.d.ts.map +1 -0
  273. package/dist/core/query/types.js +18 -0
  274. package/dist/core/query/types.js.map +1 -0
  275. package/dist/core/storeEntryTime.d.ts +57 -0
  276. package/dist/core/storeEntryTime.d.ts.map +1 -0
  277. package/dist/core/storeEntryTime.js +72 -0
  278. package/dist/core/storeEntryTime.js.map +1 -0
  279. package/dist/core/types.d.ts +1509 -39
  280. package/dist/core/types.d.ts.map +1 -1
  281. package/dist/core/types.js +13 -1
  282. package/dist/core/types.js.map +1 -1
  283. package/dist/core/uri/MindooURI.d.ts +53 -1
  284. package/dist/core/uri/MindooURI.d.ts.map +1 -1
  285. package/dist/core/uri/MindooURI.js +135 -0
  286. package/dist/core/uri/MindooURI.js.map +1 -1
  287. package/dist/core/utils/urlSafety.d.ts +46 -0
  288. package/dist/core/utils/urlSafety.d.ts.map +1 -0
  289. package/dist/core/utils/urlSafety.js +144 -0
  290. package/dist/core/utils/urlSafety.js.map +1 -0
  291. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts +14 -0
  292. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -1
  293. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +95 -4
  294. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -1
  295. package/dist/node/server/CapabilityMatcher.d.ts +10 -0
  296. package/dist/node/server/CapabilityMatcher.d.ts.map +1 -1
  297. package/dist/node/server/CapabilityMatcher.js +14 -2
  298. package/dist/node/server/CapabilityMatcher.js.map +1 -1
  299. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  300. package/dist/node/server/MindooDBServer.js +117 -8
  301. package/dist/node/server/MindooDBServer.js.map +1 -1
  302. package/dist/node/server/PurgedDocRegistry.d.ts +32 -0
  303. package/dist/node/server/PurgedDocRegistry.d.ts.map +1 -0
  304. package/dist/node/server/PurgedDocRegistry.js +94 -0
  305. package/dist/node/server/PurgedDocRegistry.js.map +1 -0
  306. package/dist/node/server/ServerSync.d.ts.map +1 -1
  307. package/dist/node/server/ServerSync.js +5 -2
  308. package/dist/node/server/ServerSync.js.map +1 -1
  309. package/dist/node/server/SystemAdminAuth.d.ts +10 -0
  310. package/dist/node/server/SystemAdminAuth.d.ts.map +1 -1
  311. package/dist/node/server/SystemAdminAuth.js +25 -0
  312. package/dist/node/server/SystemAdminAuth.js.map +1 -1
  313. package/dist/node/server/TenantManager.d.ts +95 -1
  314. package/dist/node/server/TenantManager.d.ts.map +1 -1
  315. package/dist/node/server/TenantManager.js +382 -15
  316. package/dist/node/server/TenantManager.js.map +1 -1
  317. package/dist/node/server/config.d.ts.map +1 -1
  318. package/dist/node/server/config.js +5 -1
  319. package/dist/node/server/config.js.map +1 -1
  320. package/dist/node/server/types.d.ts +29 -3
  321. package/dist/node/server/types.d.ts.map +1 -1
  322. package/dist/node/server/types.js +12 -0
  323. package/dist/node/server/types.js.map +1 -1
  324. package/dist/node/server/validation.d.ts +6 -0
  325. package/dist/node/server/validation.d.ts.map +1 -1
  326. package/dist/node/server/validation.js +7 -1
  327. package/dist/node/server/validation.js.map +1 -1
  328. package/package.json +4 -3
  329. package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.d.ts +0 -8
  330. package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.d.ts.map +0 -1
  331. package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.js +0 -16
  332. package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.js.map +0 -1
  333. package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.d.ts +0 -68
  334. package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +0 -1
  335. package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.js +0 -659
  336. package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.js.map +0 -1
  337. package/dist/node/attachments/writeAttachmentToFile.d.ts +0 -15
  338. package/dist/node/attachments/writeAttachmentToFile.d.ts.map +0 -1
  339. package/dist/node/attachments/writeAttachmentToFile.js +0 -73
  340. package/dist/node/attachments/writeAttachmentToFile.js.map +0 -1
@@ -4,9 +4,13 @@ exports.BaseMindooTenant = void 0;
4
4
  const types_1 = require("./types");
5
5
  const BaseMindooDB_1 = require("./BaseMindooDB");
6
6
  const BaseMindooTenantDirectory_1 = require("./BaseMindooTenantDirectory");
7
+ const grantKeys_1 = require("./accesscontrol/grantKeys");
7
8
  const MindooDocSigner_1 = require("./crypto/MindooDocSigner");
8
9
  const RSAEncryption_1 = require("./crypto/RSAEncryption");
9
10
  const privateKeyEncryption_1 = require("./crypto/privateKeyEncryption");
11
+ const EntrySignature_1 = require("./crypto/EntrySignature");
12
+ const storeEntryTime_1 = require("./storeEntryTime");
13
+ const idGeneration_1 = require("./utils/idGeneration");
10
14
  const errors_1 = require("./errors");
11
15
  const logging_1 = require("./logging");
12
16
  const MindooURI_1 = require("./uri/MindooURI");
@@ -60,6 +64,27 @@ class BaseMindooTenant {
60
64
  * explicit calls cannot race against each other or duplicate work.
61
65
  */
62
66
  this.keyBagReconcilePromise = null;
67
+ /**
68
+ * Cache of imported AES-GCM `CryptoKey`s, keyed by usage + raw key bytes
69
+ * (hex). `subtle.importKey` costs a WebCrypto round trip per call, which
70
+ * adds up on bulk writes/reads (one import per store entry). Because the
71
+ * cache is content-addressed by the key bytes, rotated key versions miss
72
+ * the cache naturally — no explicit invalidation is needed.
73
+ */
74
+ this.importedAesKeyCache = new Map();
75
+ /**
76
+ * Cache of imported Ed25519 verify `CryptoKey`s, keyed by the PEM public
77
+ * key. Signature verification runs once per entry when materializing
78
+ * documents, so the per-call SPKI import is on the hot read path.
79
+ */
80
+ this.importedVerifyKeyCache = new Map();
81
+ // Single-flight guard for SDK-driven key-distribution reconcile (§13). The
82
+ // reconcile driver itself calls getDirectoryDB / updateUnifiedCache /
83
+ // syncStoreChanges, any of which can re-enter a trigger; this flag keeps the
84
+ // run-always trigger (directory bring-up + after each directory pull) from
85
+ // recursing or overlapping. Reconcile is idempotent, so a skipped overlap is
86
+ // harmless — the next trigger re-runs it.
87
+ this.reconcileInFlight = false;
63
88
  // Local cache support
64
89
  this.cacheManager = null;
65
90
  this.factory = factory;
@@ -189,59 +214,86 @@ class BaseMindooTenant {
189
214
  getAdministrationEncryptionPublicKey() {
190
215
  return this.administrationEncryptionPublicKey;
191
216
  }
217
+ /**
218
+ * Import an AES-GCM key for `usage`, memoized on the raw key bytes. See
219
+ * {@link importedAesKeyCache}. On overflow the cache is simply cleared —
220
+ * refilling it costs one import per active key, which is negligible.
221
+ */
222
+ async importAesKeyCached(symmetricKey, usage) {
223
+ let cacheKey = usage + ":";
224
+ for (let i = 0; i < symmetricKey.length; i++) {
225
+ cacheKey += symmetricKey[i].toString(16).padStart(2, "0");
226
+ }
227
+ const cached = this.importedAesKeyCache.get(cacheKey);
228
+ if (cached) {
229
+ return cached;
230
+ }
231
+ const subtle = this.cryptoAdapter.getSubtle();
232
+ // Create a new Uint8Array to ensure we have a proper ArrayBuffer
233
+ const keyArray = new Uint8Array(symmetricKey);
234
+ const cryptoKey = await subtle.importKey("raw", keyArray.buffer, { name: "AES-GCM" }, false, // not extractable
235
+ [usage]);
236
+ if (this.importedAesKeyCache.size >= BaseMindooTenant.MAX_IMPORTED_KEY_CACHE_ENTRIES) {
237
+ this.importedAesKeyCache.clear();
238
+ }
239
+ this.importedAesKeyCache.set(cacheKey, cryptoKey);
240
+ return cryptoKey;
241
+ }
192
242
  async encryptPayload(payload, decryptionKeyId) {
193
- this.logger.debug(`Encrypting payload with key: ${decryptionKeyId}`);
194
- this.logger.debug(`Payload size: ${payload.length} bytes`);
243
+ // The per-step debug logs below are on the hot write path (one call per
244
+ // store entry); skip building the interpolated strings when debug is off.
245
+ const debugEnabled = this.logger.isLevelEnabled(logging_1.LogLevel.DEBUG);
246
+ if (debugEnabled) {
247
+ this.logger.debug(`Encrypting payload with key: ${decryptionKeyId}`);
248
+ this.logger.debug(`Payload size: ${payload.length} bytes`);
249
+ }
195
250
  // Get the symmetric key for this key ID
196
251
  let symmetricKey;
197
252
  try {
198
253
  if (decryptionKeyId === "default") {
199
- this.logger.debug(`Using default key (tenant encryption key)`);
200
254
  // Use cached tenant encryption key if available
201
255
  if (this.decryptedTenantKeyCache) {
202
256
  symmetricKey = this.decryptedTenantKeyCache;
203
- this.logger.debug(`Using cached tenant key, length: ${symmetricKey.length} bytes`);
257
+ if (debugEnabled)
258
+ this.logger.debug(`Using cached tenant key, length: ${symmetricKey.length} bytes`);
204
259
  }
205
260
  else {
206
- this.logger.debug(`Resolving tenant encryption key from KeyBag`);
261
+ if (debugEnabled)
262
+ this.logger.debug(`Resolving tenant encryption key from KeyBag`);
207
263
  const tenantKey = await this.keyBag.get("doc", this.tenantId, types_1.DEFAULT_TENANT_KEY_ID);
208
264
  if (!tenantKey) {
209
265
  throw new errors_1.SymmetricKeyNotFoundError(`doc:${this.tenantId}:${types_1.DEFAULT_TENANT_KEY_ID}`);
210
266
  }
211
267
  symmetricKey = tenantKey;
212
268
  this.decryptedTenantKeyCache = symmetricKey;
213
- this.logger.debug(`Loaded tenant key from KeyBag, length: ${symmetricKey.length} bytes`);
269
+ if (debugEnabled)
270
+ this.logger.debug(`Loaded tenant key from KeyBag, length: ${symmetricKey.length} bytes`);
214
271
  }
215
272
  }
216
273
  else {
217
- this.logger.debug(`Getting named key from KeyBag: ${decryptionKeyId}`);
274
+ if (debugEnabled)
275
+ this.logger.debug(`Getting named key from KeyBag: ${decryptionKeyId}`);
218
276
  // Get the decrypted key from KeyBag
219
277
  const decryptedKey = await this.keyBag.get("doc", this.tenantId, decryptionKeyId);
220
278
  if (!decryptedKey) {
221
279
  throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
222
280
  }
223
281
  symmetricKey = decryptedKey;
224
- this.logger.debug(`Got named key, length: ${symmetricKey.length} bytes`);
282
+ if (debugEnabled)
283
+ this.logger.debug(`Got named key, length: ${symmetricKey.length} bytes`);
225
284
  }
226
285
  }
227
286
  catch (error) {
228
287
  this.logger.error(`Error getting symmetric key:`, error);
229
288
  throw error;
230
289
  }
231
- this.logger.debug(`Got symmetric key, length: ${symmetricKey.length} bytes`);
232
290
  const subtle = this.cryptoAdapter.getSubtle();
233
291
  // Bind getRandomValues to maintain 'this' context
234
292
  const randomValues = this.cryptoAdapter.getRandomValues.bind(this.cryptoAdapter);
235
- // Import the symmetric key
236
- this.logger.debug(`Importing symmetric key for AES-GCM`);
293
+ // Import the symmetric key (memoized; see importAesKeyCached)
237
294
  let cryptoKey;
238
295
  try {
239
- // Create a new Uint8Array to ensure we have a proper ArrayBuffer
240
- const keyArray = new Uint8Array(symmetricKey);
241
- this.logger.debug(`Key buffer size: ${keyArray.buffer.byteLength} bytes`);
242
- cryptoKey = await subtle.importKey("raw", keyArray.buffer, { name: "AES-GCM" }, false, // not extractable
243
- ["encrypt"]);
244
- this.logger.debug(`Successfully imported key`);
296
+ cryptoKey = await this.importAesKeyCached(symmetricKey, "encrypt");
245
297
  }
246
298
  catch (error) {
247
299
  this.logger.error(`Error importing key:`, error);
@@ -249,33 +301,26 @@ class BaseMindooTenant {
249
301
  throw error;
250
302
  }
251
303
  // Generate IV (12 bytes for AES-GCM)
252
- this.logger.debug(`Generating IV`);
253
304
  let iv;
254
305
  try {
255
306
  const ivArray = new Uint8Array(12);
256
307
  randomValues(ivArray);
257
308
  iv = new Uint8Array(ivArray.buffer, ivArray.byteOffset, ivArray.byteLength);
258
- this.logger.debug(`Generated IV: ${iv.length} bytes`);
259
309
  }
260
310
  catch (error) {
261
311
  this.logger.error(`Error generating IV:`, error);
262
312
  throw error;
263
313
  }
264
314
  // Encrypt the payload
265
- this.logger.debug(`Encrypting payload with AES-GCM`);
266
- this.logger.debug(`Payload buffer size: ${payload.buffer.byteLength} bytes`);
267
- this.logger.debug(`Payload byteOffset: ${payload.byteOffset}, byteLength: ${payload.byteLength}`);
268
315
  let encrypted;
269
316
  try {
270
317
  // Create a new Uint8Array to ensure we have a proper ArrayBuffer
271
318
  const payloadArray = new Uint8Array(payload);
272
- this.logger.debug(`Using payload array: ${payloadArray.length} bytes`);
273
319
  encrypted = await subtle.encrypt({
274
320
  name: "AES-GCM",
275
321
  iv: iv,
276
322
  tagLength: 128, // 128-bit authentication tag
277
323
  }, cryptoKey, payloadArray);
278
- this.logger.debug(`Successfully encrypted payload, encrypted size: ${encrypted.byteLength} bytes`);
279
324
  }
280
325
  catch (error) {
281
326
  this.logger.error(`Error encrypting payload:`, error);
@@ -290,58 +335,73 @@ class BaseMindooTenant {
290
335
  }
291
336
  // Combine IV and encrypted data
292
337
  // Format: IV (12 bytes) + encrypted data (includes authentication tag)
293
- this.logger.debug(`Combining IV and encrypted data`);
294
338
  const encryptedArray = new Uint8Array(encrypted);
295
339
  const result = new Uint8Array(12 + encryptedArray.length);
296
340
  result.set(iv, 0);
297
341
  result.set(encryptedArray, 12);
298
- this.logger.debug(`Encrypted payload (${payload.length} -> ${result.length} bytes)`);
342
+ if (debugEnabled)
343
+ this.logger.debug(`Encrypted payload (${payload.length} -> ${result.length} bytes)`);
299
344
  return result;
300
345
  }
301
346
  async decryptPayload(encryptedPayload, decryptionKeyId) {
302
- this.logger.debug(`Decrypting payload with key: ${decryptionKeyId}`);
347
+ // Hot read path (one call per store entry): skip interpolations when off.
348
+ const debugEnabled = this.logger.isLevelEnabled(logging_1.LogLevel.DEBUG);
349
+ if (debugEnabled)
350
+ this.logger.debug(`Decrypting payload with key: ${decryptionKeyId}`);
303
351
  if (encryptedPayload.length < 12) {
304
352
  throw new Error("Encrypted payload too short (missing IV)");
305
353
  }
306
354
  // Extract IV (first 12 bytes) and encrypted data (rest)
307
355
  const iv = encryptedPayload.slice(0, 12);
308
356
  const encryptedData = encryptedPayload.slice(12);
309
- // Get the symmetric key for this key ID
310
- let symmetricKey;
311
- if (decryptionKeyId === "default") {
312
- // Use cached tenant encryption key if available
313
- if (this.decryptedTenantKeyCache) {
314
- symmetricKey = this.decryptedTenantKeyCache;
315
- }
316
- else {
317
- const tenantKey = await this.keyBag.get("doc", this.tenantId, types_1.DEFAULT_TENANT_KEY_ID);
318
- if (!tenantKey) {
319
- throw new errors_1.SymmetricKeyNotFoundError(`doc:${this.tenantId}:${types_1.DEFAULT_TENANT_KEY_ID}`);
357
+ // Gather ALL stored versions of the key (newest first). Key rotation keeps
358
+ // several versions under one keyId, and a payload may have been encrypted
359
+ // under any of them, so we must try each — not just the newest. The newest
360
+ // version is overwhelmingly the common case, so it is tried first.
361
+ const candidates = await this.getDecryptionKeyCandidates(decryptionKeyId);
362
+ if (candidates.length === 0) {
363
+ throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId === "default" ? `doc:${this.tenantId}:${types_1.DEFAULT_TENANT_KEY_ID}` : decryptionKeyId);
364
+ }
365
+ const subtle = this.cryptoAdapter.getSubtle();
366
+ let lastError = undefined;
367
+ for (const symmetricKey of candidates) {
368
+ try {
369
+ const cryptoKey = await this.importAesKeyCached(symmetricKey, "decrypt");
370
+ const decrypted = await subtle.decrypt({
371
+ name: "AES-GCM",
372
+ iv: iv,
373
+ tagLength: 128, // 128-bit authentication tag
374
+ }, cryptoKey, encryptedData);
375
+ if (debugEnabled) {
376
+ this.logger.debug(`Decrypted payload (${encryptedPayload.length} -> ${decrypted.byteLength} bytes)`);
320
377
  }
321
- symmetricKey = tenantKey;
322
- this.decryptedTenantKeyCache = symmetricKey;
378
+ return new Uint8Array(decrypted);
323
379
  }
324
- }
325
- else {
326
- // Get the decrypted key from KeyBag
327
- const decryptedKey = await this.keyBag.get("doc", this.tenantId, decryptionKeyId);
328
- if (!decryptedKey) {
329
- throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
380
+ catch (error) {
381
+ // AES-GCM authentication failure means this version was not the one used
382
+ // to encrypt; fall through and try the next version.
383
+ lastError = error;
330
384
  }
331
- symmetricKey = decryptedKey;
332
385
  }
333
- const subtle = this.cryptoAdapter.getSubtle();
334
- // Import the symmetric key
335
- const cryptoKey = await subtle.importKey("raw", symmetricKey.buffer, { name: "AES-GCM" }, false, // not extractable
336
- ["decrypt"]);
337
- // Decrypt the payload
338
- const decrypted = await subtle.decrypt({
339
- name: "AES-GCM",
340
- iv: iv,
341
- tagLength: 128, // 128-bit authentication tag
342
- }, cryptoKey, encryptedData.buffer);
343
- this.logger.debug(`Decrypted payload (${encryptedPayload.length} -> ${decrypted.byteLength} bytes)`);
344
- return new Uint8Array(decrypted);
386
+ this.logger.debug(`Decrypt failed against all ${candidates.length} version(s) of key "${decryptionKeyId}"`);
387
+ throw lastError ?? new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
388
+ }
389
+ /**
390
+ * Return every stored version of the symmetric key for `decryptionKeyId`,
391
+ * newest first, mapping the `"default"` alias to the tenant default key id.
392
+ * Used by the decryption paths to support key rotation (a payload may have
393
+ * been encrypted under an older version than the current newest one).
394
+ *
395
+ * As a side effect, refreshes {@link decryptedTenantKeyCache} to the newest
396
+ * tenant-default version so encryption keeps using the latest key.
397
+ */
398
+ async getDecryptionKeyCandidates(decryptionKeyId) {
399
+ const id = decryptionKeyId === "default" ? types_1.DEFAULT_TENANT_KEY_ID : decryptionKeyId;
400
+ const versions = await this.keyBag.getAllKeys("doc", this.tenantId, id);
401
+ if (decryptionKeyId === "default" && versions.length > 0) {
402
+ this.decryptedTenantKeyCache = versions[0];
403
+ }
404
+ return versions;
345
405
  }
346
406
  async signPayload(payload) {
347
407
  this.logger.debug(`Signing payload`);
@@ -381,20 +441,100 @@ class BaseMindooTenant {
381
441
  return false;
382
442
  }
383
443
  const subtle = this.cryptoAdapter.getSubtle();
384
- // Convert PEM format to ArrayBuffer
385
- const publicKeyBuffer = this.pemToArrayBuffer(publicKey);
386
- // Import the public key from SPKI format
387
- const cryptoKey = await subtle.importKey("spki", publicKeyBuffer, {
388
- name: "Ed25519",
389
- }, false, // not extractable
390
- ["verify"]);
444
+ // Import the public key from SPKI format (memoized per PEM string)
445
+ const cryptoKey = await this.importVerifyKeyCached(publicKey);
391
446
  // Verify the signature
392
447
  const isValid = await subtle.verify({
393
448
  name: "Ed25519",
394
449
  }, cryptoKey, signature.buffer, payload.buffer);
395
- this.logger.info(`Signature verification result: ${isValid}`);
450
+ // Debug (not info): this runs once per verified entry on the read path.
451
+ if (this.logger.isLevelEnabled(logging_1.LogLevel.DEBUG)) {
452
+ this.logger.debug(`Signature verification result: ${isValid}`);
453
+ }
396
454
  return isValid;
397
455
  }
456
+ /**
457
+ * Import an Ed25519 verify key from its PEM SPKI form, memoized per PEM
458
+ * string. See {@link importedVerifyKeyCache}.
459
+ */
460
+ async importVerifyKeyCached(publicKeyPem) {
461
+ const cached = this.importedVerifyKeyCache.get(publicKeyPem);
462
+ if (cached) {
463
+ return cached;
464
+ }
465
+ const subtle = this.cryptoAdapter.getSubtle();
466
+ const cryptoKey = await subtle.importKey("spki", this.pemToArrayBuffer(publicKeyPem), {
467
+ name: "Ed25519",
468
+ }, false, // not extractable
469
+ ["verify"]);
470
+ if (this.importedVerifyKeyCache.size >= BaseMindooTenant.MAX_IMPORTED_KEY_CACHE_ENTRIES) {
471
+ this.importedVerifyKeyCache.clear();
472
+ }
473
+ this.importedVerifyKeyCache.set(publicKeyPem, cryptoKey);
474
+ return cryptoKey;
475
+ }
476
+ async verifyEntrySignature(entry, encryptedData) {
477
+ // The author key must be trusted by the tenant directory.
478
+ const directory = await this.openDirectory();
479
+ const isTrusted = await directory.validatePublicSigningKey(entry.createdByPublicKey);
480
+ if (!isTrusted) {
481
+ this.logger.warn(`Public key not trusted: ${entry.createdByPublicKey}`);
482
+ return false;
483
+ }
484
+ const subtle = this.cryptoAdapter.getSubtle();
485
+ // Integrity: the served bytes must hash to the signed/hashed contentHash so
486
+ // a relay cannot substitute payload bytes that disagree with the metadata
487
+ // (audit finding #5).
488
+ const actualHash = await (0, idGeneration_1.computeContentHash)(encryptedData, subtle);
489
+ if (actualHash !== entry.contentHash) {
490
+ this.logger.warn(`Content hash mismatch for entry ${entry.id} (expected ${entry.contentHash.substring(0, 16)}..., got ${actualHash.substring(0, 16)}...)`);
491
+ return false;
492
+ }
493
+ // Storage-format floor (requireMetadataSignatureSince): if the tenant
494
+ // requires the v2 metadata-binding signature for entries at/after a
495
+ // trusted-time cutoff, disable the legacy fallback for this entry. The
496
+ // cutoff is compared against the entry's trusted time so genuine older
497
+ // history still verifies via the legacy signature.
498
+ const requireMetadataSignature = await this.requiresMetadataSignature(directory, entry);
499
+ // Version-aware author signature: prefer the metadata-binding signature,
500
+ // fall back to the legacy ciphertext-only signature for v1/legacy entries
501
+ // (unless the floor above forbids the fallback for this entry). The
502
+ // author's verify key is imported once per PEM and cached — this runs for
503
+ // every entry a document replays.
504
+ const authorKey = await this.importVerifyKeyCached(entry.createdByPublicKey);
505
+ const isValid = await (0, EntrySignature_1.verifyEntrySignatureWithImportedKey)(entry, encryptedData, authorKey, subtle, { requireMetadataSignature });
506
+ if (this.logger.isLevelEnabled(logging_1.LogLevel.DEBUG)) {
507
+ this.logger.debug(`Entry signature verification result: ${isValid}`);
508
+ }
509
+ return isValid;
510
+ }
511
+ /**
512
+ * Whether the storage-format floor (`requireMetadataSignatureSince`) requires
513
+ * `entry` to carry the v2 metadata-binding signature, based on the entry's
514
+ * trusted time. Returns false when no floor is configured, when the directory
515
+ * implementation predates the policy, or on any resolution error (fail-open
516
+ * here is safe: the legacy signature is still cryptographically verified — the
517
+ * floor only refuses the *weaker* fallback for new entries).
518
+ */
519
+ async requiresMetadataSignature(directory, entry) {
520
+ if (entry.metadataSignature) {
521
+ return false; // already v2 — the floor is irrelevant
522
+ }
523
+ if (typeof directory.getRequireMetadataSignatureSince !== "function") {
524
+ return false;
525
+ }
526
+ try {
527
+ const cutoff = await directory.getRequireMetadataSignatureSince();
528
+ if (cutoff === undefined) {
529
+ return false;
530
+ }
531
+ return (0, storeEntryTime_1.entryTrustedTime)(entry, Date.now()) >= cutoff;
532
+ }
533
+ catch (error) {
534
+ this.logger.warn(`Failed to resolve metadata-signature floor: ${String(error)}`);
535
+ return false;
536
+ }
537
+ }
398
538
  async getCurrentUserId() {
399
539
  // Convert PrivateUserId to PublicUserId
400
540
  return {
@@ -417,6 +557,108 @@ class BaseMindooTenant {
417
557
  }
418
558
  return this.directoryCache;
419
559
  }
560
+ /**
561
+ * Check whether an admin has requested a remote wipe of THIS device and, if
562
+ * so, delete the entire local tenant (docs/accesscontrol.md §6.5).
563
+ *
564
+ * The directive lives in the current user's admin-signed `grantaccess`
565
+ * document as `wipeRequestedForSigningKeys`. Because the directory database is
566
+ * admin-only, any grant document that materializes there is already verified
567
+ * as admin-signed — so finding this device's signing key in that list is a
568
+ * genuine, admin-issued directive (the server, which may be hostile, cannot
569
+ * forge it). Sync the directory before calling this so the latest directive is
570
+ * visible.
571
+ *
572
+ * @returns true if a wipe was applied (the tenant is now gone locally), false
573
+ * if this device is not targeted.
574
+ */
575
+ async checkAndApplyRemoteWipe() {
576
+ const directory = await this.openDirectory();
577
+ const myKey = this.currentUser.userSigningKeyPair.publicKey;
578
+ const finder = directory;
579
+ if (typeof finder.findGrantAccessDocuments !== "function") {
580
+ return false;
581
+ }
582
+ const grants = await finder.findGrantAccessDocuments(this.currentUser.username);
583
+ const targeted = grants.some((grant) => (0, grantKeys_1.extractWipeRequestedSigningKeys)(grant.getData()).includes(myKey));
584
+ if (!targeted) {
585
+ return false;
586
+ }
587
+ this.logger.warn(`Remote-wipe directive found for this device; deleting local tenant "${this.tenantId}".`);
588
+ await this.wipeLocalTenant();
589
+ return true;
590
+ }
591
+ /**
592
+ * Delete all local data for this tenant from the device (docs/accesscontrol.md
593
+ * §6.5): every local database (the directory database plus all known data
594
+ * databases), this tenant's keys in the (multi-tenant) KeyBag, and all
595
+ * in-memory caches. Other tenants on the same device are untouched.
596
+ *
597
+ * Idempotent and best-effort: once the local tenant is gone there is nothing
598
+ * left to delete. Pass extra database ids for databases that exist locally but
599
+ * have not been opened in this session.
600
+ *
601
+ * @param additionalDbIds Optional extra local database ids to wipe.
602
+ */
603
+ async wipeLocalTenant(additionalDbIds) {
604
+ // Every local database belonging to this tenant: the directory plus any
605
+ // opened/known data databases. Wipe is a whole-tenant operation.
606
+ const dbIds = new Set([
607
+ "directory",
608
+ ...this.databaseCache.keys(),
609
+ ...(additionalDbIds ?? []),
610
+ ]);
611
+ for (const dbId of dbIds) {
612
+ await this.clearLocalStoresForDb(dbId);
613
+ }
614
+ // Drop in-memory caches and decrypted key material so nothing lingers.
615
+ this.databaseCache.clear();
616
+ this.directoryCache = null;
617
+ this.remoteStoreCache.clear();
618
+ this.decryptedTenantKeyCache = undefined;
619
+ this.decryptedUserSigningKeyCache = undefined;
620
+ this.decryptedUserEncryptionKeyCache = undefined;
621
+ this.disposeCacheManager();
622
+ // Remove this tenant's keys from the multi-tenant KeyBag, leaving other
623
+ // tenants intact.
624
+ try {
625
+ await this.keyBag.deleteTenantKeys(this.tenantId);
626
+ }
627
+ catch (error) {
628
+ this.logger.warn(`wipeLocalTenant: failed to delete KeyBag keys: ${error}`);
629
+ }
630
+ }
631
+ /** Clear the document and attachment stores for one local database. */
632
+ async clearLocalStoresForDb(dbId) {
633
+ let docStore;
634
+ let attachmentStore;
635
+ const opened = this.databaseCache.get(dbId);
636
+ if (opened) {
637
+ docStore = opened.getStore();
638
+ attachmentStore = opened.getAttachmentStore();
639
+ }
640
+ else {
641
+ try {
642
+ const created = this.storeFactory.createStore(dbId);
643
+ docStore = created.docStore;
644
+ attachmentStore = created.attachmentStore;
645
+ }
646
+ catch (error) {
647
+ this.logger.warn(`wipeLocalTenant: could not open stores for db "${dbId}": ${error}`);
648
+ return;
649
+ }
650
+ }
651
+ for (const store of [docStore, attachmentStore]) {
652
+ if (store && typeof store.clearAllLocalData === "function") {
653
+ try {
654
+ await store.clearAllLocalData();
655
+ }
656
+ catch (error) {
657
+ this.logger.warn(`wipeLocalTenant: clearAllLocalData failed for db "${dbId}": ${error}`);
658
+ }
659
+ }
660
+ }
661
+ }
420
662
  async assertCurrentUserCanOpenDB(id) {
421
663
  if (id === "directory") {
422
664
  return;
@@ -429,11 +671,35 @@ class BaseMindooTenant {
429
671
  const registeredUser = await directory.getUserPublicKeys(currentUser.username);
430
672
  const hasMatchingGrant = registeredUser?.signingPublicKey === currentUser.userSigningPublicKey &&
431
673
  registeredUser?.encryptionPublicKey === currentUser.userEncryptionPublicKey;
432
- if (hasMatchingGrant) {
433
- return;
674
+ if (!hasMatchingGrant) {
675
+ this.logger.warn(`Denied database open for ungranted user ${currentUser.username} on database ${id}`);
676
+ throw new Error(`User "${currentUser.username}" does not have tenant access yet; the tenant admin must grant access first.`);
677
+ }
678
+ // Directory-restricted database policy: when the tenant's default policy
679
+ // restricts which databases may be opened, a granted (non-admin) user may
680
+ // only open a listed database. "directory" is already short-circuited above
681
+ // and the admin returned earlier, so this never blocks the policy read
682
+ // itself (which opens "directory").
683
+ if (typeof directory.isDatabaseAllowed === "function") {
684
+ const allowed = await directory.isDatabaseAllowed(id);
685
+ if (!allowed) {
686
+ this.logger.warn(`Denied database open for ${currentUser.username}: database "${id}" is not in the tenant's allowed database list`);
687
+ throw new Error(`Database "${id}" is not in the tenant's allowed database list.`);
688
+ }
689
+ }
690
+ // Database read gate (doc_read, §6.6): when the tenant policy denies read
691
+ // access to this database for the current user (and no doc_read allow rule
692
+ // grants it), refuse to open it. Because read is the coarse gate in front
693
+ // of every sync operation, this also prevents creating data in a database
694
+ // the user cannot read. "directory" and the admin are exempt (handled in
695
+ // canReadDatabase).
696
+ if (typeof directory.canReadDatabase === "function") {
697
+ const canRead = await directory.canReadDatabase(id);
698
+ if (!canRead) {
699
+ this.logger.warn(`Denied database open for ${currentUser.username}: no read access to database "${id}"`);
700
+ throw new Error(`User "${currentUser.username}" does not have read access to database "${id}".`);
701
+ }
434
702
  }
435
- this.logger.warn(`Denied database open for ungranted user ${currentUser.username} on database ${id}`);
436
- throw new Error(`User "${currentUser.username}" does not have tenant access yet; the tenant admin must grant access first.`);
437
703
  }
438
704
  async openDB(id, options) {
439
705
  const validDbId = (0, databaseIdValidation_1.validateDatabaseId)(id, "dbId");
@@ -626,7 +892,12 @@ class BaseMindooTenant {
626
892
  userSigningPublicKey: request.signingPublicKey,
627
893
  userEncryptionPublicKey: request.encryptionPublicKey,
628
894
  };
629
- await directory.registerUser(publicUserId, options.adminSigningKey, options.adminPassword);
895
+ // Device label for this key pair (§6.5): an explicit admin-provided label
896
+ // overrides the one suggested by the joining user in the request.
897
+ const deviceLabel = typeof options.label === "string" && options.label.trim().length > 0
898
+ ? options.label.trim()
899
+ : request.label;
900
+ await directory.registerUser(publicUserId, options.adminSigningKey, options.adminPassword, undefined, deviceLabel);
630
901
  // 2. Export selected document keys encrypted with the share password.
631
902
  // `$publicinfos` is mandatory because the joining user needs it for
632
903
  // directory access; all selected key ids include every rotated version.
@@ -862,6 +1133,35 @@ class BaseMindooTenant {
862
1133
  this.decryptedUserEncryptionKeyCache = cryptoKey;
863
1134
  return cryptoKey;
864
1135
  }
1136
+ /**
1137
+ * Inject an already-unlocked RSA-OAEP encryption private key for the current
1138
+ * user, priming the same cache {@link getDecryptedEncryptionKey} uses. Hosts
1139
+ * that create the tenant password-less (e.g. Haven, which holds the unlocked
1140
+ * key in its session rather than a password) call this so SDK-driven KeyBag
1141
+ * reconcile can unwrap pushed key versions (docs/accesscontrol.md §13).
1142
+ */
1143
+ setSessionEncryptionKey(cryptoKey) {
1144
+ this.decryptedUserEncryptionKeyCache = cryptoKey;
1145
+ }
1146
+ /**
1147
+ * The RSA-OAEP encryption private key used by KeyBag reconcile to unwrap
1148
+ * pushed key versions. Returns the injected/cached session key when present,
1149
+ * else derives it from the current user password. Returns `null` when neither
1150
+ * is available (a locked, password-less host) so reconcile can skip its import
1151
+ * pass without throwing — the revoke pass needs no key and still runs.
1152
+ */
1153
+ async getEncryptionPrivateKeyForReconcile() {
1154
+ if (this.decryptedUserEncryptionKeyCache) {
1155
+ return this.decryptedUserEncryptionKeyCache;
1156
+ }
1157
+ try {
1158
+ return await this.getDecryptedEncryptionKey();
1159
+ }
1160
+ catch (error) {
1161
+ this.logger.debug(`getEncryptionPrivateKeyForReconcile: no encryption key available (locked host): ${error}`);
1162
+ return null;
1163
+ }
1164
+ }
865
1165
  /**
866
1166
  * Internal method to decrypt a private key using password-based key derivation.
867
1167
  * Protected so that BaseMindooTenantDirectory can access it.
@@ -873,9 +1173,9 @@ class BaseMindooTenant {
873
1173
  * @returns The decrypted private key as ArrayBuffer
874
1174
  */
875
1175
  async decryptPrivateKey(encryptedKey, password, saltString) {
876
- this.logger.debug(`decryptPrivateKey: Starting decryption with saltString: ${saltString}`);
877
- this.logger.debug(`decryptPrivateKey: Password length: ${password.length}`);
878
- this.logger.debug(`decryptPrivateKey: EncryptedKey iterations: ${encryptedKey.iterations}`);
1176
+ // Do NOT log the password length or KDF iteration count (audit, Medium:
1177
+ // sensitive debug logging) — both narrow an offline cracking search space.
1178
+ this.logger.debug(`decryptPrivateKey: Starting decryption (keyCategory: ${saltString})`);
879
1179
  this.logger.debug(`decryptPrivateKey: Delegating PBKDF2 + AES-GCM work to shared helper`);
880
1180
  try {
881
1181
  const decrypted = await (0, privateKeyEncryption_1.decryptPrivateKey)(this.cryptoAdapter, encryptedKey, password, saltString);
@@ -967,6 +1267,238 @@ class BaseMindooTenant {
967
1267
  async getDocKeyFingerprint() {
968
1268
  return this.keyBag.getDocKeyFingerprint(this.tenantId);
969
1269
  }
1270
+ /**
1271
+ * Remove a **named** document key from the local KeyBag. Refuses to delete the
1272
+ * shared tenant default key (`"default"` / {@link DEFAULT_TENANT_KEY_ID}),
1273
+ * which is required for the tenant to function. Returns true if a key was
1274
+ * removed.
1275
+ */
1276
+ async removeNamedDecryptionKey(decryptionKeyId) {
1277
+ if (decryptionKeyId === "default" || decryptionKeyId === types_1.DEFAULT_TENANT_KEY_ID) {
1278
+ this.logger.debug(`removeNamedDecryptionKey: refusing to delete the tenant default key`);
1279
+ return false;
1280
+ }
1281
+ try {
1282
+ await this.keyBag.deleteKey("doc", this.tenantId, decryptionKeyId);
1283
+ this.invalidateDecryptedKeyCaches();
1284
+ return true;
1285
+ }
1286
+ catch (error) {
1287
+ this.logger.warn(`removeNamedDecryptionKey: failed to delete key ${decryptionKeyId}: ${error}`);
1288
+ return false;
1289
+ }
1290
+ }
1291
+ /**
1292
+ * Reconcile the local KeyBag against the directory head for the current user
1293
+ * (docs/accesscontrol.md §13). SDK-driven so standalone apps using the
1294
+ * `mindoodb` package get key distribution/revocation automatically after a
1295
+ * directory sync — not just the Haven UI. Two independent passes:
1296
+ *
1297
+ * - **Revoke (no key required):** bulk-remove every revoked key id (from the
1298
+ * directory head cache) from the bag. Idempotent and a no-op when the bag
1299
+ * does not hold the key, so it also re-cleans a restored older KeyBag
1300
+ * backup on every run. The `deleteKey` mutation drives visibility
1301
+ * reconciliation (forgetting now-inaccessible docs) and emits
1302
+ * `keyBag.onChanges` so the host can persist the cleaned bag.
1303
+ * - **Import (needs the encryption private key):** unwrap and merge the key
1304
+ * versions pushed to this user. Skipped with a debug log when the host is
1305
+ * locked (no session key / password); the revoke pass still ran.
1306
+ *
1307
+ * Best-effort and idempotent; safe to call on every directory bring-up / pull.
1308
+ * Returns the key ids imported and removed.
1309
+ */
1310
+ async reconcileKeyDistributionsForCurrentUser() {
1311
+ const username = this.currentUser.username;
1312
+ const directory = await this.openDirectory();
1313
+ const removed = [];
1314
+ const imported = [];
1315
+ // Revoke pass — no key, no comparison, idempotent.
1316
+ if (typeof directory.getRevokedDecryptionKeyIdsForUser === "function") {
1317
+ let revokedIds = [];
1318
+ try {
1319
+ revokedIds = await directory.getRevokedDecryptionKeyIdsForUser(username);
1320
+ }
1321
+ catch (error) {
1322
+ this.logger.warn(`reconcileKeyDistributionsForCurrentUser: revoked-id lookup failed: ${error}`);
1323
+ }
1324
+ for (const keyId of revokedIds) {
1325
+ if (await this.removeNamedDecryptionKey(keyId)) {
1326
+ removed.push(keyId);
1327
+ }
1328
+ }
1329
+ }
1330
+ // Import pass — sources the encryption private key from this tenant. The
1331
+ // directory exposes this only via the internal KeyBagReconciler contract,
1332
+ // so the key is never passed across the public directory API.
1333
+ const reconciler = directory;
1334
+ if (typeof reconciler.reconcileImportedKeysForCurrentUser === "function") {
1335
+ try {
1336
+ const result = await reconciler.reconcileImportedKeysForCurrentUser(username);
1337
+ imported.push(...result.imported);
1338
+ for (const id of result.removed) {
1339
+ if (!removed.includes(id))
1340
+ removed.push(id);
1341
+ }
1342
+ }
1343
+ catch (error) {
1344
+ this.logger.warn(`reconcileKeyDistributionsForCurrentUser: import pass failed: ${error}`);
1345
+ }
1346
+ }
1347
+ return { imported, removed };
1348
+ }
1349
+ /**
1350
+ * Single-flight, best-effort wrapper around
1351
+ * {@link reconcileKeyDistributionsForCurrentUser} for the run-always trigger
1352
+ * (docs/accesscontrol.md §13). Fired on directory bring-up and after every
1353
+ * directory pull. Never throws and never blocks sync: a reconcile error is
1354
+ * logged and swallowed. The in-flight guard prevents the driver's own
1355
+ * directory access from re-triggering it.
1356
+ */
1357
+ async reconcileKeyDistributionsForCurrentUserSafe() {
1358
+ if (this.reconcileInFlight) {
1359
+ return;
1360
+ }
1361
+ this.reconcileInFlight = true;
1362
+ try {
1363
+ await this.reconcileKeyDistributionsForCurrentUser();
1364
+ }
1365
+ catch (error) {
1366
+ this.logger.warn(`reconcileKeyDistributionsForCurrentUserSafe: ${error}`);
1367
+ }
1368
+ finally {
1369
+ this.reconcileInFlight = false;
1370
+ }
1371
+ }
1372
+ /**
1373
+ * Export **all** stored versions of a document key for an admin-blind key
1374
+ * delivery (read-side key push). A rotated key keeps multiple versions in the
1375
+ * KeyBag and decryption tries them all, so a delivery must carry every version
1376
+ * - otherwise the recipient could not read documents encrypted under an
1377
+ * earlier version. Each entry pairs the raw key bytes with the version's
1378
+ * `createdAt` (used as `keyVersionCreatedAt`); versions are newest-first.
1379
+ * Returns null when the caller's KeyBag lacks the key. Only a key-holder can
1380
+ * run this — never the admin who later publishes the wrapped bytes.
1381
+ */
1382
+ async exportDecryptionKeyForDelivery(decryptionKeyId) {
1383
+ const id = decryptionKeyId === "default" ? types_1.DEFAULT_TENANT_KEY_ID : decryptionKeyId;
1384
+ const versions = await this.keyBag.getAllKeyVersions("doc", this.tenantId, id);
1385
+ if (versions.length === 0) {
1386
+ return null;
1387
+ }
1388
+ return versions.map((v) => ({ bytes: v.key, keyVersionCreatedAt: v.createdAt ?? 0 }));
1389
+ }
1390
+ /**
1391
+ * Import a delivered document key into the local KeyBag (read-side key push,
1392
+ * recipient side). Writing the key triggers the existing KeyBag change
1393
+ * listener -> {@link reconcileKeyBagChanges} -> per-database reveal-on-add, so
1394
+ * documents encrypted with it surface automatically. Idempotent: a key
1395
+ * version with the same `keyVersionCreatedAt` is not duplicated.
1396
+ */
1397
+ async importDeliveredDecryptionKey(keyId, bytes, keyVersionCreatedAt) {
1398
+ await this.importDeliveredDecryptionKeyVersions(keyId, [{ bytes, keyVersionCreatedAt }]);
1399
+ }
1400
+ /**
1401
+ * Import several delivered versions of one document key at once (read-side key
1402
+ * push, recipient side). Each version absent from the KeyBag (matched by
1403
+ * `keyVersionCreatedAt`) is added; already-present versions are skipped so the
1404
+ * call is idempotent. Caches are invalidated and reveal-on-add reconciliation
1405
+ * runs once, after all new versions are written. Returns the number of
1406
+ * versions actually imported.
1407
+ */
1408
+ async importDeliveredDecryptionKeyVersions(keyId, versions) {
1409
+ const id = keyId === "default" ? types_1.DEFAULT_TENANT_KEY_ID : keyId;
1410
+ const scopedKeyId = `doc:${this.tenantId}:${id}`;
1411
+ const existing = await this.keyBag.listKeyDetails();
1412
+ const haveStamps = new Set(existing.filter((d) => d.scopedKeyId === scopedKeyId).map((d) => d.createdAt ?? 0));
1413
+ let importedCount = 0;
1414
+ for (const version of versions) {
1415
+ if (haveStamps.has(version.keyVersionCreatedAt)) {
1416
+ this.logger.debug(`importDeliveredDecryptionKeyVersions: ${keyId}@${version.keyVersionCreatedAt} already present`);
1417
+ continue;
1418
+ }
1419
+ await this.keyBag.set("doc", this.tenantId, id, version.bytes, version.keyVersionCreatedAt || undefined);
1420
+ haveStamps.add(version.keyVersionCreatedAt);
1421
+ importedCount++;
1422
+ }
1423
+ if (importedCount > 0) {
1424
+ this.invalidateDecryptedKeyCaches();
1425
+ await this.reconcileKeyBagChanges();
1426
+ }
1427
+ return importedCount;
1428
+ }
1429
+ /** SHA-256 hex of raw key bytes — the stable version fingerprint used by key distribution. */
1430
+ async fingerprintKeyBytes(bytes) {
1431
+ const subtle = this.getCryptoAdapter().getSubtle();
1432
+ const digest = await subtle.digest("SHA-256", bytes);
1433
+ return Array.from(new Uint8Array(digest))
1434
+ .map((b) => b.toString(16).padStart(2, "0"))
1435
+ .join("");
1436
+ }
1437
+ /**
1438
+ * All stored versions of a document key with their raw bytes, `createdAt`, and
1439
+ * SHA-256 fingerprint (newest first). The source of truth for a key
1440
+ * distribution's `keyVersions` manifest and per-device wrapping. Empty when the
1441
+ * caller's KeyBag lacks the key.
1442
+ */
1443
+ async fingerprintKeyVersions(keyId) {
1444
+ const id = keyId === "default" ? types_1.DEFAULT_TENANT_KEY_ID : keyId;
1445
+ const versions = await this.keyBag.getAllKeyVersions("doc", this.tenantId, id);
1446
+ const result = [];
1447
+ for (const v of versions) {
1448
+ result.push({
1449
+ bytes: v.key,
1450
+ createdAt: v.createdAt ?? 0,
1451
+ fingerprint: await this.fingerprintKeyBytes(v.key),
1452
+ });
1453
+ }
1454
+ return result;
1455
+ }
1456
+ /**
1457
+ * Remove exactly the key versions of `keyId` whose raw-bytes fingerprint is in
1458
+ * `fingerprints` (version-scoped `pullfrom` revocation). Versions obtained
1459
+ * elsewhere — i.e. not in the distribution manifest — survive. Refuses the
1460
+ * protected tenant default / `$publicinfos` keys. The KeyBag change feed
1461
+ * triggers visibility reconciliation (scope purge when nothing remains).
1462
+ * Returns the number of versions removed.
1463
+ */
1464
+ async removeDecryptionKeyVersionsByFingerprint(keyId, fingerprints) {
1465
+ if (keyId === "default" || keyId === types_1.DEFAULT_TENANT_KEY_ID || keyId === types_1.PUBLIC_INFOS_KEY_ID) {
1466
+ this.logger.debug(`removeDecryptionKeyVersionsByFingerprint: refusing protected key ${keyId}`);
1467
+ return 0;
1468
+ }
1469
+ const target = new Set(fingerprints);
1470
+ if (target.size === 0)
1471
+ return 0;
1472
+ const id = keyId === "default" ? types_1.DEFAULT_TENANT_KEY_ID : keyId;
1473
+ let removed = 0;
1474
+ // Re-read versions each pass: deleteKeyVersion is index-based against the
1475
+ // current sorted list, which shifts as entries are removed.
1476
+ // eslint-disable-next-line no-constant-condition
1477
+ while (true) {
1478
+ const versions = await this.keyBag.getAllKeyVersions("doc", this.tenantId, id);
1479
+ let removedThisPass = false;
1480
+ for (let index = 0; index < versions.length; index++) {
1481
+ const fp = await this.fingerprintKeyBytes(versions[index].key);
1482
+ if (target.has(fp)) {
1483
+ await this.keyBag.deleteKeyVersion("doc", this.tenantId, id, index);
1484
+ removed++;
1485
+ removedThisPass = true;
1486
+ break;
1487
+ }
1488
+ }
1489
+ if (!removedThisPass)
1490
+ break;
1491
+ }
1492
+ if (removed > 0) {
1493
+ this.invalidateDecryptedKeyCaches();
1494
+ await this.reconcileKeyBagChanges();
1495
+ }
1496
+ return removed;
1497
+ }
1498
+ /** Clear in-memory decrypted-key caches after a KeyBag mutation. */
1499
+ invalidateDecryptedKeyCaches() {
1500
+ this.decryptedTenantKeyCache = undefined;
1501
+ }
970
1502
  /**
971
1503
  * Get the symmetric key for a given key ID.
972
1504
  * This is a protected helper method that can be used by subclasses.
@@ -1094,23 +1626,36 @@ class BaseMindooTenant {
1094
1626
  const iv = encryptedPayload.slice(1, 13);
1095
1627
  // Extract ciphertext (bytes 13 onwards)
1096
1628
  const ciphertext = encryptedPayload.slice(13);
1097
- const symmetricKey = await this.getSymmetricKey(decryptionKeyId);
1629
+ // Try every stored version of the key (newest first) so attachments
1630
+ // encrypted under an earlier rotation of the key still decrypt.
1631
+ const candidates = await this.getDecryptionKeyCandidates(decryptionKeyId);
1632
+ if (candidates.length === 0) {
1633
+ throw new errors_1.SymmetricKeyNotFoundError(decryptionKeyId === "default" ? `doc:${this.tenantId}:${types_1.DEFAULT_TENANT_KEY_ID}` : decryptionKeyId);
1634
+ }
1098
1635
  const subtle = this.cryptoAdapter.getSubtle();
1099
- // Import the symmetric key
1100
- const keyArray = new Uint8Array(symmetricKey);
1101
- const cryptoKey = await subtle.importKey("raw", keyArray.buffer, { name: "AES-GCM" }, false, ["decrypt"]);
1102
- // Decrypt
1103
- const decrypted = await subtle.decrypt({
1104
- name: "AES-GCM",
1105
- iv: iv,
1106
- tagLength: 128,
1107
- }, cryptoKey, ciphertext);
1108
- const result = new Uint8Array(decrypted);
1109
- this.logger.debug(`Decrypted attachment (${encryptedPayload.length} -> ${result.length} bytes)`);
1110
- return result;
1636
+ let lastError = undefined;
1637
+ for (const symmetricKey of candidates) {
1638
+ try {
1639
+ const cryptoKey = await subtle.importKey("raw", new Uint8Array(symmetricKey).buffer, { name: "AES-GCM" }, false, ["decrypt"]);
1640
+ const decrypted = await subtle.decrypt({
1641
+ name: "AES-GCM",
1642
+ iv: iv,
1643
+ tagLength: 128,
1644
+ }, cryptoKey, ciphertext);
1645
+ const result = new Uint8Array(decrypted);
1646
+ this.logger.debug(`Decrypted attachment (${encryptedPayload.length} -> ${result.length} bytes)`);
1647
+ return result;
1648
+ }
1649
+ catch (error) {
1650
+ lastError = error;
1651
+ }
1652
+ }
1653
+ this.logger.debug(`Attachment decrypt failed against all ${candidates.length} version(s) of key "${decryptionKeyId}"`);
1654
+ throw lastError ?? new errors_1.SymmetricKeyNotFoundError(decryptionKeyId);
1111
1655
  }
1112
1656
  }
1113
1657
  exports.BaseMindooTenant = BaseMindooTenant;
1658
+ BaseMindooTenant.MAX_IMPORTED_KEY_CACHE_ENTRIES = 128;
1114
1659
  // ============================================================================
1115
1660
  // Attachment Encryption Methods
1116
1661
  // ============================================================================