mindoodb 0.0.51 → 0.0.54

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 (249) hide show
  1. package/README-server.md +136 -71
  2. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +30 -1
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +67 -20
  5. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  6. package/dist/appendonlystores/network/HttpTransport.d.ts +2 -0
  7. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  8. package/dist/appendonlystores/network/HttpTransport.js +6 -62
  9. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  10. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +82 -0
  11. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  12. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +229 -25
  13. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  14. package/dist/browser/index.mjs +59 -0
  15. package/dist/core/BaseMindooDB.d.ts +148 -80
  16. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  17. package/dist/core/BaseMindooDB.js +890 -692
  18. package/dist/core/BaseMindooDB.js.map +1 -1
  19. package/dist/core/BaseMindooTenant.d.ts +59 -7
  20. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  21. package/dist/core/BaseMindooTenant.js +407 -86
  22. package/dist/core/BaseMindooTenant.js.map +1 -1
  23. package/dist/core/BaseMindooTenantDirectory.d.ts +61 -9
  24. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  25. package/dist/core/BaseMindooTenantDirectory.js +363 -35
  26. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  27. package/dist/core/BaseMindooTenantFactory.d.ts +17 -1
  28. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  29. package/dist/core/BaseMindooTenantFactory.js +286 -17
  30. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  31. package/dist/core/MindooDBServerAdmin.d.ts +44 -22
  32. package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
  33. package/dist/core/MindooDBServerAdmin.js +45 -21
  34. package/dist/core/MindooDBServerAdmin.js.map +1 -1
  35. package/dist/core/accesscontrol/DirectoryStateNode.d.ts +68 -1
  36. package/dist/core/accesscontrol/DirectoryStateNode.d.ts.map +1 -1
  37. package/dist/core/accesscontrol/DirectoryStateNode.js +157 -4
  38. package/dist/core/accesscontrol/DirectoryStateNode.js.map +1 -1
  39. package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
  40. package/dist/core/accesscontrol/directoryProjection.js +14 -5
  41. package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
  42. package/dist/core/accesscontrol/grantKeys.d.ts +17 -14
  43. package/dist/core/accesscontrol/grantKeys.d.ts.map +1 -1
  44. package/dist/core/accesscontrol/grantKeys.js +51 -21
  45. package/dist/core/accesscontrol/grantKeys.js.map +1 -1
  46. package/dist/core/accesscontrol/types.d.ts +2 -1
  47. package/dist/core/accesscontrol/types.d.ts.map +1 -1
  48. package/dist/core/accesscontrol/types.js +3 -2
  49. package/dist/core/accesscontrol/types.js.map +1 -1
  50. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +7 -0
  51. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  52. package/dist/core/appendonlystores/network/AuthenticationService.js +70 -14
  53. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  54. package/dist/core/appendonlystores/network/entryWireCodec.d.ts +51 -0
  55. package/dist/core/appendonlystores/network/entryWireCodec.d.ts.map +1 -0
  56. package/dist/core/appendonlystores/network/entryWireCodec.js +93 -0
  57. package/dist/core/appendonlystores/network/entryWireCodec.js.map +1 -0
  58. package/dist/core/appendonlystores/syncStores.d.ts +147 -0
  59. package/dist/core/appendonlystores/syncStores.d.ts.map +1 -0
  60. package/dist/core/appendonlystores/syncStores.js +612 -0
  61. package/dist/core/appendonlystores/syncStores.js.map +1 -0
  62. package/dist/core/builtinDbInvariants.d.ts +83 -0
  63. package/dist/core/builtinDbInvariants.d.ts.map +1 -0
  64. package/dist/core/builtinDbInvariants.js +244 -0
  65. package/dist/core/builtinDbInvariants.js.map +1 -0
  66. package/dist/core/copy/feasibility.d.ts.map +1 -1
  67. package/dist/core/copy/feasibility.js +3 -0
  68. package/dist/core/copy/feasibility.js.map +1 -1
  69. package/dist/core/copy/flatten.d.ts.map +1 -1
  70. package/dist/core/copy/flatten.js +6 -1
  71. package/dist/core/copy/flatten.js.map +1 -1
  72. package/dist/core/copy/host.d.ts +5 -0
  73. package/dist/core/copy/host.d.ts.map +1 -1
  74. package/dist/core/copy/preflight.d.ts.map +1 -1
  75. package/dist/core/copy/preflight.js +7 -1
  76. package/dist/core/copy/preflight.js.map +1 -1
  77. package/dist/core/copy/types.d.ts +7 -0
  78. package/dist/core/copy/types.d.ts.map +1 -1
  79. package/dist/core/crypto/EntrySignature.d.ts +8 -2
  80. package/dist/core/crypto/EntrySignature.d.ts.map +1 -1
  81. package/dist/core/crypto/EntrySignature.js +14 -5
  82. package/dist/core/crypto/EntrySignature.js.map +1 -1
  83. package/dist/core/crypto/MindooDocSigner.d.ts.map +1 -1
  84. package/dist/core/crypto/MindooDocSigner.js +11 -2
  85. package/dist/core/crypto/MindooDocSigner.js.map +1 -1
  86. package/dist/core/crypto/RSAEncryption.d.ts +19 -0
  87. package/dist/core/crypto/RSAEncryption.d.ts.map +1 -1
  88. package/dist/core/crypto/RSAEncryption.js +36 -3
  89. package/dist/core/crypto/RSAEncryption.js.map +1 -1
  90. package/dist/core/crypto/privateKeyEncryption.js +1 -1
  91. package/dist/core/crypto/privateKeyEncryption.js.map +1 -1
  92. package/dist/core/crypto/rsaOaep3072.d.ts +12 -0
  93. package/dist/core/crypto/rsaOaep3072.d.ts.map +1 -0
  94. package/dist/core/crypto/rsaOaep3072.js +30 -0
  95. package/dist/core/crypto/rsaOaep3072.js.map +1 -0
  96. package/dist/core/index.d.ts +7 -3
  97. package/dist/core/index.d.ts.map +1 -1
  98. package/dist/core/index.js +33 -4
  99. package/dist/core/index.js.map +1 -1
  100. package/dist/core/join/deviceJoinBootstrap.d.ts +23 -0
  101. package/dist/core/join/deviceJoinBootstrap.d.ts.map +1 -0
  102. package/dist/core/join/deviceJoinBootstrap.js +37 -0
  103. package/dist/core/join/deviceJoinBootstrap.js.map +1 -0
  104. package/dist/core/keys/KeyBag.d.ts +20 -2
  105. package/dist/core/keys/KeyBag.d.ts.map +1 -1
  106. package/dist/core/keys/KeyBag.js +22 -10
  107. package/dist/core/keys/KeyBag.js.map +1 -1
  108. package/dist/core/logging/MindooLogger.d.ts.map +1 -1
  109. package/dist/core/logging/MindooLogger.js +65 -40
  110. package/dist/core/logging/MindooLogger.js.map +1 -1
  111. package/dist/core/tenantIdValidation.d.ts.map +1 -1
  112. package/dist/core/tenantIdValidation.js +1 -1
  113. package/dist/core/tenantIdValidation.js.map +1 -1
  114. package/dist/core/tenantSetup.d.ts +94 -9
  115. package/dist/core/tenantSetup.d.ts.map +1 -1
  116. package/dist/core/tenantSetup.js +208 -20
  117. package/dist/core/tenantSetup.js.map +1 -1
  118. package/dist/core/types.d.ts +313 -33
  119. package/dist/core/types.d.ts.map +1 -1
  120. package/dist/core/types.js +14 -1
  121. package/dist/core/types.js.map +1 -1
  122. package/dist/core/uri/joinRequestUri.d.ts.map +1 -1
  123. package/dist/core/uri/joinRequestUri.js +12 -3
  124. package/dist/core/uri/joinRequestUri.js.map +1 -1
  125. package/dist/core/userid/canonicalUsername.d.ts +66 -0
  126. package/dist/core/userid/canonicalUsername.d.ts.map +1 -0
  127. package/dist/core/userid/canonicalUsername.js +235 -0
  128. package/dist/core/userid/canonicalUsername.js.map +1 -0
  129. package/dist/core/userid/index.d.ts +9 -0
  130. package/dist/core/userid/index.d.ts.map +1 -1
  131. package/dist/core/userid/index.js +14 -0
  132. package/dist/core/userid/index.js.map +1 -1
  133. package/dist/core/userkeys/UserKeyDocument.d.ts +50 -0
  134. package/dist/core/userkeys/UserKeyDocument.d.ts.map +1 -0
  135. package/dist/core/userkeys/UserKeyDocument.js +145 -0
  136. package/dist/core/userkeys/UserKeyDocument.js.map +1 -0
  137. package/dist/core/userkeys/UserKeyManager.d.ts +170 -0
  138. package/dist/core/userkeys/UserKeyManager.d.ts.map +1 -0
  139. package/dist/core/userkeys/UserKeyManager.js +1277 -0
  140. package/dist/core/userkeys/UserKeyManager.js.map +1 -0
  141. package/dist/core/userkeys/fingerprint.d.ts +17 -0
  142. package/dist/core/userkeys/fingerprint.d.ts.map +1 -0
  143. package/dist/core/userkeys/fingerprint.js +59 -0
  144. package/dist/core/userkeys/fingerprint.js.map +1 -0
  145. package/dist/core/userkeys/index.d.ts +11 -0
  146. package/dist/core/userkeys/index.d.ts.map +1 -0
  147. package/dist/core/userkeys/index.js +49 -0
  148. package/dist/core/userkeys/index.js.map +1 -0
  149. package/dist/core/userkeys/recipients.d.ts +51 -0
  150. package/dist/core/userkeys/recipients.d.ts.map +1 -0
  151. package/dist/core/userkeys/recipients.js +292 -0
  152. package/dist/core/userkeys/recipients.js.map +1 -0
  153. package/dist/core/userkeys/resolveUserKeyDocument.d.ts +21 -0
  154. package/dist/core/userkeys/resolveUserKeyDocument.d.ts.map +1 -0
  155. package/dist/core/userkeys/resolveUserKeyDocument.js +98 -0
  156. package/dist/core/userkeys/resolveUserKeyDocument.js.map +1 -0
  157. package/dist/core/userkeys/sealedCrypto.d.ts +21 -0
  158. package/dist/core/userkeys/sealedCrypto.d.ts.map +1 -0
  159. package/dist/core/userkeys/sealedCrypto.js +79 -0
  160. package/dist/core/userkeys/sealedCrypto.js.map +1 -0
  161. package/dist/core/userkeys/sealedTypes.d.ts +72 -0
  162. package/dist/core/userkeys/sealedTypes.d.ts.map +1 -0
  163. package/dist/core/userkeys/sealedTypes.js +14 -0
  164. package/dist/core/userkeys/sealedTypes.js.map +1 -0
  165. package/dist/core/userkeys/types.d.ts +70 -0
  166. package/dist/core/userkeys/types.d.ts.map +1 -0
  167. package/dist/core/userkeys/types.js +48 -0
  168. package/dist/core/userkeys/types.js.map +1 -0
  169. package/dist/core/userkeys/validateUserKeyDocument.d.ts +19 -0
  170. package/dist/core/userkeys/validateUserKeyDocument.d.ts.map +1 -0
  171. package/dist/core/userkeys/validateUserKeyDocument.js +42 -0
  172. package/dist/core/userkeys/validateUserKeyDocument.js.map +1 -0
  173. package/dist/node/server/CapabilityMatcher.d.ts +7 -2
  174. package/dist/node/server/CapabilityMatcher.d.ts.map +1 -1
  175. package/dist/node/server/CapabilityMatcher.js +12 -3
  176. package/dist/node/server/CapabilityMatcher.js.map +1 -1
  177. package/dist/node/server/DeviceDiscoveryService.d.ts +36 -0
  178. package/dist/node/server/DeviceDiscoveryService.d.ts.map +1 -0
  179. package/dist/node/server/DeviceDiscoveryService.js +165 -0
  180. package/dist/node/server/DeviceDiscoveryService.js.map +1 -0
  181. package/dist/node/server/MindooDBServer.d.ts +27 -1
  182. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  183. package/dist/node/server/MindooDBServer.js +265 -220
  184. package/dist/node/server/MindooDBServer.js.map +1 -1
  185. package/dist/node/server/SyncEventBus.d.ts +10 -0
  186. package/dist/node/server/SyncEventBus.d.ts.map +1 -1
  187. package/dist/node/server/SyncEventBus.js.map +1 -1
  188. package/dist/node/server/SystemAdminAuth.d.ts +7 -0
  189. package/dist/node/server/SystemAdminAuth.d.ts.map +1 -1
  190. package/dist/node/server/SystemAdminAuth.js +4 -1
  191. package/dist/node/server/SystemAdminAuth.js.map +1 -1
  192. package/dist/node/server/TenantManager.d.ts +86 -5
  193. package/dist/node/server/TenantManager.d.ts.map +1 -1
  194. package/dist/node/server/TenantManager.js +235 -71
  195. package/dist/node/server/TenantManager.js.map +1 -1
  196. package/dist/node/server/add-to-network.d.ts.map +1 -1
  197. package/dist/node/server/add-to-network.js +33 -15
  198. package/dist/node/server/add-to-network.js.map +1 -1
  199. package/dist/node/server/canonicalSetupName.d.ts +22 -0
  200. package/dist/node/server/canonicalSetupName.d.ts.map +1 -0
  201. package/dist/node/server/canonicalSetupName.js +38 -0
  202. package/dist/node/server/canonicalSetupName.js.map +1 -0
  203. package/dist/node/server/config.d.ts.map +1 -1
  204. package/dist/node/server/config.js +33 -1
  205. package/dist/node/server/config.js.map +1 -1
  206. package/dist/node/server/peer/ClusterAuditLog.d.ts +64 -0
  207. package/dist/node/server/peer/ClusterAuditLog.d.ts.map +1 -0
  208. package/dist/node/server/peer/ClusterAuditLog.js +99 -0
  209. package/dist/node/server/peer/ClusterAuditLog.js.map +1 -0
  210. package/dist/node/server/peer/ClusterJobRunner.d.ts +43 -0
  211. package/dist/node/server/peer/ClusterJobRunner.d.ts.map +1 -0
  212. package/dist/node/server/peer/ClusterJobRunner.js +119 -0
  213. package/dist/node/server/peer/ClusterJobRunner.js.map +1 -0
  214. package/dist/node/server/peer/ClusterManager.d.ts +116 -0
  215. package/dist/node/server/peer/ClusterManager.d.ts.map +1 -0
  216. package/dist/node/server/peer/ClusterManager.js +274 -0
  217. package/dist/node/server/peer/ClusterManager.js.map +1 -0
  218. package/dist/node/server/peer/PeerAuthService.d.ts +69 -0
  219. package/dist/node/server/peer/PeerAuthService.d.ts.map +1 -0
  220. package/dist/node/server/peer/PeerAuthService.js +208 -0
  221. package/dist/node/server/peer/PeerAuthService.js.map +1 -0
  222. package/dist/node/server/peer/PeerReplicator.d.ts +232 -0
  223. package/dist/node/server/peer/PeerReplicator.d.ts.map +1 -0
  224. package/dist/node/server/peer/PeerReplicator.js +879 -0
  225. package/dist/node/server/peer/PeerReplicator.js.map +1 -0
  226. package/dist/node/server/peer/routes.d.ts +66 -0
  227. package/dist/node/server/peer/routes.d.ts.map +1 -0
  228. package/dist/node/server/peer/routes.js +322 -0
  229. package/dist/node/server/peer/routes.js.map +1 -0
  230. package/dist/node/server/peer/types.d.ts +244 -0
  231. package/dist/node/server/peer/types.d.ts.map +1 -0
  232. package/dist/node/server/peer/types.js +189 -0
  233. package/dist/node/server/peer/types.js.map +1 -0
  234. package/dist/node/server/server.d.ts +1 -1
  235. package/dist/node/server/server.js +18 -37
  236. package/dist/node/server/server.js.map +1 -1
  237. package/dist/node/server/serverinit.d.ts +2 -1
  238. package/dist/node/server/serverinit.d.ts.map +1 -1
  239. package/dist/node/server/serverinit.js +34 -12
  240. package/dist/node/server/serverinit.js.map +1 -1
  241. package/dist/node/server/types.d.ts +42 -30
  242. package/dist/node/server/types.d.ts.map +1 -1
  243. package/dist/node/server/types.js +1 -7
  244. package/dist/node/server/types.js.map +1 -1
  245. package/dist/node/server/validation.d.ts +0 -6
  246. package/dist/node/server/validation.d.ts.map +1 -1
  247. package/dist/node/server/validation.js +1 -7
  248. package/dist/node/server/validation.js.map +1 -1
  249. package/package.json +5 -5
@@ -59,19 +59,31 @@ const cors_1 = __importDefault(require("cors"));
59
59
  const express_rate_limit_1 = __importStar(require("express-rate-limit"));
60
60
  const compressionMiddleware_1 = require("./compressionMiddleware");
61
61
  const binaryEntryFraming_1 = require("../../core/appendonlystores/network/binaryEntryFraming");
62
+ const entryWireCodec_1 = require("../../core/appendonlystores/network/entryWireCodec");
62
63
  const SyncEventBus_1 = require("./SyncEventBus");
63
- const types_1 = require("../../core/appendonlystores/types");
64
- const types_2 = require("../../core/appendonlystores/network/types");
64
+ const ClusterManager_1 = require("./peer/ClusterManager");
65
+ const routes_1 = require("./peer/routes");
66
+ const types_1 = require("./peer/types");
67
+ const types_2 = require("../../core/appendonlystores/types");
68
+ const types_3 = require("../../core/appendonlystores/network/types");
65
69
  const TenantManager_1 = require("./TenantManager");
66
70
  const CapabilityMatcher_1 = require("./CapabilityMatcher");
67
71
  const SystemAdminAuth_1 = require("./SystemAdminAuth");
72
+ const DeviceDiscoveryService_1 = require("./DeviceDiscoveryService");
68
73
  const config_1 = require("./config");
69
74
  const SystemIpAllowlist_1 = require("./SystemIpAllowlist");
70
75
  const validation_1 = require("./validation");
71
76
  const urlSafety_1 = require("../../core/utils/urlSafety");
72
- const types_3 = require("./types");
77
+ const types_4 = require("./types");
73
78
  const timestampProxy_1 = require("./timestampProxy");
74
79
  const rateLimits_1 = require("./rateLimits");
80
+ /**
81
+ * Wire-format types for the sync HTTP API live in
82
+ * {@link ../../core/appendonlystores/network/entryWireCodec}: binary fields
83
+ * (signature, encryptedData, rsaEncryptedPayload) are base64 strings in
84
+ * transit. Client and server share that codec so signed trailing blocks
85
+ * cannot drift off the wire.
86
+ */
75
87
  /**
76
88
  * Rate-limit defaults. Auth and sync have separate tiers because a single
77
89
  * challenge/authenticate handshake is two requests; with multiple tenants,
@@ -114,24 +126,6 @@ const DEFAULT_TIMESTAMP_RATE_LIMIT_MAX = 10;
114
126
  const DEFAULT_TIMESTAMP_DAILY_LIMIT_MAX = 500;
115
127
  const TIMESTAMP_DAILY_WINDOW_MS = 24 * 60 * 60 * 1000;
116
128
  const DEFAULT_SERVER_SOCKET_TIMEOUT_MS = 120000;
117
- /**
118
- * The effective max number of database ids per sync-server registration. Reads
119
- * the `MINDOODB_MAX_SYNC_SERVER_DATABASES` env override and falls back to
120
- * {@link DEFAULT_MAX_SYNC_SERVER_DATABASES} when unset or invalid (non-integer
121
- * or non-positive), so a typo can never silently disable the bound.
122
- */
123
- function resolveMaxSyncServerDatabases() {
124
- const raw = process.env[types_3.ENV_VARS.MAX_SYNC_SERVER_DATABASES]?.trim();
125
- if (!raw)
126
- return validation_1.DEFAULT_MAX_SYNC_SERVER_DATABASES;
127
- const parsed = Number(raw);
128
- if (!Number.isInteger(parsed) || parsed <= 0) {
129
- console.warn(`[MindooDBServer] Ignoring invalid ${types_3.ENV_VARS.MAX_SYNC_SERVER_DATABASES}="${raw}" ` +
130
- `(expected a positive integer); using default ${validation_1.DEFAULT_MAX_SYNC_SERVER_DATABASES}`);
131
- return validation_1.DEFAULT_MAX_SYNC_SERVER_DATABASES;
132
- }
133
- return parsed;
134
- }
135
129
  /** Parse a human-readable size string ("5mb", "1024", "100kb") into bytes. */
136
130
  function parseBodySizeLimitToBytes(limit) {
137
131
  const normalized = limit.trim().toLowerCase();
@@ -174,8 +168,8 @@ class MindooDBServer {
174
168
  this.configPath = configPath ?? path_1.default.join(dataDir, "config.json");
175
169
  this.jsonBodyLimit = process.env.MINDOODB_JSON_BODY_LIMIT?.trim() || "5mb";
176
170
  this.jsonBodyLimitBytes = parseBodySizeLimitToBytes(this.jsonBodyLimit);
177
- this.tsaProviders = (0, timestampProxy_1.parseTsaProviders)(process.env[types_3.ENV_VARS.TSA_PROVIDERS], {
178
- allowInsecureUrls: /^(1|true)$/i.test(process.env[types_3.ENV_VARS.ALLOW_INSECURE_SYNC_URLS] ?? ""),
171
+ this.tsaProviders = (0, timestampProxy_1.parseTsaProviders)(process.env[types_4.ENV_VARS.TSA_PROVIDERS], {
172
+ allowInsecureUrls: /^(1|true)$/i.test(process.env[types_4.ENV_VARS.ALLOW_INSECURE_SYNC_URLS] ?? ""),
179
173
  });
180
174
  if (this.tsaProviders.length > 0) {
181
175
  console.log(`[MindooDBServer] RFC 3161 timestamp proxy enabled with providers: ${this.tsaProviders
@@ -187,7 +181,43 @@ class MindooDBServer {
187
181
  // SystemAdminAuthService generates a random HMAC-SHA256 secret for JWTs
188
182
  // on each server start — existing tokens become invalid after restart.
189
183
  const cryptoAdapter = this.tenantManager.getCryptoAdapter();
190
- this.systemAdminAuth = new SystemAdminAuth_1.SystemAdminAuthService(cryptoAdapter, this.serverConfig);
184
+ this.systemAdminAuth = new SystemAdminAuth_1.SystemAdminAuthService(cryptoAdapter, this.serverConfig, {
185
+ isTenantAdminSigningKey: (publicsignkey) => this.tenantManager.isRegisteredTenantAdminSigningKey(publicsignkey),
186
+ });
187
+ this.deviceDiscovery = new DeviceDiscoveryService_1.DeviceDiscoveryService(this.tenantManager, cryptoAdapter);
188
+ this.cluster = new ClusterManager_1.ClusterManager({
189
+ cryptoAdapter,
190
+ dataDir,
191
+ eventBus: this.syncEventBus,
192
+ listTrustedServers: () => this.tenantManager.listTrustedServers(),
193
+ localServerName: () => this.tenantManager.getServerPublicInfo()?.name ?? null,
194
+ localSigningPublicKey: () => this.tenantManager.getServerPublicInfo()?.signingPublicKey ?? null,
195
+ getSigningKey: async () => (await this.tenantManager.getWitnessSigner())?.signingPrivateKey,
196
+ getEncryptionKey: () => this.tenantManager.getServerEncryptionPrivateKey(),
197
+ listTenants: () => this.tenantManager.listTenants(),
198
+ listDatabases: (tenantId) => this.tenantManager.listDatabases(tenantId),
199
+ getLocalStore: (tenantId, dbId, storeKind) => this.tenantManager.getStore(tenantId, dbId, storeKind),
200
+ localRole: this.serverConfig.cluster?.role,
201
+ });
202
+ // Peer tokens are accepted by the tenant sync routes so a peer can mirror
203
+ // the encrypted stores through the ordinary protocol; the store bypasses
204
+ // per-user access control for them (see ServerNetworkContentAddressedStore).
205
+ this.tenantManager.setPeerAccessControl({
206
+ peerTokenResolver: async (token) => {
207
+ const payload = await this.cluster.auth.validateToken(token);
208
+ if (!payload)
209
+ return null;
210
+ const trusted = this.cluster.auth.findTrustedServerByKey(payload.publicsignkey);
211
+ if (!trusted)
212
+ return null;
213
+ return {
214
+ name: trusted.name,
215
+ signingPublicKey: trusted.signingPublicKey,
216
+ encryptionPublicKey: trusted.encryptionPublicKey,
217
+ };
218
+ },
219
+ trustedWitnessResolver: (receivedByPublicKey) => this.cluster.auth.findTrustedServerByKey(receivedByPublicKey) !== null,
220
+ });
191
221
  const principalCount = Object.values(this.serverConfig.capabilities).reduce((sum, arr) => sum + arr.length, 0);
192
222
  if (principalCount === 0) {
193
223
  console.log(`[MindooDBServer] WARNING: No system admin principals configured. All /system/* endpoints are locked down.`);
@@ -207,6 +237,23 @@ class MindooDBServer {
207
237
  getSystemAdminAuth() {
208
238
  return this.systemAdminAuth;
209
239
  }
240
+ getClusterManager() {
241
+ return this.cluster;
242
+ }
243
+ /**
244
+ * Begin replicating with every trusted server that has a `url`.
245
+ *
246
+ * Separate from the constructor so a server can serve the peer and cluster
247
+ * endpoints — answering handshakes, status and admin actions — without
248
+ * dialling out itself. That is what `--auto-sync` toggles, and it is what
249
+ * makes a passive mirror or a test harness possible.
250
+ */
251
+ startCluster() {
252
+ this.cluster.start();
253
+ }
254
+ async stopCluster() {
255
+ await this.cluster.stop();
256
+ }
210
257
  getServerConfig() {
211
258
  return this.serverConfig;
212
259
  }
@@ -307,7 +354,7 @@ class MindooDBServer {
307
354
  * list over `true`, which trusts the whole chain.
308
355
  */
309
356
  applyTrustProxySetting() {
310
- const raw = process.env[types_3.ENV_VARS.TRUST_PROXY]?.trim();
357
+ const raw = process.env[types_4.ENV_VARS.TRUST_PROXY]?.trim();
311
358
  if (!raw || /^(0|false)$/i.test(raw)) {
312
359
  return;
313
360
  }
@@ -362,6 +409,17 @@ class MindooDBServer {
362
409
  this.app.get("/health", (req, res) => {
363
410
  res.json({ status: "ok", timestamp: Date.now() });
364
411
  });
412
+ // Server-wide device discovery (before /:tenantId so "device" is not a tenant id).
413
+ // Aggressive rate limit: challenge+discover scans every local tenant.
414
+ const deviceDiscoveryRateLimit = (0, express_rate_limit_1.default)({
415
+ windowMs: 60000,
416
+ max: 10,
417
+ standardHeaders: true,
418
+ legacyHeaders: false,
419
+ message: { error: "Too many device discovery requests, please try again later" },
420
+ });
421
+ this.app.post("/device/challenge", deviceDiscoveryRateLimit, this.handleDeviceChallenge.bind(this));
422
+ this.app.post("/device/discover", deviceDiscoveryRateLimit, this.handleDeviceDiscover.bind(this));
365
423
  // .well-known endpoints are unauthenticated so clients can discover
366
424
  // server identity and tenant availability before attempting to authenticate.
367
425
  this.app.get("/.well-known/mindoodb-server-info", (req, res) => {
@@ -372,6 +430,10 @@ class MindooDBServer {
372
430
  }
373
431
  res.json({
374
432
  ...info,
433
+ // Its own mesh role, so `add-to-network` can propagate it into the
434
+ // other side's trusted-servers entry instead of asking the operator to
435
+ // repeat it on every node.
436
+ clusterRole: this.serverConfig.cluster?.role ?? "peer",
375
437
  maxJsonRequestBodyLimit: this.jsonBodyLimit,
376
438
  maxJsonRequestBodyBytes: this.jsonBodyLimitBytes ?? undefined,
377
439
  // Lets a client decide between calling a browser-reachable TSA directly
@@ -382,6 +444,33 @@ class MindooDBServer {
382
444
  },
383
445
  });
384
446
  });
447
+ /**
448
+ * Mirrors of one tenant, for client failover.
449
+ *
450
+ * Scoped to the tenant in the query on purpose: a client asking about
451
+ * tenant A must not learn which other tenants this server replicates, or
452
+ * to whom. Unauthenticated like the other discovery endpoints — a client
453
+ * needs it precisely when it cannot reach a server to authenticate with.
454
+ */
455
+ this.app.get("/.well-known/mindoodb-cluster", (req, res) => {
456
+ const tenantId = typeof req.query.tenantId === "string" ? req.query.tenantId.toLowerCase() : "";
457
+ if (!tenantId) {
458
+ res.status(400).json({ error: "tenantId query parameter is required" });
459
+ return;
460
+ }
461
+ try {
462
+ (0, validation_1.validateTenantId)(tenantId);
463
+ }
464
+ catch {
465
+ res.status(400).json({ error: "Invalid tenantId format" });
466
+ return;
467
+ }
468
+ if (!this.tenantManager.tenantExists(tenantId)) {
469
+ res.status(404).json({ error: "Tenant not found on server" });
470
+ return;
471
+ }
472
+ res.json({ tenantId, mirrors: this.cluster.mirrorsForTenant(tenantId) });
473
+ });
385
474
  this.app.get("/.well-known/mindoodb-tenants/:tenantId/publicinfos-fingerprints", async (req, res) => {
386
475
  const tenantId = req.params.tenantId.toLowerCase();
387
476
  try {
@@ -431,12 +520,29 @@ class MindooDBServer {
431
520
  console.log(`[MindooDBServer] MINDOODB_ADMIN_ALLOWED_IPS is set — /system/* limited to: ${systemIpRaw}`);
432
521
  }
433
522
  const systemRateLimit = (0, express_rate_limit_1.default)({
434
- windowMs: 60000,
435
- max: 30,
523
+ windowMs: this.serverConfig.rateLimits?.system?.windowMs ?? 60000,
524
+ max: this.serverConfig.rateLimits?.system?.max ?? 30,
436
525
  standardHeaders: true,
437
526
  legacyHeaders: false,
438
527
  message: { error: "Too many system requests, please try again later" },
439
528
  });
529
+ // Peer routes are mounted before the /system chain on purpose. They are
530
+ // server-to-server traffic, not administration: the admin IP allowlist
531
+ // would lock out the operator's own cluster, and 30 req/min would throttle
532
+ // replication to a crawl. They are protected by the peer JWT, which is only
533
+ // issued to a key in trusted-servers.json.
534
+ this.app.use("/system/peer", (0, express_rate_limit_1.default)({
535
+ windowMs: 60000,
536
+ max: 600,
537
+ standardHeaders: true,
538
+ legacyHeaders: false,
539
+ message: { error: "Too many peer requests, please try again later" },
540
+ }), (0, routes_1.createPeerRouter)({
541
+ cluster: this.cluster,
542
+ eventBus: this.syncEventBus,
543
+ listTenants: () => this.tenantManager.listTenants(),
544
+ listDatabases: (tenantId) => this.tenantManager.listDatabases(tenantId),
545
+ }));
440
546
  const systemRouter = (0, express_1.Router)();
441
547
  this.setupSystemRoutes(systemRouter);
442
548
  this.app.use("/system", SystemIpAllowlist_1.systemIpAllowlistMiddleware, systemRateLimit, systemRouter);
@@ -451,6 +557,10 @@ class MindooDBServer {
451
557
  * JWT-based middleware for /system/* routes (excluding /system/auth/*).
452
558
  * Extracts and validates the bearer token, then checks capabilities
453
559
  * for the current (method, path, username, publicsignkey).
560
+ *
561
+ * `DELETE /system/tenants/:tenantId` is also allowed when the JWT signing
562
+ * key is that tenant's registered `adminSigningPublicKey` (own-tenant
563
+ * delete). Sub-resources and other tenants stay capability-gated.
454
564
  */
455
565
  async systemAdminMiddleware(req, res, next) {
456
566
  const auth = req.headers.authorization;
@@ -468,7 +578,10 @@ class MindooDBServer {
468
578
  // but we need the full path for capability matching.
469
579
  const fullPath = `/system${req.path}`;
470
580
  const method = req.method.toUpperCase();
471
- if (!this.capabilityMatcher.isAuthorized(method, fullPath, payload.sub, payload.publicsignkey)) {
581
+ const capabilityAuthorized = this.capabilityMatcher.isAuthorized(method, fullPath, payload.sub, payload.publicsignkey);
582
+ const ownerDeleteAuthorized = !capabilityAuthorized &&
583
+ this.isOwnTenantDeleteAuthorized(method, fullPath, payload.publicsignkey);
584
+ if (!capabilityAuthorized && !ownerDeleteAuthorized) {
472
585
  res.status(403).json({ error: "Forbidden: insufficient capabilities" });
473
586
  return;
474
587
  }
@@ -478,6 +591,27 @@ class MindooDBServer {
478
591
  };
479
592
  next();
480
593
  }
594
+ /**
595
+ * Owner bypass for deleting the caller's own tenant. Matches only
596
+ * `DELETE /system/tenants/:tenantId` (no sub-resource) and requires the
597
+ * JWT public key to equal that tenant's stored admin signing key.
598
+ */
599
+ isOwnTenantDeleteAuthorized(method, fullPath, publicsignkey) {
600
+ if (method !== "DELETE") {
601
+ return false;
602
+ }
603
+ const tenantId = (0, CapabilityMatcher_1.parseSystemTenantItemPath)(fullPath);
604
+ if (!tenantId) {
605
+ return false;
606
+ }
607
+ try {
608
+ (0, validation_1.validateTenantIdFormat)(tenantId);
609
+ }
610
+ catch {
611
+ return false;
612
+ }
613
+ return this.tenantManager.isTenantAdminSigningKeyFor(tenantId, publicsignkey);
614
+ }
481
615
  // ==================== Tenant Middleware ====================
482
616
  tenantMiddleware(req, res, next) {
483
617
  const rawTenantId = req.params.tenantId?.toLowerCase();
@@ -512,6 +646,15 @@ class MindooDBServer {
512
646
  router.post("/auth/challenge", challengeRateLimit, this.handleSystemChallenge.bind(this));
513
647
  router.post("/auth/authenticate", authenticateRateLimit, this.handleSystemAuthenticate.bind(this));
514
648
  const authMiddleware = this.systemAdminMiddleware.bind(this);
649
+ // Cluster administration. Behind the same JWT + capability middleware as
650
+ // the rest of /system, so config.json can grant an auditor principal the
651
+ // GET routes without granting the POST/PATCH/DELETE ones.
652
+ router.use("/cluster", authMiddleware, (0, routes_1.createClusterRouter)({
653
+ cluster: this.cluster,
654
+ listTrustedServers: () => this.tenantManager.listTrustedServers(),
655
+ saveTrustedServer: (peer) => this.tenantManager.saveTrustedServer(peer),
656
+ removeTrustedServer: (name) => this.tenantManager.removeTrustedServer(name),
657
+ }));
515
658
  // Tenant CRUD
516
659
  router.get("/tenants", authMiddleware, (req, res) => {
517
660
  try {
@@ -651,7 +794,7 @@ class MindooDBServer {
651
794
  });
652
795
  router.post("/trusted-servers", authMiddleware, (req, res) => {
653
796
  try {
654
- const { name, signingPublicKey, encryptionPublicKey } = req.body;
797
+ const { name, signingPublicKey, encryptionPublicKey, url, direction, role, attachments } = req.body;
655
798
  if (!name || !signingPublicKey || !encryptionPublicKey) {
656
799
  res.status(400).json({ error: "name, signingPublicKey, and encryptionPublicKey are required" });
657
800
  return;
@@ -660,7 +803,41 @@ class MindooDBServer {
660
803
  (0, validation_1.validateStringLength)(signingPublicKey, validation_1.MAX_PEM_KEY_LENGTH, "signingPublicKey");
661
804
  (0, validation_1.validateStringLength)(encryptionPublicKey, validation_1.MAX_PEM_KEY_LENGTH, "encryptionPublicKey");
662
805
  const server = { name, signingPublicKey, encryptionPublicKey };
806
+ // Replication settings are optional: an entry without a url is a trust
807
+ // relationship only (inbound peers may authenticate, we never dial).
808
+ if (url !== undefined) {
809
+ (0, validation_1.validateStringLength)(url, 2048, "url");
810
+ const allowInsecure = /^(1|true)$/i.test(process.env[types_4.ENV_VARS.ALLOW_INSECURE_SYNC_URLS] ?? "");
811
+ try {
812
+ (0, urlSafety_1.assertSafeSyncUrl)(url, {
813
+ requireHttps: !allowInsecure,
814
+ allowPrivate: allowInsecure,
815
+ });
816
+ }
817
+ catch (e) {
818
+ if (e instanceof urlSafety_1.UnsafeUrlError) {
819
+ res.status(400).json({ error: e.message });
820
+ return;
821
+ }
822
+ throw e;
823
+ }
824
+ server.url = url;
825
+ }
826
+ for (const [field, value, guard] of [
827
+ ["direction", direction, types_1.isPeerSyncDirection],
828
+ ["role", role, types_1.isPeerRole],
829
+ ["attachments", attachments, types_1.isPeerAttachmentMode],
830
+ ]) {
831
+ if (value === undefined)
832
+ continue;
833
+ if (!guard(value)) {
834
+ res.status(400).json({ error: `Invalid ${field}: ${String(value)}` });
835
+ return;
836
+ }
837
+ Object.assign(server, { [field]: value });
838
+ }
663
839
  this.tenantManager.addTrustedServer(server);
840
+ this.cluster.syncReplicators();
664
841
  res.status(201).json({ success: true, message: `Trusted server "${name}" added` });
665
842
  }
666
843
  catch (error) {
@@ -685,6 +862,8 @@ class MindooDBServer {
685
862
  res.status(404).json({ error: "Trusted server not found" });
686
863
  return;
687
864
  }
865
+ // Revoking trust must also stop replicating with that server.
866
+ this.cluster.syncReplicators();
688
867
  res.json({ success: true, message: `Trusted server "${serverName}" removed` });
689
868
  }
690
869
  catch (error) {
@@ -696,128 +875,6 @@ class MindooDBServer {
696
875
  res.status(500).json({ error: "Failed to remove trusted server" });
697
876
  }
698
877
  });
699
- // Per-tenant sync server management
700
- router.get("/tenants/:tenantId/sync-servers", authMiddleware, (req, res) => {
701
- try {
702
- const tenantId = req.params.tenantId.toLowerCase();
703
- try {
704
- (0, validation_1.validateTenantId)(tenantId);
705
- }
706
- catch {
707
- res.status(400).json({ error: "Invalid tenantId format" });
708
- return;
709
- }
710
- if (!this.tenantManager.tenantExists(tenantId)) {
711
- res.status(404).json({ error: "Tenant not found" });
712
- return;
713
- }
714
- const servers = this.tenantManager.getTenantSyncServers(tenantId);
715
- res.json({ servers });
716
- }
717
- catch (error) {
718
- console.error("[MindooDBServer] Error listing sync servers:", error);
719
- res.status(500).json({ error: "Failed to list sync servers" });
720
- }
721
- });
722
- router.post("/tenants/:tenantId/sync-servers", authMiddleware, (req, res) => {
723
- try {
724
- const tenantId = req.params.tenantId.toLowerCase();
725
- try {
726
- (0, validation_1.validateTenantId)(tenantId);
727
- }
728
- catch {
729
- res.status(400).json({ error: "Invalid tenantId format" });
730
- return;
731
- }
732
- if (!this.tenantManager.tenantExists(tenantId)) {
733
- res.status(404).json({ error: "Tenant not found" });
734
- return;
735
- }
736
- const { name, url, syncIntervalMs, databases } = req.body;
737
- if (!name || !url) {
738
- res.status(400).json({ error: "name and url are required" });
739
- return;
740
- }
741
- if (!databases || !Array.isArray(databases) || databases.length === 0) {
742
- res.status(400).json({ error: "databases array is required and must not be empty" });
743
- return;
744
- }
745
- // Bound and format-validate each database id (audit, Low): these are
746
- // used to scope sync and must be safe identifiers, not arbitrary input.
747
- // The bound is overridable per deployment via
748
- // MINDOODB_MAX_SYNC_SERVER_DATABASES.
749
- (0, validation_1.validateArraySize)(databases, resolveMaxSyncServerDatabases(), "databases");
750
- for (const db of databases) {
751
- (0, validation_1.validateIdentifier)(db, "databases[]");
752
- }
753
- (0, validation_1.validateStringLength)(name, 256, "name");
754
- (0, validation_1.validateStringLength)(url, 2048, "url");
755
- // SSRF guard: an admin-supplied sync URL is fetched server-side, so
756
- // reject plaintext/internal targets unless explicitly allowed for dev.
757
- const allowInsecure = /^(1|true)$/i.test(process.env[types_3.ENV_VARS.ALLOW_INSECURE_SYNC_URLS] ?? "");
758
- try {
759
- (0, urlSafety_1.assertSafeSyncUrl)(url, {
760
- requireHttps: !allowInsecure,
761
- allowPrivate: allowInsecure,
762
- });
763
- }
764
- catch (e) {
765
- if (e instanceof urlSafety_1.UnsafeUrlError) {
766
- res.status(400).json({ error: e.message });
767
- return;
768
- }
769
- throw e;
770
- }
771
- const config = { name, url, databases };
772
- if (syncIntervalMs !== undefined) {
773
- config.syncIntervalMs = syncIntervalMs;
774
- }
775
- this.tenantManager.addTenantSyncServer(tenantId, config);
776
- res.status(201).json({ success: true, message: `Sync server "${name}" configured for tenant ${tenantId}` });
777
- }
778
- catch (error) {
779
- if (error instanceof validation_1.ValidationError) {
780
- res.status(400).json({ error: error.message });
781
- return;
782
- }
783
- console.error("[MindooDBServer] Error adding sync server:", error);
784
- res.status(500).json({ error: "Failed to add sync server" });
785
- }
786
- });
787
- router.delete("/tenants/:tenantId/sync-servers/:serverName", authMiddleware, (req, res) => {
788
- try {
789
- const tenantId = req.params.tenantId.toLowerCase();
790
- try {
791
- (0, validation_1.validateTenantId)(tenantId);
792
- }
793
- catch {
794
- res.status(400).json({ error: "Invalid tenantId format" });
795
- return;
796
- }
797
- if (!this.tenantManager.tenantExists(tenantId)) {
798
- res.status(404).json({ error: "Tenant not found" });
799
- return;
800
- }
801
- const serverName = decodeURIComponent(req.params.serverName);
802
- (0, validation_1.validateStringLength)(serverName, 256, "serverName");
803
- const removed = this.tenantManager.removeTenantSyncServer(tenantId, serverName);
804
- if (!removed) {
805
- res.status(404).json({ error: "Sync server not found" });
806
- return;
807
- }
808
- res.json({ success: true, message: `Sync server "${serverName}" removed from tenant ${tenantId}` });
809
- }
810
- catch (error) {
811
- if (error instanceof validation_1.ValidationError) {
812
- res.status(400).json({ error: error.message });
813
- return;
814
- }
815
- console.error("[MindooDBServer] Error removing sync server:", error);
816
- res.status(500).json({ error: "Failed to remove sync server" });
817
- }
818
- });
819
- // Per-tenant trigger-sync (moved from /:tenantId/admin/trigger-sync)
820
- router.post("/tenants/:tenantId/trigger-sync", authMiddleware, this.handleTriggerSync.bind(this));
821
878
  // Runtime config management
822
879
  router.get("/config/backups", authMiddleware, (req, res) => {
823
880
  try {
@@ -1298,6 +1355,47 @@ class MindooDBServer {
1298
1355
  const serverName = this.tenantManager.getServerPublicInfo()?.name ?? "unknown";
1299
1356
  return `Tenant ${tenantId} not found on server ${serverName}`;
1300
1357
  }
1358
+ // ==================== Device Discovery (server-wide) ====================
1359
+ async handleDeviceChallenge(req, res) {
1360
+ try {
1361
+ const { signingPublicKey } = req.body ?? {};
1362
+ if (typeof signingPublicKey !== "string" || !signingPublicKey.trim()) {
1363
+ res.status(400).json({ error: "signingPublicKey is required" });
1364
+ return;
1365
+ }
1366
+ (0, validation_1.validateStringLength)(signingPublicKey, validation_1.MAX_PEM_KEY_LENGTH, "signingPublicKey");
1367
+ const challenge = this.deviceDiscovery.createChallenge(signingPublicKey);
1368
+ res.json({ challenge });
1369
+ }
1370
+ catch (error) {
1371
+ this.handleRequestError(error, res);
1372
+ }
1373
+ }
1374
+ async handleDeviceDiscover(req, res) {
1375
+ try {
1376
+ const { challenge, signature } = req.body ?? {};
1377
+ if (typeof challenge !== "string" || !challenge.trim()) {
1378
+ res.status(400).json({ error: "challenge is required" });
1379
+ return;
1380
+ }
1381
+ if (typeof signature !== "string" || !signature.trim()) {
1382
+ res.status(400).json({ error: "signature is required" });
1383
+ return;
1384
+ }
1385
+ (0, validation_1.validateStringLength)(challenge, validation_1.MAX_CHALLENGE_LENGTH, "challenge");
1386
+ (0, validation_1.validateStringLength)(signature, validation_1.MAX_SIGNATURE_LENGTH, "signature");
1387
+ const signatureBytes = this.base64ToUint8Array(signature);
1388
+ const result = await this.deviceDiscovery.discover(challenge, signatureBytes);
1389
+ res.json(result);
1390
+ }
1391
+ catch (error) {
1392
+ if (error instanceof DeviceDiscoveryService_1.DeviceDiscoveryAuthError) {
1393
+ res.status(401).json({ error: error.message });
1394
+ return;
1395
+ }
1396
+ this.handleRequestError(error, res);
1397
+ }
1398
+ }
1301
1399
  // ==================== Tenant Auth Handlers ====================
1302
1400
  // Two-step challenge-response flow:
1303
1401
  // 1. POST /auth/challenge → client sends username, server returns a random nonce
@@ -1533,16 +1631,20 @@ class MindooDBServer {
1533
1631
  if (this.syncEventBus.listenerCount === 0) {
1534
1632
  return;
1535
1633
  }
1536
- void serverStore
1537
- .handleGetStoreHead(token)
1538
- .catch(() => null)
1539
- .then((head) => {
1634
+ // Peer replicators subscribe to this bus, so a write that arrived from a
1635
+ // peer has to be labelled: without the label it would be pushed straight
1636
+ // back to its origin, and two bidirectional peers would ping-pong.
1637
+ const originPeer = this.cluster?.auth
1638
+ ? this.cluster.auth.validateToken(token).then((payload) => payload?.sub)
1639
+ : Promise.resolve(undefined);
1640
+ void Promise.all([serverStore.handleGetStoreHead(token).catch(() => null), originPeer]).then(([head, peerName]) => {
1540
1641
  this.syncEventBus.publish({
1541
1642
  tenantId,
1542
1643
  dbId,
1543
1644
  storeKind,
1544
1645
  epoch: head?.epoch,
1545
1646
  maxReceiptOrder: head?.maxReceiptOrder,
1647
+ originPeer: peerName,
1546
1648
  });
1547
1649
  });
1548
1650
  }
@@ -1907,9 +2009,6 @@ class MindooDBServer {
1907
2009
  }
1908
2010
  }
1909
2011
  // ==================== Management Handlers ====================
1910
- async handleTriggerSync(req, res) {
1911
- res.json({ success: true, message: "Sync triggered (not yet implemented)" });
1912
- }
1913
2012
  // ==================== Helper Methods ====================
1914
2013
  /**
1915
2014
  * Derive the StoreKind from the request path. Both "docs" and
@@ -1919,17 +2018,17 @@ class MindooDBServer {
1919
2018
  getStoreKindFromRequest(req) {
1920
2019
  const trimmedPath = req.path.replace(/\/$/, "");
1921
2020
  if (trimmedPath.startsWith("/sync/docs/") || trimmedPath === "/sync/docs/capabilities" || trimmedPath === "/sync/docs/getAllIds") {
1922
- return types_1.StoreKind.docs;
2021
+ return types_2.StoreKind.docs;
1923
2022
  }
1924
2023
  if (trimmedPath.startsWith("/sync/attachments/") || trimmedPath === "/sync/attachments/capabilities" || trimmedPath === "/sync/attachments/getAllIds") {
1925
- return types_1.StoreKind.attachments;
2024
+ return types_2.StoreKind.attachments;
1926
2025
  }
1927
2026
  throw new Error(`Unsupported sync store kind for path: ${req.path}`);
1928
2027
  }
1929
2028
  extractToken(req) {
1930
2029
  const auth = req.headers.authorization;
1931
2030
  if (!auth || !auth.startsWith("Bearer ")) {
1932
- throw new types_2.NetworkError("INVALID_TOKEN", "Missing or invalid Authorization header");
2031
+ throw new types_3.NetworkError("INVALID_TOKEN", "Missing or invalid Authorization header");
1933
2032
  }
1934
2033
  return auth.substring(7);
1935
2034
  }
@@ -2007,70 +2106,16 @@ class MindooDBServer {
2007
2106
  }
2008
2107
  // ==================== Serialization ====================
2009
2108
  // Entries are stored with binary fields (Uint8Array) internally but
2010
- // transmitted as base64 strings over JSON. These methods handle the
2011
- // conversion in both directions.
2109
+ // transmitted as base64 strings over JSON. Shared with the client
2110
+ // (entryWireCodec) so signed trailing blocks cannot drift off the wire.
2012
2111
  serializeEntryMetadata(metadata) {
2013
- return {
2014
- entryType: metadata.entryType,
2015
- id: metadata.id,
2016
- contentHash: metadata.contentHash,
2017
- docId: metadata.docId,
2018
- dependencyIds: metadata.dependencyIds,
2019
- createdAt: metadata.createdAt,
2020
- receiptOrder: metadata.receiptOrder,
2021
- createdByPublicKey: metadata.createdByPublicKey,
2022
- decryptionKeyId: metadata.decryptionKeyId,
2023
- signature: this.uint8ArrayToBase64(metadata.signature),
2024
- metadataSignature: metadata.metadataSignature
2025
- ? this.uint8ArrayToBase64(metadata.metadataSignature)
2026
- : undefined,
2027
- originalSize: metadata.originalSize,
2028
- encryptedSize: metadata.encryptedSize,
2029
- receivedAt: metadata.receivedAt,
2030
- receivedByPublicKey: metadata.receivedByPublicKey,
2031
- receivedDateSignature: metadata.receivedDateSignature
2032
- ? this.uint8ArrayToBase64(metadata.receivedDateSignature)
2033
- : undefined,
2034
- attachmentRefs: metadata.attachmentRefs,
2035
- // Bound into metadataSignature, so it must survive the round-trip.
2036
- provenance: metadata.provenance,
2037
- entryVersion: metadata.entryVersion,
2038
- };
2112
+ return (0, entryWireCodec_1.serializeEntryMetadata)(metadata);
2039
2113
  }
2040
2114
  deserializeEntryMetadata(serialized) {
2041
- return {
2042
- entryType: serialized.entryType,
2043
- id: serialized.id,
2044
- contentHash: serialized.contentHash,
2045
- docId: serialized.docId,
2046
- dependencyIds: serialized.dependencyIds,
2047
- createdAt: serialized.createdAt,
2048
- receiptOrder: serialized.receiptOrder,
2049
- createdByPublicKey: serialized.createdByPublicKey,
2050
- decryptionKeyId: serialized.decryptionKeyId,
2051
- signature: this.base64ToUint8Array(serialized.signature),
2052
- metadataSignature: serialized.metadataSignature
2053
- ? this.base64ToUint8Array(serialized.metadataSignature)
2054
- : undefined,
2055
- originalSize: serialized.originalSize,
2056
- encryptedSize: serialized.encryptedSize,
2057
- // Preserve any pre-existing witness receipt (e.g. on re-sync or
2058
- // server-to-server forwarding); a fresh local push has none.
2059
- receivedAt: serialized.receivedAt,
2060
- receivedByPublicKey: serialized.receivedByPublicKey,
2061
- receivedDateSignature: serialized.receivedDateSignature
2062
- ? this.base64ToUint8Array(serialized.receivedDateSignature)
2063
- : undefined,
2064
- attachmentRefs: serialized.attachmentRefs,
2065
- provenance: serialized.provenance,
2066
- entryVersion: serialized.entryVersion,
2067
- };
2115
+ return (0, entryWireCodec_1.deserializeEntryMetadata)(serialized);
2068
2116
  }
2069
2117
  deserializeEntry(serialized) {
2070
- return {
2071
- ...this.deserializeEntryMetadata(serialized),
2072
- encryptedData: this.base64ToUint8Array(serialized.encryptedData),
2073
- };
2118
+ return (0, entryWireCodec_1.deserializeStoreEntry)(serialized);
2074
2119
  }
2075
2120
  uint8ArrayToBase64(bytes) {
2076
2121
  return Buffer.from(bytes).toString("base64");