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.
- package/README-server.md +136 -71
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +30 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +67 -20
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.d.ts +2 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.js +6 -62
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +82 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +229 -25
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
- package/dist/browser/index.mjs +59 -0
- package/dist/core/BaseMindooDB.d.ts +148 -80
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +890 -692
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +59 -7
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +407 -86
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts +61 -9
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.js +363 -35
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
- package/dist/core/BaseMindooTenantFactory.d.ts +17 -1
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantFactory.js +286 -17
- package/dist/core/BaseMindooTenantFactory.js.map +1 -1
- package/dist/core/MindooDBServerAdmin.d.ts +44 -22
- package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
- package/dist/core/MindooDBServerAdmin.js +45 -21
- package/dist/core/MindooDBServerAdmin.js.map +1 -1
- package/dist/core/accesscontrol/DirectoryStateNode.d.ts +68 -1
- package/dist/core/accesscontrol/DirectoryStateNode.d.ts.map +1 -1
- package/dist/core/accesscontrol/DirectoryStateNode.js +157 -4
- package/dist/core/accesscontrol/DirectoryStateNode.js.map +1 -1
- package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
- package/dist/core/accesscontrol/directoryProjection.js +14 -5
- package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
- package/dist/core/accesscontrol/grantKeys.d.ts +17 -14
- package/dist/core/accesscontrol/grantKeys.d.ts.map +1 -1
- package/dist/core/accesscontrol/grantKeys.js +51 -21
- package/dist/core/accesscontrol/grantKeys.js.map +1 -1
- package/dist/core/accesscontrol/types.d.ts +2 -1
- package/dist/core/accesscontrol/types.d.ts.map +1 -1
- package/dist/core/accesscontrol/types.js +3 -2
- package/dist/core/accesscontrol/types.js.map +1 -1
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts +7 -0
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/AuthenticationService.js +70 -14
- package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
- package/dist/core/appendonlystores/network/entryWireCodec.d.ts +51 -0
- package/dist/core/appendonlystores/network/entryWireCodec.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/entryWireCodec.js +93 -0
- package/dist/core/appendonlystores/network/entryWireCodec.js.map +1 -0
- package/dist/core/appendonlystores/syncStores.d.ts +147 -0
- package/dist/core/appendonlystores/syncStores.d.ts.map +1 -0
- package/dist/core/appendonlystores/syncStores.js +612 -0
- package/dist/core/appendonlystores/syncStores.js.map +1 -0
- package/dist/core/builtinDbInvariants.d.ts +83 -0
- package/dist/core/builtinDbInvariants.d.ts.map +1 -0
- package/dist/core/builtinDbInvariants.js +244 -0
- package/dist/core/builtinDbInvariants.js.map +1 -0
- package/dist/core/copy/feasibility.d.ts.map +1 -1
- package/dist/core/copy/feasibility.js +3 -0
- package/dist/core/copy/feasibility.js.map +1 -1
- package/dist/core/copy/flatten.d.ts.map +1 -1
- package/dist/core/copy/flatten.js +6 -1
- package/dist/core/copy/flatten.js.map +1 -1
- package/dist/core/copy/host.d.ts +5 -0
- package/dist/core/copy/host.d.ts.map +1 -1
- package/dist/core/copy/preflight.d.ts.map +1 -1
- package/dist/core/copy/preflight.js +7 -1
- package/dist/core/copy/preflight.js.map +1 -1
- package/dist/core/copy/types.d.ts +7 -0
- package/dist/core/copy/types.d.ts.map +1 -1
- package/dist/core/crypto/EntrySignature.d.ts +8 -2
- package/dist/core/crypto/EntrySignature.d.ts.map +1 -1
- package/dist/core/crypto/EntrySignature.js +14 -5
- package/dist/core/crypto/EntrySignature.js.map +1 -1
- package/dist/core/crypto/MindooDocSigner.d.ts.map +1 -1
- package/dist/core/crypto/MindooDocSigner.js +11 -2
- package/dist/core/crypto/MindooDocSigner.js.map +1 -1
- package/dist/core/crypto/RSAEncryption.d.ts +19 -0
- package/dist/core/crypto/RSAEncryption.d.ts.map +1 -1
- package/dist/core/crypto/RSAEncryption.js +36 -3
- package/dist/core/crypto/RSAEncryption.js.map +1 -1
- package/dist/core/crypto/privateKeyEncryption.js +1 -1
- package/dist/core/crypto/privateKeyEncryption.js.map +1 -1
- package/dist/core/crypto/rsaOaep3072.d.ts +12 -0
- package/dist/core/crypto/rsaOaep3072.d.ts.map +1 -0
- package/dist/core/crypto/rsaOaep3072.js +30 -0
- package/dist/core/crypto/rsaOaep3072.js.map +1 -0
- package/dist/core/index.d.ts +7 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +33 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/join/deviceJoinBootstrap.d.ts +23 -0
- package/dist/core/join/deviceJoinBootstrap.d.ts.map +1 -0
- package/dist/core/join/deviceJoinBootstrap.js +37 -0
- package/dist/core/join/deviceJoinBootstrap.js.map +1 -0
- package/dist/core/keys/KeyBag.d.ts +20 -2
- package/dist/core/keys/KeyBag.d.ts.map +1 -1
- package/dist/core/keys/KeyBag.js +22 -10
- package/dist/core/keys/KeyBag.js.map +1 -1
- package/dist/core/logging/MindooLogger.d.ts.map +1 -1
- package/dist/core/logging/MindooLogger.js +65 -40
- package/dist/core/logging/MindooLogger.js.map +1 -1
- package/dist/core/tenantIdValidation.d.ts.map +1 -1
- package/dist/core/tenantIdValidation.js +1 -1
- package/dist/core/tenantIdValidation.js.map +1 -1
- package/dist/core/tenantSetup.d.ts +94 -9
- package/dist/core/tenantSetup.d.ts.map +1 -1
- package/dist/core/tenantSetup.js +208 -20
- package/dist/core/tenantSetup.js.map +1 -1
- package/dist/core/types.d.ts +313 -33
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +14 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/uri/joinRequestUri.d.ts.map +1 -1
- package/dist/core/uri/joinRequestUri.js +12 -3
- package/dist/core/uri/joinRequestUri.js.map +1 -1
- package/dist/core/userid/canonicalUsername.d.ts +66 -0
- package/dist/core/userid/canonicalUsername.d.ts.map +1 -0
- package/dist/core/userid/canonicalUsername.js +235 -0
- package/dist/core/userid/canonicalUsername.js.map +1 -0
- package/dist/core/userid/index.d.ts +9 -0
- package/dist/core/userid/index.d.ts.map +1 -1
- package/dist/core/userid/index.js +14 -0
- package/dist/core/userid/index.js.map +1 -1
- package/dist/core/userkeys/UserKeyDocument.d.ts +50 -0
- package/dist/core/userkeys/UserKeyDocument.d.ts.map +1 -0
- package/dist/core/userkeys/UserKeyDocument.js +145 -0
- package/dist/core/userkeys/UserKeyDocument.js.map +1 -0
- package/dist/core/userkeys/UserKeyManager.d.ts +170 -0
- package/dist/core/userkeys/UserKeyManager.d.ts.map +1 -0
- package/dist/core/userkeys/UserKeyManager.js +1277 -0
- package/dist/core/userkeys/UserKeyManager.js.map +1 -0
- package/dist/core/userkeys/fingerprint.d.ts +17 -0
- package/dist/core/userkeys/fingerprint.d.ts.map +1 -0
- package/dist/core/userkeys/fingerprint.js +59 -0
- package/dist/core/userkeys/fingerprint.js.map +1 -0
- package/dist/core/userkeys/index.d.ts +11 -0
- package/dist/core/userkeys/index.d.ts.map +1 -0
- package/dist/core/userkeys/index.js +49 -0
- package/dist/core/userkeys/index.js.map +1 -0
- package/dist/core/userkeys/recipients.d.ts +51 -0
- package/dist/core/userkeys/recipients.d.ts.map +1 -0
- package/dist/core/userkeys/recipients.js +292 -0
- package/dist/core/userkeys/recipients.js.map +1 -0
- package/dist/core/userkeys/resolveUserKeyDocument.d.ts +21 -0
- package/dist/core/userkeys/resolveUserKeyDocument.d.ts.map +1 -0
- package/dist/core/userkeys/resolveUserKeyDocument.js +98 -0
- package/dist/core/userkeys/resolveUserKeyDocument.js.map +1 -0
- package/dist/core/userkeys/sealedCrypto.d.ts +21 -0
- package/dist/core/userkeys/sealedCrypto.d.ts.map +1 -0
- package/dist/core/userkeys/sealedCrypto.js +79 -0
- package/dist/core/userkeys/sealedCrypto.js.map +1 -0
- package/dist/core/userkeys/sealedTypes.d.ts +72 -0
- package/dist/core/userkeys/sealedTypes.d.ts.map +1 -0
- package/dist/core/userkeys/sealedTypes.js +14 -0
- package/dist/core/userkeys/sealedTypes.js.map +1 -0
- package/dist/core/userkeys/types.d.ts +70 -0
- package/dist/core/userkeys/types.d.ts.map +1 -0
- package/dist/core/userkeys/types.js +48 -0
- package/dist/core/userkeys/types.js.map +1 -0
- package/dist/core/userkeys/validateUserKeyDocument.d.ts +19 -0
- package/dist/core/userkeys/validateUserKeyDocument.d.ts.map +1 -0
- package/dist/core/userkeys/validateUserKeyDocument.js +42 -0
- package/dist/core/userkeys/validateUserKeyDocument.js.map +1 -0
- package/dist/node/server/CapabilityMatcher.d.ts +7 -2
- package/dist/node/server/CapabilityMatcher.d.ts.map +1 -1
- package/dist/node/server/CapabilityMatcher.js +12 -3
- package/dist/node/server/CapabilityMatcher.js.map +1 -1
- package/dist/node/server/DeviceDiscoveryService.d.ts +36 -0
- package/dist/node/server/DeviceDiscoveryService.d.ts.map +1 -0
- package/dist/node/server/DeviceDiscoveryService.js +165 -0
- package/dist/node/server/DeviceDiscoveryService.js.map +1 -0
- package/dist/node/server/MindooDBServer.d.ts +27 -1
- package/dist/node/server/MindooDBServer.d.ts.map +1 -1
- package/dist/node/server/MindooDBServer.js +265 -220
- package/dist/node/server/MindooDBServer.js.map +1 -1
- package/dist/node/server/SyncEventBus.d.ts +10 -0
- package/dist/node/server/SyncEventBus.d.ts.map +1 -1
- package/dist/node/server/SyncEventBus.js.map +1 -1
- package/dist/node/server/SystemAdminAuth.d.ts +7 -0
- package/dist/node/server/SystemAdminAuth.d.ts.map +1 -1
- package/dist/node/server/SystemAdminAuth.js +4 -1
- package/dist/node/server/SystemAdminAuth.js.map +1 -1
- package/dist/node/server/TenantManager.d.ts +86 -5
- package/dist/node/server/TenantManager.d.ts.map +1 -1
- package/dist/node/server/TenantManager.js +235 -71
- package/dist/node/server/TenantManager.js.map +1 -1
- package/dist/node/server/add-to-network.d.ts.map +1 -1
- package/dist/node/server/add-to-network.js +33 -15
- package/dist/node/server/add-to-network.js.map +1 -1
- package/dist/node/server/canonicalSetupName.d.ts +22 -0
- package/dist/node/server/canonicalSetupName.d.ts.map +1 -0
- package/dist/node/server/canonicalSetupName.js +38 -0
- package/dist/node/server/canonicalSetupName.js.map +1 -0
- package/dist/node/server/config.d.ts.map +1 -1
- package/dist/node/server/config.js +33 -1
- package/dist/node/server/config.js.map +1 -1
- package/dist/node/server/peer/ClusterAuditLog.d.ts +64 -0
- package/dist/node/server/peer/ClusterAuditLog.d.ts.map +1 -0
- package/dist/node/server/peer/ClusterAuditLog.js +99 -0
- package/dist/node/server/peer/ClusterAuditLog.js.map +1 -0
- package/dist/node/server/peer/ClusterJobRunner.d.ts +43 -0
- package/dist/node/server/peer/ClusterJobRunner.d.ts.map +1 -0
- package/dist/node/server/peer/ClusterJobRunner.js +119 -0
- package/dist/node/server/peer/ClusterJobRunner.js.map +1 -0
- package/dist/node/server/peer/ClusterManager.d.ts +116 -0
- package/dist/node/server/peer/ClusterManager.d.ts.map +1 -0
- package/dist/node/server/peer/ClusterManager.js +274 -0
- package/dist/node/server/peer/ClusterManager.js.map +1 -0
- package/dist/node/server/peer/PeerAuthService.d.ts +69 -0
- package/dist/node/server/peer/PeerAuthService.d.ts.map +1 -0
- package/dist/node/server/peer/PeerAuthService.js +208 -0
- package/dist/node/server/peer/PeerAuthService.js.map +1 -0
- package/dist/node/server/peer/PeerReplicator.d.ts +232 -0
- package/dist/node/server/peer/PeerReplicator.d.ts.map +1 -0
- package/dist/node/server/peer/PeerReplicator.js +879 -0
- package/dist/node/server/peer/PeerReplicator.js.map +1 -0
- package/dist/node/server/peer/routes.d.ts +66 -0
- package/dist/node/server/peer/routes.d.ts.map +1 -0
- package/dist/node/server/peer/routes.js +322 -0
- package/dist/node/server/peer/routes.js.map +1 -0
- package/dist/node/server/peer/types.d.ts +244 -0
- package/dist/node/server/peer/types.d.ts.map +1 -0
- package/dist/node/server/peer/types.js +189 -0
- package/dist/node/server/peer/types.js.map +1 -0
- package/dist/node/server/server.d.ts +1 -1
- package/dist/node/server/server.js +18 -37
- package/dist/node/server/server.js.map +1 -1
- package/dist/node/server/serverinit.d.ts +2 -1
- package/dist/node/server/serverinit.d.ts.map +1 -1
- package/dist/node/server/serverinit.js +34 -12
- package/dist/node/server/serverinit.js.map +1 -1
- package/dist/node/server/types.d.ts +42 -30
- package/dist/node/server/types.d.ts.map +1 -1
- package/dist/node/server/types.js +1 -7
- package/dist/node/server/types.js.map +1 -1
- package/dist/node/server/validation.d.ts +0 -6
- package/dist/node/server/validation.d.ts.map +1 -1
- package/dist/node/server/validation.js +1 -7
- package/dist/node/server/validation.js.map +1 -1
- 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
|
|
64
|
-
const
|
|
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
|
|
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[
|
|
178
|
-
allowInsecureUrls: /^(1|true)$/i.test(process.env[
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
2011
|
-
//
|
|
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");
|