mindoodb 0.0.84 → 0.0.86

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