mindoodb 0.0.85 → 0.0.88

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 (319) hide show
  1. package/README-server.md +6 -2
  2. package/README.md +6 -4
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +2 -1
  5. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  6. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +151 -12
  7. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  8. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +340 -24
  9. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  10. package/dist/browser/index.mjs +14 -0
  11. package/dist/cloudflare/DurableObjectSql.d.ts +56 -0
  12. package/dist/cloudflare/DurableObjectSql.d.ts.map +1 -0
  13. package/dist/cloudflare/DurableObjectSql.js +180 -0
  14. package/dist/cloudflare/DurableObjectSql.js.map +1 -0
  15. package/dist/cloudflare/MindooDatabaseObject.d.ts +86 -0
  16. package/dist/cloudflare/MindooDatabaseObject.d.ts.map +1 -0
  17. package/dist/cloudflare/MindooDatabaseObject.js +614 -0
  18. package/dist/cloudflare/MindooDatabaseObject.js.map +1 -0
  19. package/dist/cloudflare/MindooServerObject.d.ts +63 -0
  20. package/dist/cloudflare/MindooServerObject.d.ts.map +1 -0
  21. package/dist/cloudflare/MindooServerObject.js +521 -0
  22. package/dist/cloudflare/MindooServerObject.js.map +1 -0
  23. package/dist/cloudflare/WorkersCryptoAdapter.d.ts +7 -0
  24. package/dist/cloudflare/WorkersCryptoAdapter.d.ts.map +1 -0
  25. package/dist/cloudflare/WorkersCryptoAdapter.js +14 -0
  26. package/dist/cloudflare/WorkersCryptoAdapter.js.map +1 -0
  27. package/dist/cloudflare/host.d.ts +49 -0
  28. package/dist/cloudflare/host.d.ts.map +1 -0
  29. package/dist/cloudflare/host.js +104 -0
  30. package/dist/cloudflare/host.js.map +1 -0
  31. package/dist/cloudflare/http.d.ts +91 -0
  32. package/dist/cloudflare/http.d.ts.map +1 -0
  33. package/dist/cloudflare/http.js +266 -0
  34. package/dist/cloudflare/http.js.map +1 -0
  35. package/dist/cloudflare/index.d.ts +12 -0
  36. package/dist/cloudflare/index.d.ts.map +1 -0
  37. package/dist/cloudflare/index.js +20 -0
  38. package/dist/cloudflare/index.js.map +1 -0
  39. package/dist/cloudflare/storeRpc.d.ts +22 -0
  40. package/dist/cloudflare/storeRpc.d.ts.map +1 -0
  41. package/dist/cloudflare/storeRpc.js +85 -0
  42. package/dist/cloudflare/storeRpc.js.map +1 -0
  43. package/dist/cloudflare/types.d.ts +101 -0
  44. package/dist/cloudflare/types.d.ts.map +1 -0
  45. package/dist/cloudflare/types.js +10 -0
  46. package/dist/cloudflare/types.js.map +1 -0
  47. package/dist/cloudflare/worker.d.ts +25 -0
  48. package/dist/cloudflare/worker.d.ts.map +1 -0
  49. package/dist/cloudflare/worker.js +170 -0
  50. package/dist/cloudflare/worker.js.map +1 -0
  51. package/dist/core/BaseMindooDB.d.ts +95 -10
  52. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  53. package/dist/core/BaseMindooDB.js +441 -98
  54. package/dist/core/BaseMindooDB.js.map +1 -1
  55. package/dist/core/BaseMindooTenant.d.ts +43 -0
  56. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  57. package/dist/core/BaseMindooTenant.js +157 -6
  58. package/dist/core/BaseMindooTenant.js.map +1 -1
  59. package/dist/core/BaseMindooTenantDirectory.d.ts +103 -0
  60. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  61. package/dist/core/BaseMindooTenantDirectory.js +371 -8
  62. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  63. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  64. package/dist/core/BaseMindooTenantFactory.js +2 -1
  65. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  66. package/dist/core/ChangeDocDraft.d.ts +133 -0
  67. package/dist/core/ChangeDocDraft.d.ts.map +1 -0
  68. package/dist/core/ChangeDocDraft.js +492 -0
  69. package/dist/core/ChangeDocDraft.js.map +1 -0
  70. package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
  71. package/dist/core/MindooDBServerAdmin.js +2 -1
  72. package/dist/core/MindooDBServerAdmin.js.map +1 -1
  73. package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
  74. package/dist/core/accesscontrol/directoryProjection.js +19 -0
  75. package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
  76. package/dist/core/accesscontrol/evaluate.d.ts +14 -0
  77. package/dist/core/accesscontrol/evaluate.d.ts.map +1 -1
  78. package/dist/core/accesscontrol/evaluate.js +42 -1
  79. package/dist/core/accesscontrol/evaluate.js.map +1 -1
  80. package/dist/core/accesscontrol/types.d.ts +88 -4
  81. package/dist/core/accesscontrol/types.d.ts.map +1 -1
  82. package/dist/core/accesscontrol/types.js +114 -1
  83. package/dist/core/accesscontrol/types.js.map +1 -1
  84. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +14 -0
  85. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  86. package/dist/core/appendonlystores/network/AuthenticationService.js +27 -7
  87. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  88. package/dist/core/appendonlystores/network/IrohNetworkTransport.d.ts.map +1 -1
  89. package/dist/core/appendonlystores/network/IrohNetworkTransport.js +12 -0
  90. package/dist/core/appendonlystores/network/IrohNetworkTransport.js.map +1 -1
  91. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts.map +1 -1
  92. package/dist/core/appendonlystores/network/IrohStreamIO.js +3 -23
  93. package/dist/core/appendonlystores/network/IrohStreamIO.js.map +1 -1
  94. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.d.ts +6 -0
  95. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.d.ts.map +1 -1
  96. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.js +20 -1
  97. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.js.map +1 -1
  98. package/dist/core/appendonlystores/rpc/storeRpcCodec.d.ts.map +1 -1
  99. package/dist/core/appendonlystores/rpc/storeRpcCodec.js +3 -23
  100. package/dist/core/appendonlystores/rpc/storeRpcCodec.js.map +1 -1
  101. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts +7 -0
  102. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts.map +1 -1
  103. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js +39 -30
  104. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js.map +1 -1
  105. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts +37 -1
  106. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts.map +1 -1
  107. package/dist/core/appendonlystores/sqlite/SqliteDriver.js +99 -40
  108. package/dist/core/appendonlystores/sqlite/SqliteDriver.js.map +1 -1
  109. package/dist/core/appendonlystores/syncStores.d.ts.map +1 -1
  110. package/dist/core/appendonlystores/syncStores.js +25 -5
  111. package/dist/core/appendonlystores/syncStores.js.map +1 -1
  112. package/dist/core/appendonlystores/types.d.ts +7 -1
  113. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  114. package/dist/core/appendonlystores/types.js +1 -0
  115. package/dist/core/appendonlystores/types.js.map +1 -1
  116. package/dist/core/builtinDbInvariants.d.ts +49 -0
  117. package/dist/core/builtinDbInvariants.d.ts.map +1 -1
  118. package/dist/core/builtinDbInvariants.js +86 -1
  119. package/dist/core/builtinDbInvariants.js.map +1 -1
  120. package/dist/core/crypto/AuthChallenge.d.ts +68 -0
  121. package/dist/core/crypto/AuthChallenge.d.ts.map +1 -0
  122. package/dist/core/crypto/AuthChallenge.js +118 -0
  123. package/dist/core/crypto/AuthChallenge.js.map +1 -0
  124. package/dist/core/crypto/DomainStatement.d.ts +5 -3
  125. package/dist/core/crypto/DomainStatement.d.ts.map +1 -1
  126. package/dist/core/crypto/DomainStatement.js +5 -3
  127. package/dist/core/crypto/DomainStatement.js.map +1 -1
  128. package/dist/core/crypto/encryptedFields.d.ts +11 -0
  129. package/dist/core/crypto/encryptedFields.d.ts.map +1 -1
  130. package/dist/core/crypto/encryptedFields.js +18 -0
  131. package/dist/core/crypto/encryptedFields.js.map +1 -1
  132. package/dist/core/expressions/evaluateExpression.d.ts +8 -0
  133. package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
  134. package/dist/core/expressions/evaluateExpression.js +29 -4
  135. package/dist/core/expressions/evaluateExpression.js.map +1 -1
  136. package/dist/core/expressions/metadata.js +4 -4
  137. package/dist/core/expressions/metadata.js.map +1 -1
  138. package/dist/core/index.d.ts +3 -3
  139. package/dist/core/index.d.ts.map +1 -1
  140. package/dist/core/index.js +17 -6
  141. package/dist/core/index.js.map +1 -1
  142. package/dist/core/query/executeQuery.d.ts.map +1 -1
  143. package/dist/core/query/executeQuery.js +5 -1
  144. package/dist/core/query/executeQuery.js.map +1 -1
  145. package/dist/core/query/executeQueryInclude.d.ts.map +1 -1
  146. package/dist/core/query/executeQueryInclude.js +2 -0
  147. package/dist/core/query/executeQueryInclude.js.map +1 -1
  148. package/dist/core/query/queryView.d.ts.map +1 -1
  149. package/dist/core/query/queryView.js +5 -1
  150. package/dist/core/query/queryView.js.map +1 -1
  151. package/dist/core/types.d.ts +204 -10
  152. package/dist/core/types.d.ts.map +1 -1
  153. package/dist/core/types.js.map +1 -1
  154. package/dist/core/uri/MindooURI.d.ts.map +1 -1
  155. package/dist/core/uri/MindooURI.js +3 -24
  156. package/dist/core/uri/MindooURI.js.map +1 -1
  157. package/dist/core/userkeys/index.d.ts +1 -0
  158. package/dist/core/userkeys/index.d.ts.map +1 -1
  159. package/dist/core/userkeys/index.js +5 -1
  160. package/dist/core/userkeys/index.js.map +1 -1
  161. package/dist/core/userkeys/sealedValues.d.ts +54 -0
  162. package/dist/core/userkeys/sealedValues.d.ts.map +1 -0
  163. package/dist/core/userkeys/sealedValues.js +67 -0
  164. package/dist/core/userkeys/sealedValues.js.map +1 -0
  165. package/dist/core/utils/base64.d.ts +17 -0
  166. package/dist/core/utils/base64.d.ts.map +1 -0
  167. package/dist/core/utils/base64.js +54 -0
  168. package/dist/core/utils/base64.js.map +1 -0
  169. package/dist/core/utils/dynamicImportInterop.d.ts +10 -0
  170. package/dist/core/utils/dynamicImportInterop.d.ts.map +1 -0
  171. package/dist/core/utils/dynamicImportInterop.js +22 -0
  172. package/dist/core/utils/dynamicImportInterop.js.map +1 -0
  173. package/dist/core/utils/idGeneration.d.ts.map +1 -1
  174. package/dist/core/utils/idGeneration.js +14 -3
  175. package/dist/core/utils/idGeneration.js.map +1 -1
  176. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -1
  177. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +7 -0
  178. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -1
  179. package/dist/node/server/FsServerDataStore.d.ts +23 -0
  180. package/dist/node/server/FsServerDataStore.d.ts.map +1 -0
  181. package/dist/node/server/FsServerDataStore.js +59 -0
  182. package/dist/node/server/FsServerDataStore.js.map +1 -0
  183. package/dist/node/server/IrohServerEndpoint.d.ts +1 -1
  184. package/dist/node/server/IrohServerEndpoint.d.ts.map +1 -1
  185. package/dist/node/server/IrohServerRpc.d.ts +5 -5
  186. package/dist/node/server/IrohServerRpc.d.ts.map +1 -1
  187. package/dist/node/server/IrohServerRpc.js +1 -1
  188. package/dist/node/server/IrohServerRpc.js.map +1 -1
  189. package/dist/node/server/MindooDBServer.d.ts +29 -150
  190. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  191. package/dist/node/server/MindooDBServer.js +160 -1573
  192. package/dist/node/server/MindooDBServer.js.map +1 -1
  193. package/dist/node/server/PurgedDocRegistry.d.ts +4 -27
  194. package/dist/node/server/PurgedDocRegistry.d.ts.map +1 -1
  195. package/dist/node/server/PurgedDocRegistry.js +6 -83
  196. package/dist/node/server/PurgedDocRegistry.js.map +1 -1
  197. package/dist/node/server/StoreFactory.d.ts +1 -1
  198. package/dist/node/server/StoreFactory.d.ts.map +1 -1
  199. package/dist/node/server/TenantManager.d.ts +16 -284
  200. package/dist/node/server/TenantManager.d.ts.map +1 -1
  201. package/dist/node/server/TenantManager.js +35 -1168
  202. package/dist/node/server/TenantManager.js.map +1 -1
  203. package/dist/node/server/add-to-network.js +1 -1
  204. package/dist/node/server/add-to-network.js.map +1 -1
  205. package/dist/node/server/config.d.ts +11 -15
  206. package/dist/node/server/config.d.ts.map +1 -1
  207. package/dist/node/server/config.js +30 -245
  208. package/dist/node/server/config.js.map +1 -1
  209. package/dist/node/server/expressAdapter.d.ts +23 -0
  210. package/dist/node/server/expressAdapter.d.ts.map +1 -0
  211. package/dist/node/server/expressAdapter.js +108 -0
  212. package/dist/node/server/expressAdapter.js.map +1 -0
  213. package/dist/node/server/nativeIrohStreamIO.d.ts +1 -1
  214. package/dist/node/server/nativeIrohStreamIO.d.ts.map +1 -1
  215. package/dist/node/server/peer/FileClusterAuditStorage.d.ts +16 -0
  216. package/dist/node/server/peer/FileClusterAuditStorage.d.ts.map +1 -0
  217. package/dist/node/server/peer/FileClusterAuditStorage.js +39 -0
  218. package/dist/node/server/peer/FileClusterAuditStorage.js.map +1 -0
  219. package/dist/node/server/peer/IrohPeerLink.d.ts +1 -1
  220. package/dist/node/server/peer/IrohPeerLink.d.ts.map +1 -1
  221. package/dist/node/server/peer/routes.d.ts +10 -44
  222. package/dist/node/server/peer/routes.d.ts.map +1 -1
  223. package/dist/node/server/peer/routes.js +33 -275
  224. package/dist/node/server/peer/routes.js.map +1 -1
  225. package/dist/node/server/resolveServerPassword.js +1 -1
  226. package/dist/node/server/resolveServerPassword.js.map +1 -1
  227. package/dist/node/server/server.js +1 -1
  228. package/dist/node/server/server.js.map +1 -1
  229. package/dist/node/server/serverinit.js +1 -1
  230. package/dist/node/server/serverinit.js.map +1 -1
  231. package/dist/server/CapabilityMatcher.d.ts +60 -0
  232. package/dist/server/CapabilityMatcher.d.ts.map +1 -0
  233. package/dist/server/CapabilityMatcher.js +138 -0
  234. package/dist/server/CapabilityMatcher.js.map +1 -0
  235. package/dist/server/DeviceDiscoveryService.d.ts +36 -0
  236. package/dist/server/DeviceDiscoveryService.d.ts.map +1 -0
  237. package/dist/server/DeviceDiscoveryService.js +164 -0
  238. package/dist/server/DeviceDiscoveryService.js.map +1 -0
  239. package/dist/server/PurgedDocRegistry.d.ts +35 -0
  240. package/dist/server/PurgedDocRegistry.d.ts.map +1 -0
  241. package/dist/server/PurgedDocRegistry.js +90 -0
  242. package/dist/server/PurgedDocRegistry.js.map +1 -0
  243. package/dist/server/ServerDataStore.d.ts +67 -0
  244. package/dist/server/ServerDataStore.d.ts.map +1 -0
  245. package/dist/server/ServerDataStore.js +88 -0
  246. package/dist/server/ServerDataStore.js.map +1 -0
  247. package/dist/server/SyncEventBus.d.ts +39 -0
  248. package/dist/server/SyncEventBus.d.ts.map +1 -0
  249. package/dist/server/SyncEventBus.js +41 -0
  250. package/dist/server/SyncEventBus.js.map +1 -0
  251. package/dist/server/SystemAdminAuth.d.ts +82 -0
  252. package/dist/server/SystemAdminAuth.d.ts.map +1 -0
  253. package/dist/server/SystemAdminAuth.js +227 -0
  254. package/dist/server/SystemAdminAuth.js.map +1 -0
  255. package/dist/server/TenantManager.d.ts +366 -0
  256. package/dist/server/TenantManager.d.ts.map +1 -0
  257. package/dist/server/TenantManager.js +1416 -0
  258. package/dist/server/TenantManager.js.map +1 -0
  259. package/dist/server/http/MindooDBServerCore.d.ts +313 -0
  260. package/dist/server/http/MindooDBServerCore.d.ts.map +1 -0
  261. package/dist/server/http/MindooDBServerCore.js +1410 -0
  262. package/dist/server/http/MindooDBServerCore.js.map +1 -0
  263. package/dist/server/http/types.d.ts +89 -0
  264. package/dist/server/http/types.d.ts.map +1 -0
  265. package/dist/server/http/types.js +29 -0
  266. package/dist/server/http/types.js.map +1 -0
  267. package/dist/server/index.d.ts +32 -0
  268. package/dist/server/index.d.ts.map +1 -0
  269. package/dist/server/index.js +76 -0
  270. package/dist/server/index.js.map +1 -0
  271. package/dist/server/peer/ClusterAuditLog.d.ts +71 -0
  272. package/dist/server/peer/ClusterAuditLog.d.ts.map +1 -0
  273. package/dist/server/peer/ClusterAuditLog.js +81 -0
  274. package/dist/server/peer/ClusterAuditLog.js.map +1 -0
  275. package/dist/server/peer/ClusterJobRunner.d.ts +43 -0
  276. package/dist/server/peer/ClusterJobRunner.d.ts.map +1 -0
  277. package/dist/server/peer/ClusterJobRunner.js +119 -0
  278. package/dist/server/peer/ClusterJobRunner.js.map +1 -0
  279. package/dist/server/peer/ClusterManager.d.ts +149 -0
  280. package/dist/server/peer/ClusterManager.d.ts.map +1 -0
  281. package/dist/server/peer/ClusterManager.js +292 -0
  282. package/dist/server/peer/ClusterManager.js.map +1 -0
  283. package/dist/server/peer/PeerAuthService.d.ts +69 -0
  284. package/dist/server/peer/PeerAuthService.d.ts.map +1 -0
  285. package/dist/server/peer/PeerAuthService.js +207 -0
  286. package/dist/server/peer/PeerAuthService.js.map +1 -0
  287. package/dist/server/peer/PeerReplicator.d.ts +303 -0
  288. package/dist/server/peer/PeerReplicator.d.ts.map +1 -0
  289. package/dist/server/peer/PeerReplicator.js +1072 -0
  290. package/dist/server/peer/PeerReplicator.js.map +1 -0
  291. package/dist/server/peer/clusterHttp.d.ts +113 -0
  292. package/dist/server/peer/clusterHttp.d.ts.map +1 -0
  293. package/dist/server/peer/clusterHttp.js +290 -0
  294. package/dist/server/peer/clusterHttp.js.map +1 -0
  295. package/dist/server/peer/types.d.ts +244 -0
  296. package/dist/server/peer/types.d.ts.map +1 -0
  297. package/dist/server/peer/types.js +189 -0
  298. package/dist/server/peer/types.js.map +1 -0
  299. package/dist/server/rateLimits.d.ts +119 -0
  300. package/dist/server/rateLimits.d.ts.map +1 -0
  301. package/dist/server/rateLimits.js +159 -0
  302. package/dist/server/rateLimits.js.map +1 -0
  303. package/dist/server/serverConfig.d.ts +37 -0
  304. package/dist/server/serverConfig.d.ts.map +1 -0
  305. package/dist/server/serverConfig.js +251 -0
  306. package/dist/server/serverConfig.js.map +1 -0
  307. package/dist/server/timestampProxy.d.ts +129 -0
  308. package/dist/server/timestampProxy.d.ts.map +1 -0
  309. package/dist/server/timestampProxy.js +361 -0
  310. package/dist/server/timestampProxy.js.map +1 -0
  311. package/dist/server/types.d.ts +267 -0
  312. package/dist/server/types.d.ts.map +1 -0
  313. package/dist/server/types.js +56 -0
  314. package/dist/server/types.js.map +1 -0
  315. package/dist/server/validation.d.ts +43 -0
  316. package/dist/server/validation.d.ts.map +1 -0
  317. package/dist/server/validation.js +104 -0
  318. package/dist/server/validation.js.map +1 -0
  319. package/package.json +9 -1
@@ -0,0 +1,1416 @@
1
+ "use strict";
2
+ /**
3
+ * TenantManager handles loading, caching, and registration of tenants.
4
+ *
5
+ * Uses a global server identity (one per server, not per tenant) for
6
+ * authenticating with remote servers and opening tenant directories.
7
+ *
8
+ * When a tenant has a $publicinfos key (sent during publishToServer), the
9
+ * manager creates a real BaseMindooTenant for directory reading. User
10
+ * authentication is then validated against the admin-signed directory DB
11
+ * rather than a static JSON config.
12
+ *
13
+ * When no $publicinfos key is available, a SimpleMindooDirectory backed
14
+ * by config.json users[] is used as a fallback (useful for tests).
15
+ *
16
+ * A CompositeMindooDirectory wraps whichever directory source is used and
17
+ * also checks the global trusted-servers list, enabling server-to-server
18
+ * auth without servers being in each tenant's user directory.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.TenantManager = exports.SimpleMindooDirectory = void 0;
22
+ exports.lazyContentAddressedStore = lazyContentAddressedStore;
23
+ const AuthenticationService_1 = require("../core/appendonlystores/network/AuthenticationService");
24
+ const ServerNetworkContentAddressedStore_1 = require("../appendonlystores/network/ServerNetworkContentAddressedStore");
25
+ const PurgedDocRegistry_1 = require("./PurgedDocRegistry");
26
+ const purgeDocumentHistory_1 = require("../core/appendonlystores/purgeDocumentHistory");
27
+ const Ed25519WitnessProvider_1 = require("../core/accesscontrol/timestamp/Ed25519WitnessProvider");
28
+ const BaseMindooTenantFactory_1 = require("../core/BaseMindooTenantFactory");
29
+ const RSAEncryption_1 = require("../core/crypto/RSAEncryption");
30
+ const privateKeyEncryption_1 = require("../core/crypto/privateKeyEncryption");
31
+ const KeyBag_1 = require("../core/keys/KeyBag");
32
+ const types_1 = require("../core/types");
33
+ const builtinDbInvariants_1 = require("../core/builtinDbInvariants");
34
+ const DirectoryStateNode_1 = require("../core/accesscontrol/DirectoryStateNode");
35
+ const tier1Evaluator_1 = require("../core/accesscontrol/tier1Evaluator");
36
+ const base64_1 = require("../core/utils/base64");
37
+ // ---------------------------------------------------------------------------
38
+ // StoreFactoryAdapter
39
+ // ---------------------------------------------------------------------------
40
+ class StoreFactoryAdapter {
41
+ constructor(storeFactory) {
42
+ this.storeFactory = storeFactory;
43
+ }
44
+ createStore(dbId, _options) {
45
+ return {
46
+ docStore: this.storeFactory.getStore(dbId, types_1.StoreKind.docs),
47
+ attachmentStore: this.storeFactory.getStore(dbId, types_1.StoreKind.attachments),
48
+ };
49
+ }
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // SimpleMindooDirectory — config-based fallback
53
+ // ---------------------------------------------------------------------------
54
+ /** Document-id prefix of a document-history purge request in `directory` (§13). */
55
+ const DIRECTORY_PURGE_REQUEST_ID_PREFIX = "acl_dochistorypurge_";
56
+ class SimpleMindooDirectory {
57
+ constructor(config) {
58
+ /** Canonical index: entries are identified by their signing public key. */
59
+ this.usersByKey = new Map();
60
+ /**
61
+ * Backward-compat index by documentation-only username, populated only for
62
+ * config entries that carry a `username`. Lets legacy username-based
63
+ * challenges keep resolving; `username` is never required.
64
+ */
65
+ this.usersByUsername = new Map();
66
+ this.revokedUsers = new Set();
67
+ this.adminSigningPublicKey = config.adminSigningPublicKey;
68
+ if (config.users) {
69
+ for (const user of config.users) {
70
+ // Identity is the signing key. `username` is documentation-only and
71
+ // ignored for matching; index it only as a legacy convenience.
72
+ this.usersByKey.set(user.signingPublicKey, user);
73
+ if (typeof user.username === "string" && user.username.trim()) {
74
+ this.usersByUsername.set(user.username.toLowerCase(), user);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ async getUserPublicKeys(username) {
80
+ const normalizedUsername = username.toLowerCase();
81
+ if (this.revokedUsers.has(normalizedUsername)) {
82
+ return null;
83
+ }
84
+ const user = this.usersByUsername.get(normalizedUsername);
85
+ if (!user) {
86
+ return null;
87
+ }
88
+ return {
89
+ signingPublicKey: user.signingPublicKey,
90
+ encryptionPublicKey: user.encryptionPublicKey,
91
+ };
92
+ }
93
+ async getUserBySigningPublicKey(publicKey) {
94
+ const exact = this.usersByKey.get(publicKey);
95
+ if (exact) {
96
+ return this.toLookup(exact);
97
+ }
98
+ const needle = publicKey.replace(/\s+/g, "");
99
+ if (!needle)
100
+ return null;
101
+ for (const [key, user] of this.usersByKey) {
102
+ if (key.replace(/\s+/g, "") === needle) {
103
+ return this.toLookup(user);
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ toLookup(user) {
109
+ if (typeof user.username === "string" &&
110
+ this.revokedUsers.has(user.username.toLowerCase())) {
111
+ return null;
112
+ }
113
+ return {
114
+ username: typeof user.username === "string" ? user.username : "",
115
+ signingPublicKey: user.signingPublicKey,
116
+ encryptionPublicKey: user.encryptionPublicKey,
117
+ details: null,
118
+ };
119
+ }
120
+ async isUserRevoked(username) {
121
+ return this.revokedUsers.has(username.toLowerCase());
122
+ }
123
+ async validatePublicSigningKey(publicKey, _opts) {
124
+ if (publicKey === this.adminSigningPublicKey) {
125
+ return true;
126
+ }
127
+ const user = this.usersByKey.get(publicKey);
128
+ if (!user) {
129
+ return false;
130
+ }
131
+ if (typeof user.username === "string" &&
132
+ this.revokedUsers.has(user.username.toLowerCase())) {
133
+ return false;
134
+ }
135
+ return true;
136
+ }
137
+ }
138
+ exports.SimpleMindooDirectory = SimpleMindooDirectory;
139
+ // ---------------------------------------------------------------------------
140
+ // CompositeMindooDirectory — wraps tenant directory + trusted servers
141
+ // ---------------------------------------------------------------------------
142
+ /**
143
+ * Wraps a per-tenant directory (real or config-based) and also checks
144
+ * the global trusted-servers list. Holds a reference to the array so
145
+ * runtime changes via the admin API are immediately visible.
146
+ */
147
+ class CompositeMindooDirectory {
148
+ constructor(inner, trustedServers, adminBootstrapIdentity) {
149
+ this.inner = inner;
150
+ this.trustedServers = trustedServers;
151
+ this.adminBootstrapIdentity = adminBootstrapIdentity;
152
+ this.adminUsernameNormalized =
153
+ adminBootstrapIdentity?.username.toLowerCase() ?? null;
154
+ }
155
+ async getUserPublicKeys(username) {
156
+ const normalizedUsername = username.toLowerCase();
157
+ // Bootstrap special-case: allow configured admin identity to authenticate
158
+ // even before directory grantaccess docs are present on the server.
159
+ if (this.adminBootstrapIdentity &&
160
+ this.adminUsernameNormalized &&
161
+ normalizedUsername === this.adminUsernameNormalized) {
162
+ return {
163
+ signingPublicKey: this.adminBootstrapIdentity.signingPublicKey,
164
+ encryptionPublicKey: this.adminBootstrapIdentity.encryptionPublicKey,
165
+ };
166
+ }
167
+ const result = await this.inner.getUserPublicKeys(username);
168
+ if (result)
169
+ return result;
170
+ for (const server of this.trustedServers) {
171
+ if (server.name.toLowerCase() === normalizedUsername) {
172
+ return {
173
+ signingPublicKey: server.signingPublicKey,
174
+ encryptionPublicKey: server.encryptionPublicKey,
175
+ };
176
+ }
177
+ }
178
+ return null;
179
+ }
180
+ async isUserRevoked(username) {
181
+ const normalizedUsername = username.toLowerCase();
182
+ if (this.adminUsernameNormalized &&
183
+ normalizedUsername === this.adminUsernameNormalized) {
184
+ return false;
185
+ }
186
+ for (const server of this.trustedServers) {
187
+ if (server.name.toLowerCase() === normalizedUsername) {
188
+ return false;
189
+ }
190
+ }
191
+ return this.inner.isUserRevoked(username);
192
+ }
193
+ async validatePublicSigningKey(publicKey, opts) {
194
+ if (this.adminBootstrapIdentity &&
195
+ (0, DirectoryStateNode_1.signingKeysEqual)(this.adminBootstrapIdentity.signingPublicKey, publicKey)) {
196
+ return true;
197
+ }
198
+ const innerResult = await this.inner.validatePublicSigningKey(publicKey, opts);
199
+ if (innerResult)
200
+ return true;
201
+ for (const server of this.trustedServers) {
202
+ if (server.signingPublicKey === publicKey) {
203
+ return true;
204
+ }
205
+ }
206
+ return false;
207
+ }
208
+ // -----------------------------------------------------------------------
209
+ // Advanced directory features forwarded to the inner directory. Config-based
210
+ // inner directories (SimpleMindooDirectory) do not implement these, so the
211
+ // composite degrades gracefully (no key-based identity, no read policy, no
212
+ // wipe directives) — preserving legacy config-only behavior — while a real
213
+ // BaseMindooTenantDirectory inner enables the key-based auth, read gate, and
214
+ // remote-wipe paths (docs/accesscontrol.md §6.5).
215
+ // -----------------------------------------------------------------------
216
+ async getUserBySigningPublicKey(publicKey) {
217
+ if (typeof this.inner.getUserBySigningPublicKey === "function") {
218
+ const innerLookup = await this.inner.getUserBySigningPublicKey(publicKey);
219
+ if (innerLookup)
220
+ return innerLookup;
221
+ }
222
+ // Bootstrap/config fallback: the admin and trusted-server identities can be
223
+ // resolved by their signing key even without a grantaccess document on this
224
+ // server, mirroring getUserPublicKeys. Without this, key-based identity
225
+ // resolution (and the validateToken active-key check below) would treat the
226
+ // admin's own key as unknown the moment the directory has no admin grant.
227
+ if (this.adminBootstrapIdentity &&
228
+ (0, DirectoryStateNode_1.signingKeysEqual)(this.adminBootstrapIdentity.signingPublicKey, publicKey)) {
229
+ return {
230
+ username: this.adminBootstrapIdentity.username,
231
+ signingPublicKey: this.adminBootstrapIdentity.signingPublicKey,
232
+ encryptionPublicKey: this.adminBootstrapIdentity.encryptionPublicKey,
233
+ details: null,
234
+ };
235
+ }
236
+ for (const server of this.trustedServers) {
237
+ if (server.signingPublicKey === publicKey) {
238
+ return {
239
+ username: server.name,
240
+ signingPublicKey: server.signingPublicKey,
241
+ encryptionPublicKey: server.encryptionPublicKey,
242
+ details: null,
243
+ };
244
+ }
245
+ }
246
+ return null;
247
+ }
248
+ async getUserSigningKeyUniverse(username) {
249
+ const base = typeof this.inner.getUserSigningKeyUniverse === "function"
250
+ ? await this.inner.getUserSigningKeyUniverse(username)
251
+ : { active: [], wipeRequested: [] };
252
+ // The admin bootstrap identity, trusted servers, and config-based users are
253
+ // valid principals even without a grantaccess document on this server (the
254
+ // server config / bootstrap identity is the root of trust). Their signing
255
+ // key must therefore count as ACTIVE so the token-validation gate in
256
+ // AuthenticationService.validateToken (which requires the device key to be
257
+ // in this active set) does not reject a freshly-issued admin/config token.
258
+ // We reuse getUserPublicKeys, which already applies exactly those fallbacks.
259
+ const active = new Set(base.active);
260
+ const fallback = await this.getUserPublicKeys(username);
261
+ if (fallback)
262
+ active.add(fallback.signingPublicKey);
263
+ return { active: Array.from(active), wipeRequested: base.wipeRequested };
264
+ }
265
+ async getUserKeyPairs(username) {
266
+ if (typeof this.inner.getUserKeyPairs === "function") {
267
+ return this.inner.getUserKeyPairs(username);
268
+ }
269
+ return [];
270
+ }
271
+ async getWipeGrantDocId(signingKey) {
272
+ if (typeof this.inner.getWipeGrantDocId === "function") {
273
+ return this.inner.getWipeGrantDocId(signingKey);
274
+ }
275
+ return null;
276
+ }
277
+ async getRevokedDecryptionKeyIdsForSigningKey(signingKey) {
278
+ if (typeof this.inner.getRevokedDecryptionKeyIdsForSigningKey === "function") {
279
+ return this.inner.getRevokedDecryptionKeyIdsForSigningKey(signingKey);
280
+ }
281
+ return [];
282
+ }
283
+ async evaluateDbAccessForSigningKey(input) {
284
+ if (typeof this.inner.evaluateDbAccessForSigningKey === "function") {
285
+ return this.inner.evaluateDbAccessForSigningKey(input);
286
+ }
287
+ // Config-based directories carry no database-open policy -> unrestricted.
288
+ return true;
289
+ }
290
+ /**
291
+ * Forwarded so the server can tell read from deposit (docs/accesscontrol.md
292
+ * §6.10). Without it the boolean gate above stays in charge and a depositor
293
+ * is refused like anyone else without read access.
294
+ */
295
+ async evaluateDbAccessModeForSigningKey(input) {
296
+ if (typeof this.inner.evaluateDbAccessModeForSigningKey === "function") {
297
+ return this.inner.evaluateDbAccessModeForSigningKey(input);
298
+ }
299
+ return { mode: (await this.evaluateDbAccessForSigningKey(input)) ? "read" : "none" };
300
+ }
301
+ /**
302
+ * Forwarded so the witness enforces Tier 1 rules on push
303
+ * (docs/accesscontrol.md §7). `buildTier1Evaluator` only wires the check when
304
+ * the directory it is given has this method; without the forward every push
305
+ * was accepted, whatever the tenant's rules said. Config-based directories
306
+ * have no rules and therefore no method, which keeps their legacy behavior.
307
+ */
308
+ get evaluateAccessForSigningKey() {
309
+ const inner = this.inner;
310
+ return typeof inner.evaluateAccessForSigningKey === "function"
311
+ ? (input) => inner.evaluateAccessForSigningKey(input)
312
+ : undefined;
313
+ }
314
+ /** Forwarded with {@link evaluateAccessForSigningKey}: `$author` at grant level. */
315
+ get isSamePerson() {
316
+ const inner = this.inner;
317
+ return typeof inner.isSamePerson === "function" ? (input) => inner.isSamePerson(input) : undefined;
318
+ }
319
+ /** Forwarded for the author-scoped read (§6.12). */
320
+ get evaluateReadScopeForSigningKey() {
321
+ const inner = this.inner;
322
+ return typeof inner.evaluateReadScopeForSigningKey === "function"
323
+ ? (input) => inner.evaluateReadScopeForSigningKey(input)
324
+ : undefined;
325
+ }
326
+ /**
327
+ * Forwarded so server-side `userdirectory` writes can resolve the signer's
328
+ * grant. Without this, `evaluateUserdirectoryInvariant` sees a missing
329
+ * method, leaves `signerUsernameHash` null, and denies every non-admin
330
+ * change as "signer is not a granted device" — including the founder
331
+ * wrapping a second device of their own User-Key.
332
+ */
333
+ async resolveUsernameHashForSigningKey(signingKey, trustedTime) {
334
+ if (typeof this.inner.resolveUsernameHashForSigningKey === "function") {
335
+ return this.inner.resolveUsernameHashForSigningKey(signingKey, trustedTime);
336
+ }
337
+ return null;
338
+ }
339
+ }
340
+ // ---------------------------------------------------------------------------
341
+ // TenantManager
342
+ // ---------------------------------------------------------------------------
343
+ /** Keys of the server-wide state in the {@link ServerDataStore}. */
344
+ const SERVER_IDENTITY_KEY = "server.identity.json";
345
+ const TRUSTED_SERVERS_KEY = "trusted-servers.json";
346
+ const SERVER_KEYBAG_KEY = "server.keybag";
347
+ /**
348
+ * TenantManager is responsible for:
349
+ * - Loading the global server identity and trusted servers
350
+ * - Loading tenant configurations from disk
351
+ * - Caching loaded tenants
352
+ * - Registering new tenants
353
+ * - Managing tenant stores and authentication services
354
+ * - Creating a real MindooDB tenant for directory reading (when keys available)
355
+ * - Runtime management of trusted servers
356
+ */
357
+ class TenantManager {
358
+ constructor(options) {
359
+ this.loadedTenants = new Map();
360
+ this.serverIdentity = null;
361
+ this.trustedServers = [];
362
+ /** Peer wiring handed down to every server store; see setPeerAccessControl. */
363
+ this.peerAccessControl = {};
364
+ /** Per-tenant persistent registry of executed purges (docs/accesscontrol.md §13). */
365
+ this.purgedDocRegistries = new Map();
366
+ /** In-flight purge executions, keyed by normalized tenant id (serializes runs). */
367
+ this.purgeInFlight = new Map();
368
+ this.dataStore = options.dataStore;
369
+ this.storeProvider = options.storeProvider;
370
+ this.serverPassword = options.serverPassword;
371
+ this.cryptoAdapter = options.cryptoAdapter;
372
+ this.jwtSecretForTenant = options.jwtSecretForTenant;
373
+ this.publicInfosKeysResolver = options.publicInfosKeysResolver;
374
+ // Load global server identity
375
+ const identityJson = this.dataStore.readText(SERVER_IDENTITY_KEY);
376
+ if (identityJson !== null) {
377
+ this.serverIdentity = JSON.parse(identityJson);
378
+ console.log(`[TenantManager] Loaded server identity: ${this.serverIdentity.username}`);
379
+ }
380
+ else {
381
+ console.log(`[TenantManager] No server.identity.json found (run "npm run init" to create one)`);
382
+ }
383
+ // Load trusted servers
384
+ const trustedJson = this.dataStore.readText(TRUSTED_SERVERS_KEY);
385
+ if (trustedJson !== null) {
386
+ this.trustedServers = JSON.parse(trustedJson);
387
+ console.log(`[TenantManager] Loaded ${this.trustedServers.length} trusted server(s)`);
388
+ }
389
+ }
390
+ // =======================================================================
391
+ // Server identity
392
+ // =======================================================================
393
+ getServerIdentity() {
394
+ return this.serverIdentity;
395
+ }
396
+ getServerPublicInfo() {
397
+ if (!this.serverIdentity)
398
+ return null;
399
+ return {
400
+ name: this.serverIdentity.username,
401
+ signingPublicKey: this.serverIdentity.userSigningKeyPair
402
+ .publicKey,
403
+ encryptionPublicKey: this.serverIdentity.userEncryptionKeyPair
404
+ .publicKey,
405
+ };
406
+ }
407
+ /**
408
+ * Teach every server store how to recognise a trusted peer.
409
+ *
410
+ * `MindooDBServer` calls this once at startup. Two hooks, both required for
411
+ * peer replication to be correct:
412
+ *
413
+ * - `peerTokenResolver` lets a peer's cluster-level JWT through the tenant
414
+ * sync routes. A peer holds no per-tenant grant, so without it the mirror
415
+ * would need a second replication protocol; with it the peer reuses the
416
+ * client one and the store skips the per-user gates that do not apply to a
417
+ * full replica.
418
+ * - `trustedWitnessResolver` decides whose witness receipts may be preserved
419
+ * instead of re-stamped (convergence invariant 3). Without it a replicated
420
+ * entry would get a fresh `receivedAt` on every hop and "trusted time"
421
+ * would mean "time it reached the last server".
422
+ *
423
+ * Must be called before the first {@link getServerStore}, since stores are
424
+ * cached per tenant and database.
425
+ */
426
+ setPeerAccessControl(options) {
427
+ this.peerAccessControl = options;
428
+ }
429
+ /**
430
+ * Database ids this server holds for a tenant (on Node, the directories in
431
+ * `{dataDir}/{tenantId}/stores`). Used by peer replication to mirror every
432
+ * database rather than a configured subset.
433
+ */
434
+ listDatabases(tenantId) {
435
+ return this.storeProvider.listDatabases(this.tenantKey(tenantId.toLowerCase()));
436
+ }
437
+ /**
438
+ * The server's own RSA private key, imported for decryption.
439
+ *
440
+ * Peer replication needs it because the remote wraps transported entries to
441
+ * this server's `encryptionPublicKey` (the one in its `trusted-servers.json`),
442
+ * exactly as it would wrap them for a client.
443
+ */
444
+ async getServerEncryptionPrivateKey() {
445
+ if (this.serverEncryptionPrivateKey)
446
+ return this.serverEncryptionPrivateKey;
447
+ if (!this.serverIdentity || !this.serverPassword)
448
+ return undefined;
449
+ const subtle = this.cryptoAdapter.getSubtle();
450
+ const keyBuffer = await (0, privateKeyEncryption_1.decryptPrivateKey)(this.cryptoAdapter, this.serverIdentity.userEncryptionKeyPair
451
+ .privateKey, this.serverPassword, "encryption");
452
+ this.serverEncryptionPrivateKey = await subtle.importKey("pkcs8", keyBuffer, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
453
+ return this.serverEncryptionPrivateKey;
454
+ }
455
+ /**
456
+ * Build (and cache) the server's witness identity used to stamp receipts on
457
+ * accepted entries (docs/accesscontrol.md §5.3). Returns undefined when the
458
+ * server identity or its unlock password is unavailable, in which case the
459
+ * server runs in pre-access-control mode (no stamping, no Tier 1 advertised).
460
+ */
461
+ async getWitnessSigner() {
462
+ if (this.witnessSigner)
463
+ return this.witnessSigner;
464
+ if (!this.serverIdentity || !this.serverPassword)
465
+ return undefined;
466
+ const subtle = this.cryptoAdapter.getSubtle();
467
+ const signingKeyBuffer = await (0, privateKeyEncryption_1.decryptPrivateKey)(this.cryptoAdapter, this.serverIdentity.userSigningKeyPair.privateKey, this.serverPassword, "signing");
468
+ const signingPrivateKey = await subtle.importKey("pkcs8", signingKeyBuffer, { name: "Ed25519" }, false, ["sign"]);
469
+ this.witnessSigner = {
470
+ publicKeyPem: this.serverIdentity.userSigningKeyPair.publicKey,
471
+ signingPrivateKey,
472
+ subtle,
473
+ };
474
+ return this.witnessSigner;
475
+ }
476
+ /**
477
+ * Build the trusted-time provider that stamps receipts on accepted entries
478
+ * (docs/accesscontrol.md §5.3, §13). v1 wraps the server's Ed25519 witness
479
+ * identity; returns undefined when no witness signer is available, leaving the
480
+ * server in pre-access-control mode.
481
+ */
482
+ async getTimestampProvider() {
483
+ const signer = await this.getWitnessSigner();
484
+ if (!signer)
485
+ return undefined;
486
+ return new Ed25519WitnessProvider_1.Ed25519WitnessProvider({ signer, subtle: signer.subtle });
487
+ }
488
+ // =======================================================================
489
+ // Trusted server management
490
+ // =======================================================================
491
+ listTrustedServers() {
492
+ return [...this.trustedServers];
493
+ }
494
+ addTrustedServer(server) {
495
+ const existing = this.trustedServers.find((s) => s.name.toLowerCase() === server.name.toLowerCase());
496
+ if (existing) {
497
+ throw new Error(`Trusted server "${server.name}" already exists`);
498
+ }
499
+ this.trustedServers.push(server);
500
+ this.persistTrustedServers();
501
+ console.log(`[TenantManager] Added trusted server: ${server.name}`);
502
+ }
503
+ /**
504
+ * Add or replace a trusted server by name.
505
+ *
506
+ * Peer CRUD needs an upsert: `addTrustedServer` throws on an existing name,
507
+ * which is right for `add-to-network` (a silent overwrite of a peer's keys
508
+ * would be a trust change disguised as a config edit) but wrong for the admin
509
+ * API, where changing a peer's url or role is the common case.
510
+ */
511
+ saveTrustedServer(server) {
512
+ const idx = this.trustedServers.findIndex((s) => s.name.toLowerCase() === server.name.toLowerCase());
513
+ if (idx === -1) {
514
+ this.trustedServers.push(server);
515
+ }
516
+ else {
517
+ this.trustedServers[idx] = server;
518
+ }
519
+ this.persistTrustedServers();
520
+ console.log(`[TenantManager] Saved trusted server: ${server.name}`);
521
+ }
522
+ removeTrustedServer(name) {
523
+ const idx = this.trustedServers.findIndex((s) => s.name.toLowerCase() === name.toLowerCase());
524
+ if (idx === -1)
525
+ return false;
526
+ this.trustedServers.splice(idx, 1);
527
+ this.persistTrustedServers();
528
+ console.log(`[TenantManager] Removed trusted server: ${name}`);
529
+ return true;
530
+ }
531
+ persistTrustedServers() {
532
+ this.dataStore.writeText(TRUSTED_SERVERS_KEY, JSON.stringify(this.trustedServers, null, 2));
533
+ }
534
+ async loadServerKeyBag() {
535
+ if (!this.serverIdentity || !this.serverPassword) {
536
+ throw new Error("Server identity and server password are required to manage tenant $publicinfos keys");
537
+ }
538
+ const keyBag = await this.createServerKeyBag(this.serverIdentity, this.serverPassword);
539
+ const data = this.dataStore.readBytes(SERVER_KEYBAG_KEY);
540
+ if (data !== null) {
541
+ await keyBag.load(data);
542
+ }
543
+ return keyBag;
544
+ }
545
+ async createServerKeyBag(serverUser, serverPassword) {
546
+ const wrappingKey = await KeyBag_1.KeyBag.deriveWrappingKey(serverUser.userEncryptionKeyPair.privateKey, serverPassword, this.cryptoAdapter);
547
+ return new KeyBag_1.KeyBag({
548
+ wrappingKey,
549
+ cryptoAdapter: this.cryptoAdapter,
550
+ });
551
+ }
552
+ async saveServerKeyBag(keyBag) {
553
+ this.dataStore.writeBytes(SERVER_KEYBAG_KEY, await keyBag.save());
554
+ }
555
+ bytesToBase64(bytes) {
556
+ return (0, base64_1.bytesToBase64)(bytes);
557
+ }
558
+ mergeUniqueKeys(keys) {
559
+ const seen = new Set();
560
+ const unique = [];
561
+ for (const key of keys) {
562
+ const signature = this.bytesToBase64(key);
563
+ if (seen.has(signature)) {
564
+ continue;
565
+ }
566
+ seen.add(signature);
567
+ unique.push(key);
568
+ }
569
+ return unique;
570
+ }
571
+ async computePublicInfosFingerprint(key) {
572
+ const digest = await this.cryptoAdapter
573
+ .getSubtle()
574
+ .digest("SHA-256", key);
575
+ return Array.from(new Uint8Array(digest).slice(0, 8))
576
+ .map((byte) => byte.toString(16).padStart(2, "0"))
577
+ .join(":");
578
+ }
579
+ /**
580
+ * The data-store key of a tenant's directory. Tenant ids reach this from
581
+ * request paths, so anything that could address another key is refused.
582
+ */
583
+ tenantKey(tenantId) {
584
+ if (tenantId.length === 0 ||
585
+ tenantId.includes("/") ||
586
+ tenantId.includes("\\") ||
587
+ tenantId === "." ||
588
+ tenantId === "..") {
589
+ throw new Error(`Resolved tenant path escapes data directory for tenantId "${tenantId}"`);
590
+ }
591
+ return tenantId;
592
+ }
593
+ tenantConfigKey(tenantId) {
594
+ return `${this.tenantKey(tenantId)}/config.json`;
595
+ }
596
+ readTenantConfig(tenantId) {
597
+ const json = this.dataStore.readText(this.tenantConfigKey(tenantId));
598
+ return json === null ? null : JSON.parse(json);
599
+ }
600
+ writeTenantConfig(tenantId, config) {
601
+ this.dataStore.writeText(this.tenantConfigKey(tenantId), JSON.stringify(config, null, 2));
602
+ }
603
+ async decryptEncryptedPublicInfosKey(base64Payload) {
604
+ if (!this.serverIdentity) {
605
+ throw new Error("Server identity not initialized; cannot decrypt encrypted $publicinfos payload");
606
+ }
607
+ if (!this.serverPassword) {
608
+ throw new Error("Server password not configured; cannot decrypt encrypted $publicinfos payload");
609
+ }
610
+ const privateKeyBuffer = await (0, privateKeyEncryption_1.decryptPrivateKey)(this.cryptoAdapter, this.serverIdentity.userEncryptionKeyPair
611
+ .privateKey, this.serverPassword, "encryption");
612
+ const privateKey = await this.cryptoAdapter.getSubtle().importKey("pkcs8", privateKeyBuffer, {
613
+ name: "RSA-OAEP",
614
+ hash: "SHA-256",
615
+ }, false, ["decrypt"]);
616
+ const decrypted = await new RSAEncryption_1.RSAEncryption(this.cryptoAdapter).decrypt((0, base64_1.base64ToBytes)(base64Payload), privateKey);
617
+ return new Uint8Array(decrypted);
618
+ }
619
+ async resolveIncomingPublicInfosKey(request) {
620
+ if (request.encryptedPublicInfosKey) {
621
+ return this.decryptEncryptedPublicInfosKey(request.encryptedPublicInfosKey);
622
+ }
623
+ if (request.publicInfosKey) {
624
+ return (0, base64_1.base64ToBytes)(request.publicInfosKey);
625
+ }
626
+ throw new Error("Tenant registration requires encryptedPublicInfosKey or publicInfosKey");
627
+ }
628
+ async getStoredPublicInfosKeys(tenantId) {
629
+ if (this.publicInfosKeysResolver) {
630
+ return this.mergeUniqueKeys(await this.publicInfosKeysResolver(tenantId));
631
+ }
632
+ const keys = [];
633
+ if (this.serverIdentity &&
634
+ this.serverPassword &&
635
+ this.dataStore.exists(SERVER_KEYBAG_KEY)) {
636
+ const keyBag = await this.loadServerKeyBag();
637
+ keys.push(...(await keyBag.getAllKeys("doc", tenantId, types_1.PUBLIC_INFOS_KEY_ID)));
638
+ }
639
+ return this.mergeUniqueKeys(keys);
640
+ }
641
+ async persistTenantPublicInfosKey(tenantId, key) {
642
+ const keyBag = await this.loadServerKeyBag();
643
+ const existingKeys = await keyBag.getAllKeys("doc", tenantId, types_1.PUBLIC_INFOS_KEY_ID);
644
+ const incomingSignature = this.bytesToBase64(key);
645
+ const alreadyStored = existingKeys.some((entry) => this.bytesToBase64(entry) === incomingSignature);
646
+ if (!alreadyStored) {
647
+ await keyBag.set("doc", tenantId, types_1.PUBLIC_INFOS_KEY_ID, key, Date.now());
648
+ await this.saveServerKeyBag(keyBag);
649
+ }
650
+ }
651
+ /**
652
+ * All stored `$publicinfos` AES key versions for a tenant from `server.keybag`.
653
+ * Empty when the tenant was never published or the server keybag is locked.
654
+ */
655
+ async getPublicInfosKeysForTenant(tenantId) {
656
+ return this.getStoredPublicInfosKeys(tenantId.toLowerCase());
657
+ }
658
+ async listTenantPublicInfosFingerprints(tenantId) {
659
+ const normalizedId = tenantId.toLowerCase();
660
+ if (!this.dataStore.exists(this.tenantConfigKey(normalizedId))) {
661
+ throw new Error(`Tenant ${normalizedId} not found`);
662
+ }
663
+ const fingerprints = await Promise.all((await this.getStoredPublicInfosKeys(normalizedId)).map((key) => this.computePublicInfosFingerprint(key)));
664
+ return [...new Set(fingerprints)].sort();
665
+ }
666
+ // =======================================================================
667
+ // Tenant management
668
+ // =======================================================================
669
+ async registerTenant(request) {
670
+ const tenantId = request.tenantId.toLowerCase();
671
+ const tenantKey = this.tenantKey(tenantId);
672
+ if (!this.serverIdentity || !this.serverPassword) {
673
+ throw new Error("Tenant registration requires an unlocked server identity to store $publicinfos keys");
674
+ }
675
+ const publicInfosKey = await this.resolveIncomingPublicInfosKey(request);
676
+ const existingConfig = this.readTenantConfig(tenantId);
677
+ if (existingConfig !== null) {
678
+ const incomingSignature = this.bytesToBase64(publicInfosKey);
679
+ const existingKeys = await this.getStoredPublicInfosKeys(tenantId);
680
+ const hasMatch = existingKeys.some((key) => this.bytesToBase64(key) === incomingSignature);
681
+ if (hasMatch) {
682
+ console.log(`[TenantManager] Tenant ${tenantId} already registered with matching $publicinfos key`);
683
+ return {
684
+ context: { tenantId, config: existingConfig },
685
+ created: false,
686
+ };
687
+ }
688
+ throw new Error(`Tenant ${tenantId} already exists with different $publicinfos key`);
689
+ }
690
+ const config = {
691
+ adminUsername: request.adminUsername,
692
+ adminSigningPublicKey: request.adminSigningPublicKey,
693
+ adminEncryptionPublicKey: request.adminEncryptionPublicKey,
694
+ defaultStoreType: request.defaultStoreType || "file",
695
+ users: [],
696
+ };
697
+ try {
698
+ this.writeTenantConfig(tenantId, config);
699
+ await this.persistTenantPublicInfosKey(tenantId, publicInfosKey);
700
+ }
701
+ catch (error) {
702
+ this.dataStore.deleteTree(tenantKey);
703
+ throw error;
704
+ }
705
+ console.log(`[TenantManager] Registered tenant: ${tenantId} ($publicinfos stored in server keybag)`);
706
+ return {
707
+ context: { tenantId, config },
708
+ created: true,
709
+ };
710
+ }
711
+ async getTenant(tenantId) {
712
+ const normalizedId = tenantId.toLowerCase();
713
+ const cached = this.loadedTenants.get(normalizedId);
714
+ if (cached) {
715
+ return cached;
716
+ }
717
+ const loaded = await this.loadTenant(normalizedId);
718
+ this.loadedTenants.set(normalizedId, loaded);
719
+ return loaded;
720
+ }
721
+ tenantExists(tenantId) {
722
+ const normalizedId = tenantId.toLowerCase();
723
+ return this.dataStore.exists(this.tenantConfigKey(normalizedId));
724
+ }
725
+ /**
726
+ * Read the tenant's registered admin signing key without fully loading the
727
+ * tenant. Returns `null` if the tenant is missing or the config is unreadable.
728
+ */
729
+ getTenantAdminSigningPublicKey(tenantId) {
730
+ try {
731
+ const normalizedId = tenantId.toLowerCase();
732
+ const cached = this.loadedTenants.get(normalizedId);
733
+ if (cached?.context.config.adminSigningPublicKey) {
734
+ return cached.context.config.adminSigningPublicKey;
735
+ }
736
+ const config = this.readTenantConfig(normalizedId);
737
+ if (config === null) {
738
+ return null;
739
+ }
740
+ return typeof config.adminSigningPublicKey === "string" &&
741
+ config.adminSigningPublicKey.length > 0
742
+ ? config.adminSigningPublicKey
743
+ : null;
744
+ }
745
+ catch {
746
+ return null;
747
+ }
748
+ }
749
+ /**
750
+ * True when `publicsignkey` is the registered tenant-admin signing key for
751
+ * `tenantId` (PEM whitespace-insensitive).
752
+ */
753
+ isTenantAdminSigningKeyFor(tenantId, publicsignkey) {
754
+ const adminKey = this.getTenantAdminSigningPublicKey(tenantId);
755
+ return adminKey !== null && (0, DirectoryStateNode_1.signingKeysEqual)(adminKey, publicsignkey);
756
+ }
757
+ /**
758
+ * True when `publicsignkey` is the registered admin signing key of any
759
+ * tenant on this server. Used to issue a system JWT so that admin can
760
+ * DELETE their own tenant; other `/system/*` routes stay capability-gated.
761
+ */
762
+ isRegisteredTenantAdminSigningKey(publicsignkey) {
763
+ for (const tenantId of this.listTenants()) {
764
+ if (this.isTenantAdminSigningKeyFor(tenantId, publicsignkey)) {
765
+ return true;
766
+ }
767
+ }
768
+ return false;
769
+ }
770
+ listTenants() {
771
+ return this.dataStore
772
+ .listDirectories("")
773
+ .filter((entry) => this.dataStore.exists(`${entry}/config.json`))
774
+ .sort();
775
+ }
776
+ /**
777
+ * Update operator-owned fields of an existing tenant configuration.
778
+ * Only the fields present in `updates` are overwritten.
779
+ */
780
+ updateTenantConfig(tenantId, updates) {
781
+ const normalizedId = tenantId.toLowerCase();
782
+ const config = this.readTenantConfig(normalizedId);
783
+ if (config === null) {
784
+ throw new Error(`Tenant ${normalizedId} not found`);
785
+ }
786
+ if (updates.defaultStoreType !== undefined) {
787
+ config.defaultStoreType = updates.defaultStoreType;
788
+ }
789
+ this.writeTenantConfig(normalizedId, config);
790
+ this.loadedTenants.delete(normalizedId);
791
+ console.log(`[TenantManager] Updated tenant config: ${normalizedId}`);
792
+ }
793
+ async removeTenant(tenantId) {
794
+ const normalizedId = tenantId.toLowerCase();
795
+ const tenantKey = this.tenantKey(normalizedId);
796
+ if (!this.dataStore.exists(tenantKey)) {
797
+ throw new Error(`Tenant ${normalizedId} does not exist`);
798
+ }
799
+ this.loadedTenants.delete(normalizedId);
800
+ if (this.serverIdentity &&
801
+ this.serverPassword &&
802
+ this.dataStore.exists(SERVER_KEYBAG_KEY)) {
803
+ const keyBag = await this.loadServerKeyBag();
804
+ await keyBag.deleteKey("doc", normalizedId, types_1.PUBLIC_INFOS_KEY_ID);
805
+ await this.saveServerKeyBag(keyBag);
806
+ }
807
+ this.dataStore.deleteTree(tenantKey);
808
+ await this.storeProvider.removeTenantStores?.(normalizedId);
809
+ console.log(`[TenantManager] Removed tenant: ${normalizedId}`);
810
+ }
811
+ // =======================================================================
812
+ // Server stores
813
+ // =======================================================================
814
+ async getServerStore(tenantId, dbId, storeKind) {
815
+ const tenant = await this.getTenant(tenantId);
816
+ const cacheKey = `${dbId}:${storeKind}`;
817
+ const cached = tenant.serverStores.get(cacheKey);
818
+ if (cached) {
819
+ return cached;
820
+ }
821
+ // Created on first real use, i.e. after the request's token and rights
822
+ // were checked: a refused or unauthenticated request must not leave a
823
+ // store directory behind for an arbitrary database id (disk, inodes, and
824
+ // listDatabases() would report it).
825
+ const localStore = lazyContentAddressedStore(dbId, storeKind, () => tenant.storeFactory.getStore(dbId, storeKind));
826
+ // Access-control v1 wiring (docs/accesscontrol.md §5–§7). The witness signer
827
+ // lets the server stamp receipts on accepted entries; the Tier 1 evaluator
828
+ // enforces identity-tier rules at push time. Both are only active when the
829
+ // server identity is unlocked and the directory supports evaluation, so
830
+ // pre-access-control deployments keep their existing behavior.
831
+ const timestampProvider = await this.getTimestampProvider();
832
+ const directory = tenant.directory;
833
+ const tier1Evaluator = this.buildTier1Evaluator(directory, localStore);
834
+ const wipeGrantDocIdResolver = this.buildWipeGrantDocIdResolver(directory);
835
+ // Database-open gate (directory-restricted policy). Never applied to the
836
+ // directory store itself, which must always sync so the policy can be read.
837
+ const dbAccessEvaluator = dbId === "directory" ? undefined : this.buildDbAccessEvaluator(directory);
838
+ // Same gate, also reporting the deposit right (docs/accesscontrol.md §6.10).
839
+ const dbAccessModeEvaluator = dbId === "directory" ? undefined : this.buildDbAccessModeEvaluator(directory);
840
+ // Per-user revoked-key blacklist (§13). Never applied to the directory store
841
+ // itself (its policy, which defines the blacklist, must always sync).
842
+ const revokedKeyResolver = dbId === "directory"
843
+ ? undefined
844
+ : this.buildRevokedKeyResolver(directory);
845
+ // Purged-document denylist (§13). In the directory store the purge requests
846
+ // themselves (`acl_dochistorypurge_…`) must always sync, so they are never
847
+ // on it; any other purged directory document is, or a member whose replica
848
+ // still holds it (it has not processed the request yet) would upload it
849
+ // again on its next push, and the server would keep it for good.
850
+ const purgedDocResolver = dbId === "directory"
851
+ ? () => {
852
+ const ids = this.getPurgedDocRegistry(tenantId).getPurgedDocIds(dbId);
853
+ return new Set([...ids].filter((id) => !id.startsWith(DIRECTORY_PURGE_REQUEST_ID_PREFIX)));
854
+ }
855
+ : () => this.getPurgedDocRegistry(tenantId).getPurgedDocIds(dbId);
856
+ // Author-scoped read (§6.12). Never for the directory databases. The
857
+ // attachments store finds a document's creator in the docs store.
858
+ const readScopeResolver = dbId === "directory" || dbId === types_1.USER_DIRECTORY_DB_ID
859
+ ? undefined
860
+ : this.buildReadScopeResolver(directory, dbId);
861
+ const docCreatorResolver = storeKind === types_1.StoreKind.attachments
862
+ ? this.buildDocCreatorResolver(
863
+ // Lazy as well: a refused request must not create the docs store.
864
+ lazyContentAddressedStore(dbId, types_1.StoreKind.docs, () => tenant.storeFactory.getStore(dbId, types_1.StoreKind.docs)))
865
+ : undefined;
866
+ // Purge requests in personal databases (§6.11): checked on push (only in
867
+ // the docs store, where the request documents live).
868
+ const purgeRequestValidator = storeKind === types_1.StoreKind.docs && (0, builtinDbInvariants_1.personalDatabaseOwner)(dbId)
869
+ ? this.buildPurgeRequestValidator(tenant, directory, localStore, dbId)
870
+ : undefined;
871
+ const builtinWriteContext = dbId === types_1.USER_DIRECTORY_DB_ID
872
+ ? this.buildUserdirectoryWriteContext(tenant, localStore)
873
+ : undefined;
874
+ const serverStore = new ServerNetworkContentAddressedStore_1.ServerNetworkContentAddressedStore(localStore, directory, tenant.authService, this.cryptoAdapter, undefined, {
875
+ timestampProvider,
876
+ witnessDbid: dbId,
877
+ tier1Evaluator,
878
+ wipeGrantDocIdResolver,
879
+ dbAccessEvaluator,
880
+ dbAccessModeEvaluator,
881
+ revokedKeyResolver,
882
+ purgedDocResolver,
883
+ readScopeResolver,
884
+ docCreatorResolver,
885
+ purgeRequestValidator,
886
+ builtinWriteContext,
887
+ ...this.peerAccessControl,
888
+ });
889
+ tenant.serverStores.set(cacheKey, serverStore);
890
+ console.log(`[TenantManager] Created server store for ${tenantId}/${dbId}/${storeKind}`);
891
+ return serverStore;
892
+ }
893
+ /**
894
+ * Build a Tier 1 evaluator closure for a server store, or undefined when the
895
+ * directory cannot evaluate access (e.g. config-based directory without the
896
+ * access-control state chain). The closure resolves `$author` for non-create
897
+ * ops by reading the document's `doc_create` entry creator key from the local
898
+ * store (metadata only, no decryption) and comparing it to the change author.
899
+ */
900
+ /**
901
+ * The witness's Tier 1 gate. The evaluation itself is shared with the peer
902
+ * listener, which has to run it for entries that never reach a witness at
903
+ * all — see `core/accesscontrol/tier1Evaluator.ts`.
904
+ */
905
+ buildTier1Evaluator(directory, localStore) {
906
+ return (0, tier1Evaluator_1.buildTier1Evaluator)(directory, localStore);
907
+ }
908
+ /**
909
+ * Builtin `userdirectory` write invariant: admin may create/delete, only the
910
+ * owning person may change. `username_hash` is read from the decrypted
911
+ * `doc_create` payload (`$publicinfos`). Personal documents are sealed to
912
+ * their owner and therefore undecryptable here, so they are owned by whoever
913
+ * signed their `doc_create` — `resolveCreatorSigningKey` answers that from
914
+ * the entry chain alone.
915
+ */
916
+ buildUserdirectoryWriteContext(tenant, localStore) {
917
+ const adminPublicKey = tenant.context.config.adminSigningPublicKey;
918
+ const mindooTenant = tenant.mindooTenant;
919
+ return {
920
+ adminPublicKey,
921
+ resolveCreatorSigningKey: async (entry) => {
922
+ const createEntry = await this.findCreateEntry(localStore, entry);
923
+ return createEntry?.createdByPublicKey ?? null;
924
+ },
925
+ resolveDocumentUsernameHash: mindooTenant
926
+ ? async (entry) => {
927
+ const createEntry = await this.findCreateEntry(localStore, entry);
928
+ if (!createEntry)
929
+ return null;
930
+ try {
931
+ const plaintext = await mindooTenant.decryptPayload(createEntry.encryptedData, createEntry.decryptionKeyId);
932
+ return (0, builtinDbInvariants_1.usernameHashFromCreateChangeBytes)(plaintext);
933
+ }
934
+ catch {
935
+ return null;
936
+ }
937
+ }
938
+ : undefined,
939
+ };
940
+ }
941
+ /**
942
+ * Read-scope resolver (§6.12): `null` (reads everything) unless the
943
+ * database's `docReadScope` is `"own"` for this principal; then the principal
944
+ * sees documents whose creator is the same person (grant level).
945
+ */
946
+ buildReadScopeResolver(directory, dbId) {
947
+ const dir = directory;
948
+ const evaluate = dir.evaluateReadScopeForSigningKey;
949
+ const isSamePerson = dir.isSamePerson;
950
+ if (typeof evaluate !== "function" || typeof isSamePerson !== "function")
951
+ return undefined;
952
+ return async (principal) => {
953
+ const signingKey = principal.signingKey;
954
+ // No principal key: nothing to attribute documents to, so nothing is shown.
955
+ if (!signingKey) {
956
+ return { scopeId: "none", ownsDocument: async () => false };
957
+ }
958
+ if ((await evaluate({ dbid: dbId, signingKey })) === "all")
959
+ return null;
960
+ return {
961
+ scopeId: "own",
962
+ ownsDocument: (creatorSigningKey, creatorTrustedTime) => isSamePerson({
963
+ creatorSigningKey,
964
+ signerSigningKey: signingKey,
965
+ creatorTrustedTime,
966
+ signerTrustedTime: Number.MAX_SAFE_INTEGER,
967
+ }),
968
+ };
969
+ };
970
+ }
971
+ /** Finds a document's `doc_create` creator in the given (docs) store. */
972
+ buildDocCreatorResolver(docsStore) {
973
+ return async (docId) => {
974
+ const metas = await docsStore.findNewEntriesForDoc([], docId);
975
+ const create = metas.find((m) => m.entryType === "doc_create");
976
+ return create
977
+ ? { signingKey: create.createdByPublicKey, trustedTime: create.receivedAt ?? create.createdAt }
978
+ : null;
979
+ };
980
+ }
981
+ async findCreateEntry(localStore, entry) {
982
+ if (entry.entryType === "doc_create") {
983
+ return entry;
984
+ }
985
+ try {
986
+ const metas = await localStore.findNewEntriesForDoc([], entry.docId);
987
+ const createMeta = metas.find((m) => m.entryType === "doc_create");
988
+ if (!createMeta)
989
+ return null;
990
+ const loaded = await localStore.getEntries([createMeta.id]);
991
+ return loaded[0] ?? null;
992
+ }
993
+ catch {
994
+ return null;
995
+ }
996
+ }
997
+ /**
998
+ * Build a database-open evaluator closure for a server store, or undefined
999
+ * when the directory cannot evaluate the database-open policy (e.g. a
1000
+ * config-based directory without the access-control state chain). When the
1001
+ * tenant policy is `"directory-restricted"`, this rejects sync for any
1002
+ * database id that is not in the allowlist; `"directory"` is always allowed
1003
+ * and the tenant admin is exempt (resolved from the principal signing key).
1004
+ */
1005
+ buildDbAccessEvaluator(directory) {
1006
+ const evaluable = directory;
1007
+ if (typeof evaluable.evaluateDbAccessForSigningKey !== "function") {
1008
+ return undefined;
1009
+ }
1010
+ return async (principal, dbid) => {
1011
+ return evaluable.evaluateDbAccessForSigningKey({
1012
+ dbid,
1013
+ signingKey: principal.signingKey ?? "",
1014
+ });
1015
+ };
1016
+ }
1017
+ /**
1018
+ * Like {@link buildDbAccessEvaluator}, but reports read / deposit / none
1019
+ * (docs/accesscontrol.md §6.10). Undefined when the directory predates it,
1020
+ * in which case the boolean evaluator stays in charge.
1021
+ */
1022
+ buildDbAccessModeEvaluator(directory) {
1023
+ const evaluable = directory;
1024
+ if (typeof evaluable.evaluateDbAccessModeForSigningKey !== "function") {
1025
+ return undefined;
1026
+ }
1027
+ return async (principal, dbid) => evaluable.evaluateDbAccessModeForSigningKey({ dbid, signingKey: principal.signingKey ?? "" });
1028
+ }
1029
+ /**
1030
+ * Build a remote-wipe resolver closure for a server store, or undefined when
1031
+ * the directory does not support wipe directives (docs/accesscontrol.md §6.5).
1032
+ * Maps a wipe-targeted signing key to the admin-signed grant document id so
1033
+ * the server can serve only that document to the targeted device.
1034
+ */
1035
+ buildWipeGrantDocIdResolver(directory) {
1036
+ if (typeof directory.getWipeGrantDocId !== "function") {
1037
+ return undefined;
1038
+ }
1039
+ return (signingKey) => directory.getWipeGrantDocId(signingKey);
1040
+ }
1041
+ /**
1042
+ * Build a per-user revoked-key blacklist resolver for a server store, or
1043
+ * undefined when the directory cannot resolve revoked keys (e.g. a
1044
+ * config-based directory). Maps the authenticated principal's signing key to
1045
+ * the set of decryption key ids revoked for them at the directory head
1046
+ * (docs/accesscontrol.md §13).
1047
+ */
1048
+ buildRevokedKeyResolver(directory) {
1049
+ if (typeof directory.getRevokedDecryptionKeyIdsForSigningKey !== "function") {
1050
+ return undefined;
1051
+ }
1052
+ return async (principal) => {
1053
+ const ids = await directory.getRevokedDecryptionKeyIdsForSigningKey(principal.signingKey ?? "");
1054
+ return new Set(ids);
1055
+ };
1056
+ }
1057
+ async getStore(tenantId, dbId, storeKind) {
1058
+ const tenant = await this.getTenant(tenantId);
1059
+ return tenant.storeFactory.getStore(dbId, storeKind);
1060
+ }
1061
+ /**
1062
+ * Documents purged from `dbId` on this server (the persistent purge
1063
+ * registry). Peer pull replication refuses their entries.
1064
+ */
1065
+ getPurgedDocIds(tenantId, dbId) {
1066
+ return this.getPurgedDocRegistry(tenantId).getPurgedDocIds(dbId);
1067
+ }
1068
+ /** The (lazily-loaded, cached) purge registry for a tenant. */
1069
+ getPurgedDocRegistry(tenantId) {
1070
+ const normalizedId = tenantId.toLowerCase();
1071
+ let registry = this.purgedDocRegistries.get(normalizedId);
1072
+ if (!registry) {
1073
+ registry = new PurgedDocRegistry_1.PurgedDocRegistry(this.dataStore, `${this.tenantKey(normalizedId)}/purged-docs.json`);
1074
+ this.purgedDocRegistries.set(normalizedId, registry);
1075
+ }
1076
+ return registry;
1077
+ }
1078
+ /**
1079
+ * Execute any not-yet-processed document-history purge requests for a tenant
1080
+ * (docs/accesscontrol.md §13). Reads the admin-signed purge requests from the
1081
+ * tenant directory (server-readable `$publicinfos` envelope), physically
1082
+ * purges each requested document's history from the tenant's `docs` and
1083
+ * `attachments` stores, and records the purge in the persistent registry so
1084
+ * (a) it is never executed twice and (b) re-pushes of the purged docs are
1085
+ * rejected at sync time.
1086
+ *
1087
+ * No-op when the server cannot read the directory (config-based fallback, or
1088
+ * the directory does not expose the purge reader). Runs are serialized per
1089
+ * tenant; concurrent callers share the in-flight promise.
1090
+ */
1091
+ async executePendingPurges(tenantId, options) {
1092
+ const normalizedId = tenantId.toLowerCase();
1093
+ const existing = this.purgeInFlight.get(normalizedId);
1094
+ if (existing)
1095
+ return existing;
1096
+ const run = this.executePendingPurgesInternal(normalizedId, options?.dbIds).finally(() => {
1097
+ this.purgeInFlight.delete(normalizedId);
1098
+ });
1099
+ this.purgeInFlight.set(normalizedId, run);
1100
+ return run;
1101
+ }
1102
+ async executePendingPurgesInternal(normalizedId, dbIds) {
1103
+ const tenant = await this.getTenant(normalizedId);
1104
+ if (!tenant.mindooTenant) {
1105
+ // Config-based fallback: the server holds no $publicinfos directory and
1106
+ // cannot read purge requests. Clients still purge locally on reconcile.
1107
+ return;
1108
+ }
1109
+ let directory;
1110
+ try {
1111
+ directory = await tenant.mindooTenant.openDirectory();
1112
+ }
1113
+ catch (error) {
1114
+ console.error(`[TenantManager] executePendingPurges: cannot open directory for ${normalizedId}:`, error);
1115
+ return;
1116
+ }
1117
+ if (typeof directory.getRequestedDocHistoryPurges !== "function") {
1118
+ return;
1119
+ }
1120
+ let requests;
1121
+ try {
1122
+ requests = await directory.getRequestedDocHistoryPurges();
1123
+ }
1124
+ catch (error) {
1125
+ console.error(`[TenantManager] executePendingPurges: cannot read purge requests for ${normalizedId}:`, error);
1126
+ return;
1127
+ }
1128
+ if (requests.length === 0)
1129
+ return;
1130
+ const registry = this.getPurgedDocRegistry(normalizedId);
1131
+ const purgedNow = new Set();
1132
+ const touchedDbIds = new Set();
1133
+ for (const request of requests) {
1134
+ if (registry.isRequestProcessed(request.purgeRequestDocId)) {
1135
+ continue;
1136
+ }
1137
+ if (typeof request.dbId !== "string" || request.dbId.length === 0) {
1138
+ continue;
1139
+ }
1140
+ if (dbIds && !dbIds.has(request.dbId)) {
1141
+ continue;
1142
+ }
1143
+ for (const docId of request.docIds) {
1144
+ if (typeof docId !== "string" || docId.length === 0)
1145
+ continue;
1146
+ // Denylist BEFORE purging: the sync push path consults the registry
1147
+ // live, so from this moment re-pushes of the doc are rejected. Doing it
1148
+ // after the store purges would leave a window in which the entries are
1149
+ // already gone but a stale client could still re-push (and the server
1150
+ // would re-accept) the very history that is being purged. The record is
1151
+ // kept regardless of per-store outcome so re-pushes are rejected even
1152
+ // if one store had nothing to purge.
1153
+ registry.recordPurgedDoc(request.dbId, docId);
1154
+ await this.purgeDocumentFromDatabase(normalizedId, request.dbId, docId);
1155
+ purgedNow.add(`${request.dbId}\u0000${docId}`);
1156
+ touchedDbIds.add(request.dbId);
1157
+ }
1158
+ registry.markRequestProcessed(request.purgeRequestDocId);
1159
+ console.log(`[TenantManager] Executed purge request ${request.purgeRequestDocId} (${request.docIds.length} doc(s)) for ${normalizedId}/${request.dbId}`);
1160
+ }
1161
+ // A purge keeps attachment chunks that another document's history still
1162
+ // references (an in-place copy). Once that document is purged as well,
1163
+ // the earlier purge's leftovers are unreferenced: sweep them now, since a
1164
+ // processed request is never executed again.
1165
+ for (const dbId of touchedDbIds) {
1166
+ for (const docId of registry.getPurgedDocIds(dbId)) {
1167
+ if (!purgedNow.has(`${dbId}\u0000${docId}`)) {
1168
+ await this.purgeDocumentFromDatabase(normalizedId, dbId, docId);
1169
+ }
1170
+ }
1171
+ }
1172
+ }
1173
+ /**
1174
+ * The push check for purge requests in a personal database (§6.11): a
1175
+ * `$publicinfos` create, signed by someone with read access to the database
1176
+ * (a device of the owner, a service with a matching `accessPrefixes` grant,
1177
+ * the tenant admin), naming only documents that are deleted (last
1178
+ * delete/undelete is a delete, on the server or in the same push). Purge
1179
+ * requests themselves cannot be purged, changed or deleted.
1180
+ */
1181
+ buildPurgeRequestValidator(tenant, directory, localStore, dbId) {
1182
+ const mindooTenant = tenant.mindooTenant;
1183
+ const dir = directory;
1184
+ return async (entry, batch) => {
1185
+ if (entry.entryType !== "doc_create") {
1186
+ return "a purge request can only be created, not changed or deleted";
1187
+ }
1188
+ if (entry.decryptionKeyId !== types_1.PUBLIC_INFOS_KEY_ID) {
1189
+ return "a purge request must be encrypted with $publicinfos";
1190
+ }
1191
+ if (!mindooTenant || typeof dir.evaluateDbAccessModeForSigningKey !== "function") {
1192
+ return "this server cannot check purge requests";
1193
+ }
1194
+ const access = await dir.evaluateDbAccessModeForSigningKey({ dbid: dbId, signingKey: entry.createdByPublicKey });
1195
+ if (access.mode !== "read") {
1196
+ return "only the owner, a service with access or the tenant admin may purge";
1197
+ }
1198
+ let docIds = null;
1199
+ try {
1200
+ docIds = (0, builtinDbInvariants_1.purgeRequestDocIdsFromCreateBytes)(await mindooTenant.decryptPayload(entry.encryptedData, types_1.PUBLIC_INFOS_KEY_ID));
1201
+ }
1202
+ catch {
1203
+ docIds = null;
1204
+ }
1205
+ if (!docIds)
1206
+ return "not a readable purge request";
1207
+ for (const docId of docIds) {
1208
+ if ((0, builtinDbInvariants_1.isPurgeRequestDocId)(docId))
1209
+ return "purge requests cannot be purged";
1210
+ const history = [
1211
+ ...(await localStore.findNewEntriesForDoc([], docId)),
1212
+ ...batch.filter((e) => e.docId === docId),
1213
+ ].filter((e) => e.entryType === "doc_delete" || e.entryType === "doc_undelete");
1214
+ history.sort((a, b) => (a.receivedAt ?? a.createdAt) - (b.receivedAt ?? b.createdAt));
1215
+ if (history.at(-1)?.entryType !== "doc_delete") {
1216
+ return `document ${docId} is not deleted`;
1217
+ }
1218
+ }
1219
+ return null;
1220
+ };
1221
+ }
1222
+ /**
1223
+ * Executes the purge requests of one personal database (§6.11): every
1224
+ * document a request names is put on the denylist and removed from the docs
1225
+ * and attachment stores. Called after accepted writes to the database;
1226
+ * requests already executed are skipped. Serialized per database.
1227
+ */
1228
+ async executePersonalPurges(tenantId, dbId) {
1229
+ const key = `${tenantId.toLowerCase()}\u0000${dbId}`;
1230
+ const existing = this.purgeInFlight.get(key);
1231
+ if (existing)
1232
+ return existing;
1233
+ const run = this.executePersonalPurgesInternal(tenantId.toLowerCase(), dbId).finally(() => {
1234
+ this.purgeInFlight.delete(key);
1235
+ });
1236
+ this.purgeInFlight.set(key, run);
1237
+ return run;
1238
+ }
1239
+ async executePersonalPurgesInternal(normalizedId, dbId) {
1240
+ if (!(0, builtinDbInvariants_1.personalDatabaseOwner)(dbId))
1241
+ return;
1242
+ const tenant = await this.getTenant(normalizedId);
1243
+ const mindooTenant = tenant.mindooTenant;
1244
+ if (!mindooTenant)
1245
+ return;
1246
+ const docsStore = await this.getStore(normalizedId, dbId, types_1.StoreKind.docs);
1247
+ const creates = (await docsStore.findEntries("doc_create", null, null)).filter((m) => (0, builtinDbInvariants_1.isPurgeRequestDocId)(m.docId));
1248
+ if (creates.length === 0)
1249
+ return;
1250
+ const registry = this.getPurgedDocRegistry(normalizedId);
1251
+ for (const meta of creates) {
1252
+ const requestKey = `${dbId}/${meta.docId}`;
1253
+ if (registry.isRequestProcessed(requestKey))
1254
+ continue;
1255
+ const [entry] = await docsStore.getEntries([meta.id]);
1256
+ if (!entry)
1257
+ continue;
1258
+ let docIds = null;
1259
+ try {
1260
+ docIds = (0, builtinDbInvariants_1.purgeRequestDocIdsFromCreateBytes)(await mindooTenant.decryptPayload(entry.encryptedData, entry.decryptionKeyId));
1261
+ }
1262
+ catch (error) {
1263
+ console.error(`[TenantManager] purge request ${requestKey} is not readable:`, error);
1264
+ continue;
1265
+ }
1266
+ for (const docId of docIds ?? []) {
1267
+ if ((0, builtinDbInvariants_1.isPurgeRequestDocId)(docId))
1268
+ continue;
1269
+ // Denylist first, as for directory purge requests (see above).
1270
+ registry.recordPurgedDoc(dbId, docId);
1271
+ await this.purgeDocumentFromDatabase(normalizedId, dbId, docId);
1272
+ }
1273
+ registry.markRequestProcessed(requestKey);
1274
+ console.log(`[TenantManager] Executed personal purge ${requestKey} (${docIds?.length ?? 0} doc(s)) for ${normalizedId}`);
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Purge one document from a database's docs and attachment stores. Attachment
1279
+ * chunks still referenced by another document are kept (see
1280
+ * `purgeDocumentHistory.ts`). Failures are logged, never thrown.
1281
+ */
1282
+ async purgeDocumentFromDatabase(tenantId, dbId, docId) {
1283
+ try {
1284
+ await (0, purgeDocumentHistory_1.purgeDocumentHistoryFromStores)({
1285
+ docsStore: await this.getStore(tenantId, dbId, types_1.StoreKind.docs),
1286
+ attachmentStore: await this.getStore(tenantId, dbId, types_1.StoreKind.attachments),
1287
+ }, docId);
1288
+ }
1289
+ catch (error) {
1290
+ console.error(`[TenantManager] executePendingPurges: failed to purge ${docId} in ${tenantId}/${dbId}:`, error);
1291
+ }
1292
+ }
1293
+ async getAuthService(tenantId) {
1294
+ const tenant = await this.getTenant(tenantId);
1295
+ return tenant.authService;
1296
+ }
1297
+ getCryptoAdapter() {
1298
+ return this.cryptoAdapter;
1299
+ }
1300
+ async reloadTenant(tenantId) {
1301
+ const normalizedId = tenantId.toLowerCase();
1302
+ this.loadedTenants.delete(normalizedId);
1303
+ return this.getTenant(normalizedId);
1304
+ }
1305
+ clearCache() {
1306
+ this.loadedTenants.clear();
1307
+ console.log(`[TenantManager] Cleared tenant cache`);
1308
+ }
1309
+ // -----------------------------------------------------------------------
1310
+ // Private helpers
1311
+ // -----------------------------------------------------------------------
1312
+ async loadTenant(tenantId) {
1313
+ const config = this.readTenantConfig(tenantId);
1314
+ if (config === null) {
1315
+ throw new Error(`Tenant ${tenantId} not found: ${this.tenantConfigKey(tenantId)}`);
1316
+ }
1317
+ const context = { tenantId, config };
1318
+ const storeFactory = this.storeProvider.createTenantStores(tenantId, config);
1319
+ // Build the inner directory (real or config-based fallback)
1320
+ let innerDirectory;
1321
+ let mindooTenant;
1322
+ const publicInfosKeys = await this.getStoredPublicInfosKeys(tenantId);
1323
+ if (publicInfosKeys.length > 0 &&
1324
+ this.serverIdentity &&
1325
+ this.serverPassword) {
1326
+ try {
1327
+ const result = await this.createDirectoryTenant(tenantId, config, storeFactory, publicInfosKeys);
1328
+ mindooTenant = result.tenant;
1329
+ innerDirectory = await mindooTenant.openDirectory();
1330
+ console.log(`[TenantManager] Loaded tenant ${tenantId} with real directory`);
1331
+ }
1332
+ catch (error) {
1333
+ console.error(`[TenantManager] Failed to create real directory for ${tenantId}, falling back to config:`, error);
1334
+ innerDirectory = new SimpleMindooDirectory(config);
1335
+ }
1336
+ }
1337
+ else {
1338
+ if (publicInfosKeys.length > 0 && !this.serverIdentity) {
1339
+ console.log(`[TenantManager] Tenant ${tenantId} has $publicinfos keys but no server identity; using config-based directory`);
1340
+ }
1341
+ else {
1342
+ console.log(`[TenantManager] No $publicinfos keys for ${tenantId}, using config-based directory`);
1343
+ }
1344
+ innerDirectory = new SimpleMindooDirectory(config);
1345
+ }
1346
+ // Wrap with CompositeMindooDirectory so trusted servers are also recognized
1347
+ const directory = new CompositeMindooDirectory(innerDirectory, this.trustedServers, config.adminUsername
1348
+ ? {
1349
+ username: config.adminUsername,
1350
+ signingPublicKey: config.adminSigningPublicKey,
1351
+ encryptionPublicKey: config.adminEncryptionPublicKey,
1352
+ }
1353
+ : undefined);
1354
+ const authService = new AuthenticationService_1.AuthenticationService(this.cryptoAdapter, directory, tenantId, this.jwtSecretForTenant
1355
+ ? { jwtSecret: await this.jwtSecretForTenant(tenantId) }
1356
+ : undefined);
1357
+ console.log(`[TenantManager] Loaded tenant: ${tenantId}`);
1358
+ return {
1359
+ context,
1360
+ storeFactory,
1361
+ authService,
1362
+ directory,
1363
+ mindooTenant,
1364
+ serverStores: new Map(),
1365
+ };
1366
+ }
1367
+ /**
1368
+ * Create a real BaseMindooTenant for directory reading using the global
1369
+ * server identity.
1370
+ */
1371
+ async createDirectoryTenant(tenantId, config, storeFactory, publicInfosKeys) {
1372
+ const storeFactoryAdapter = new StoreFactoryAdapter(storeFactory);
1373
+ const factory = new BaseMindooTenantFactory_1.BaseMindooTenantFactory(storeFactoryAdapter, this.cryptoAdapter);
1374
+ const serverUser = this.serverIdentity;
1375
+ // Create a KeyBag using the server user's encryption key
1376
+ const keyBag = await this.createServerKeyBag(serverUser, this.serverPassword);
1377
+ for (const publicInfosKey of this.mergeUniqueKeys(publicInfosKeys)) {
1378
+ await keyBag.set("doc", tenantId, types_1.PUBLIC_INFOS_KEY_ID, publicInfosKey);
1379
+ }
1380
+ // Collect trusted server signing keys for additionalTrustedKeys
1381
+ const openOptions = {};
1382
+ if (this.trustedServers.length > 0) {
1383
+ const trustedKeys = new Map();
1384
+ for (const server of this.trustedServers) {
1385
+ trustedKeys.set(server.signingPublicKey, true);
1386
+ }
1387
+ openOptions.additionalTrustedKeys = trustedKeys;
1388
+ }
1389
+ const tenant = await factory.openTenant(tenantId, config.adminSigningPublicKey, config.adminEncryptionPublicKey, serverUser, this.serverPassword, keyBag, openOptions);
1390
+ return { tenant };
1391
+ }
1392
+ }
1393
+ exports.TenantManager = TenantManager;
1394
+ /**
1395
+ * A store that is only created when something other than its identity
1396
+ * (`getId`, `getStoreKind`) is used. See {@link TenantManager.getServerStore}.
1397
+ */
1398
+ function lazyContentAddressedStore(dbId, storeKind, create) {
1399
+ let real = null;
1400
+ const target = () => (real ?? (real = create()));
1401
+ return new Proxy({}, {
1402
+ get(_shell, prop) {
1403
+ if (prop === "getId")
1404
+ return () => dbId;
1405
+ if (prop === "getStoreKind")
1406
+ return () => storeKind;
1407
+ const store = target();
1408
+ const value = store[prop];
1409
+ return typeof value === "function" ? value.bind(store) : value;
1410
+ },
1411
+ has(_shell, prop) {
1412
+ return prop in target();
1413
+ },
1414
+ });
1415
+ }
1416
+ //# sourceMappingURL=TenantManager.js.map