mindoodb 0.0.85 → 0.0.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-server.md +6 -2
- package/README.md +6 -4
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +2 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +151 -12
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +340 -24
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
- package/dist/browser/index.mjs +14 -0
- package/dist/cloudflare/DurableObjectSql.d.ts +56 -0
- package/dist/cloudflare/DurableObjectSql.d.ts.map +1 -0
- package/dist/cloudflare/DurableObjectSql.js +180 -0
- package/dist/cloudflare/DurableObjectSql.js.map +1 -0
- package/dist/cloudflare/MindooDatabaseObject.d.ts +86 -0
- package/dist/cloudflare/MindooDatabaseObject.d.ts.map +1 -0
- package/dist/cloudflare/MindooDatabaseObject.js +614 -0
- package/dist/cloudflare/MindooDatabaseObject.js.map +1 -0
- package/dist/cloudflare/MindooServerObject.d.ts +63 -0
- package/dist/cloudflare/MindooServerObject.d.ts.map +1 -0
- package/dist/cloudflare/MindooServerObject.js +521 -0
- package/dist/cloudflare/MindooServerObject.js.map +1 -0
- package/dist/cloudflare/WorkersCryptoAdapter.d.ts +7 -0
- package/dist/cloudflare/WorkersCryptoAdapter.d.ts.map +1 -0
- package/dist/cloudflare/WorkersCryptoAdapter.js +14 -0
- package/dist/cloudflare/WorkersCryptoAdapter.js.map +1 -0
- package/dist/cloudflare/host.d.ts +49 -0
- package/dist/cloudflare/host.d.ts.map +1 -0
- package/dist/cloudflare/host.js +104 -0
- package/dist/cloudflare/host.js.map +1 -0
- package/dist/cloudflare/http.d.ts +91 -0
- package/dist/cloudflare/http.d.ts.map +1 -0
- package/dist/cloudflare/http.js +266 -0
- package/dist/cloudflare/http.js.map +1 -0
- package/dist/cloudflare/index.d.ts +12 -0
- package/dist/cloudflare/index.d.ts.map +1 -0
- package/dist/cloudflare/index.js +20 -0
- package/dist/cloudflare/index.js.map +1 -0
- package/dist/cloudflare/storeRpc.d.ts +22 -0
- package/dist/cloudflare/storeRpc.d.ts.map +1 -0
- package/dist/cloudflare/storeRpc.js +85 -0
- package/dist/cloudflare/storeRpc.js.map +1 -0
- package/dist/cloudflare/types.d.ts +101 -0
- package/dist/cloudflare/types.d.ts.map +1 -0
- package/dist/cloudflare/types.js +10 -0
- package/dist/cloudflare/types.js.map +1 -0
- package/dist/cloudflare/worker.d.ts +25 -0
- package/dist/cloudflare/worker.d.ts.map +1 -0
- package/dist/cloudflare/worker.js +170 -0
- package/dist/cloudflare/worker.js.map +1 -0
- package/dist/core/BaseMindooDB.d.ts +95 -10
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +441 -98
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +43 -0
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +157 -6
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts +103 -0
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.js +371 -8
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantFactory.js +2 -1
- package/dist/core/BaseMindooTenantFactory.js.map +1 -1
- package/dist/core/ChangeDocDraft.d.ts +133 -0
- package/dist/core/ChangeDocDraft.d.ts.map +1 -0
- package/dist/core/ChangeDocDraft.js +492 -0
- package/dist/core/ChangeDocDraft.js.map +1 -0
- package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
- package/dist/core/MindooDBServerAdmin.js +2 -1
- package/dist/core/MindooDBServerAdmin.js.map +1 -1
- package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
- package/dist/core/accesscontrol/directoryProjection.js +19 -0
- package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
- package/dist/core/accesscontrol/evaluate.d.ts +14 -0
- package/dist/core/accesscontrol/evaluate.d.ts.map +1 -1
- package/dist/core/accesscontrol/evaluate.js +42 -1
- package/dist/core/accesscontrol/evaluate.js.map +1 -1
- package/dist/core/accesscontrol/types.d.ts +88 -4
- package/dist/core/accesscontrol/types.d.ts.map +1 -1
- package/dist/core/accesscontrol/types.js +114 -1
- package/dist/core/accesscontrol/types.js.map +1 -1
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts +14 -0
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/AuthenticationService.js +27 -7
- package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
- package/dist/core/appendonlystores/network/IrohNetworkTransport.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/IrohNetworkTransport.js +12 -0
- package/dist/core/appendonlystores/network/IrohNetworkTransport.js.map +1 -1
- package/dist/core/appendonlystores/network/IrohStreamIO.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/IrohStreamIO.js +3 -23
- package/dist/core/appendonlystores/network/IrohStreamIO.js.map +1 -1
- package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.d.ts +6 -0
- package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.d.ts.map +1 -1
- package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.js +20 -1
- package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.js.map +1 -1
- package/dist/core/appendonlystores/rpc/storeRpcCodec.d.ts.map +1 -1
- package/dist/core/appendonlystores/rpc/storeRpcCodec.js +3 -23
- package/dist/core/appendonlystores/rpc/storeRpcCodec.js.map +1 -1
- package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts +7 -0
- package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts.map +1 -1
- package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js +39 -30
- package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js.map +1 -1
- package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts +37 -1
- package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts.map +1 -1
- package/dist/core/appendonlystores/sqlite/SqliteDriver.js +99 -40
- package/dist/core/appendonlystores/sqlite/SqliteDriver.js.map +1 -1
- package/dist/core/appendonlystores/syncStores.d.ts.map +1 -1
- package/dist/core/appendonlystores/syncStores.js +25 -5
- package/dist/core/appendonlystores/syncStores.js.map +1 -1
- package/dist/core/appendonlystores/types.d.ts +7 -1
- package/dist/core/appendonlystores/types.d.ts.map +1 -1
- package/dist/core/appendonlystores/types.js +1 -0
- package/dist/core/appendonlystores/types.js.map +1 -1
- package/dist/core/builtinDbInvariants.d.ts +49 -0
- package/dist/core/builtinDbInvariants.d.ts.map +1 -1
- package/dist/core/builtinDbInvariants.js +86 -1
- package/dist/core/builtinDbInvariants.js.map +1 -1
- package/dist/core/crypto/AuthChallenge.d.ts +68 -0
- package/dist/core/crypto/AuthChallenge.d.ts.map +1 -0
- package/dist/core/crypto/AuthChallenge.js +118 -0
- package/dist/core/crypto/AuthChallenge.js.map +1 -0
- package/dist/core/crypto/DomainStatement.d.ts +5 -3
- package/dist/core/crypto/DomainStatement.d.ts.map +1 -1
- package/dist/core/crypto/DomainStatement.js +5 -3
- package/dist/core/crypto/DomainStatement.js.map +1 -1
- package/dist/core/crypto/encryptedFields.d.ts +11 -0
- package/dist/core/crypto/encryptedFields.d.ts.map +1 -1
- package/dist/core/crypto/encryptedFields.js +18 -0
- package/dist/core/crypto/encryptedFields.js.map +1 -1
- package/dist/core/expressions/evaluateExpression.d.ts +8 -0
- package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
- package/dist/core/expressions/evaluateExpression.js +29 -4
- package/dist/core/expressions/evaluateExpression.js.map +1 -1
- package/dist/core/expressions/metadata.js +4 -4
- package/dist/core/expressions/metadata.js.map +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +17 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/query/executeQuery.d.ts.map +1 -1
- package/dist/core/query/executeQuery.js +5 -1
- package/dist/core/query/executeQuery.js.map +1 -1
- package/dist/core/query/executeQueryInclude.d.ts.map +1 -1
- package/dist/core/query/executeQueryInclude.js +2 -0
- package/dist/core/query/executeQueryInclude.js.map +1 -1
- package/dist/core/query/queryView.d.ts.map +1 -1
- package/dist/core/query/queryView.js +5 -1
- package/dist/core/query/queryView.js.map +1 -1
- package/dist/core/types.d.ts +204 -10
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/uri/MindooURI.d.ts.map +1 -1
- package/dist/core/uri/MindooURI.js +3 -24
- package/dist/core/uri/MindooURI.js.map +1 -1
- package/dist/core/userkeys/index.d.ts +1 -0
- package/dist/core/userkeys/index.d.ts.map +1 -1
- package/dist/core/userkeys/index.js +5 -1
- package/dist/core/userkeys/index.js.map +1 -1
- package/dist/core/userkeys/sealedValues.d.ts +54 -0
- package/dist/core/userkeys/sealedValues.d.ts.map +1 -0
- package/dist/core/userkeys/sealedValues.js +67 -0
- package/dist/core/userkeys/sealedValues.js.map +1 -0
- package/dist/core/utils/base64.d.ts +17 -0
- package/dist/core/utils/base64.d.ts.map +1 -0
- package/dist/core/utils/base64.js +54 -0
- package/dist/core/utils/base64.js.map +1 -0
- package/dist/core/utils/dynamicImportInterop.d.ts +10 -0
- package/dist/core/utils/dynamicImportInterop.d.ts.map +1 -0
- package/dist/core/utils/dynamicImportInterop.js +22 -0
- package/dist/core/utils/dynamicImportInterop.js.map +1 -0
- package/dist/core/utils/idGeneration.d.ts.map +1 -1
- package/dist/core/utils/idGeneration.js +14 -3
- package/dist/core/utils/idGeneration.js.map +1 -1
- package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -1
- package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +7 -0
- package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -1
- package/dist/node/server/FsServerDataStore.d.ts +23 -0
- package/dist/node/server/FsServerDataStore.d.ts.map +1 -0
- package/dist/node/server/FsServerDataStore.js +59 -0
- package/dist/node/server/FsServerDataStore.js.map +1 -0
- package/dist/node/server/IrohServerEndpoint.d.ts +1 -1
- package/dist/node/server/IrohServerEndpoint.d.ts.map +1 -1
- package/dist/node/server/IrohServerRpc.d.ts +5 -5
- package/dist/node/server/IrohServerRpc.d.ts.map +1 -1
- package/dist/node/server/IrohServerRpc.js +1 -1
- package/dist/node/server/IrohServerRpc.js.map +1 -1
- package/dist/node/server/MindooDBServer.d.ts +29 -150
- package/dist/node/server/MindooDBServer.d.ts.map +1 -1
- package/dist/node/server/MindooDBServer.js +160 -1573
- package/dist/node/server/MindooDBServer.js.map +1 -1
- package/dist/node/server/PurgedDocRegistry.d.ts +4 -27
- package/dist/node/server/PurgedDocRegistry.d.ts.map +1 -1
- package/dist/node/server/PurgedDocRegistry.js +6 -83
- package/dist/node/server/PurgedDocRegistry.js.map +1 -1
- package/dist/node/server/StoreFactory.d.ts +1 -1
- package/dist/node/server/StoreFactory.d.ts.map +1 -1
- package/dist/node/server/TenantManager.d.ts +16 -284
- package/dist/node/server/TenantManager.d.ts.map +1 -1
- package/dist/node/server/TenantManager.js +35 -1168
- package/dist/node/server/TenantManager.js.map +1 -1
- package/dist/node/server/add-to-network.js +1 -1
- package/dist/node/server/add-to-network.js.map +1 -1
- package/dist/node/server/config.d.ts +11 -15
- package/dist/node/server/config.d.ts.map +1 -1
- package/dist/node/server/config.js +30 -245
- package/dist/node/server/config.js.map +1 -1
- package/dist/node/server/expressAdapter.d.ts +23 -0
- package/dist/node/server/expressAdapter.d.ts.map +1 -0
- package/dist/node/server/expressAdapter.js +108 -0
- package/dist/node/server/expressAdapter.js.map +1 -0
- package/dist/node/server/nativeIrohStreamIO.d.ts +1 -1
- package/dist/node/server/nativeIrohStreamIO.d.ts.map +1 -1
- package/dist/node/server/peer/FileClusterAuditStorage.d.ts +16 -0
- package/dist/node/server/peer/FileClusterAuditStorage.d.ts.map +1 -0
- package/dist/node/server/peer/FileClusterAuditStorage.js +39 -0
- package/dist/node/server/peer/FileClusterAuditStorage.js.map +1 -0
- package/dist/node/server/peer/IrohPeerLink.d.ts +1 -1
- package/dist/node/server/peer/IrohPeerLink.d.ts.map +1 -1
- package/dist/node/server/peer/routes.d.ts +10 -44
- package/dist/node/server/peer/routes.d.ts.map +1 -1
- package/dist/node/server/peer/routes.js +33 -275
- package/dist/node/server/peer/routes.js.map +1 -1
- package/dist/node/server/resolveServerPassword.js +1 -1
- package/dist/node/server/resolveServerPassword.js.map +1 -1
- package/dist/node/server/server.js +1 -1
- package/dist/node/server/server.js.map +1 -1
- package/dist/node/server/serverinit.js +1 -1
- package/dist/node/server/serverinit.js.map +1 -1
- package/dist/server/CapabilityMatcher.d.ts +60 -0
- package/dist/server/CapabilityMatcher.d.ts.map +1 -0
- package/dist/server/CapabilityMatcher.js +138 -0
- package/dist/server/CapabilityMatcher.js.map +1 -0
- package/dist/server/DeviceDiscoveryService.d.ts +36 -0
- package/dist/server/DeviceDiscoveryService.d.ts.map +1 -0
- package/dist/server/DeviceDiscoveryService.js +164 -0
- package/dist/server/DeviceDiscoveryService.js.map +1 -0
- package/dist/server/PurgedDocRegistry.d.ts +35 -0
- package/dist/server/PurgedDocRegistry.d.ts.map +1 -0
- package/dist/server/PurgedDocRegistry.js +90 -0
- package/dist/server/PurgedDocRegistry.js.map +1 -0
- package/dist/server/ServerDataStore.d.ts +67 -0
- package/dist/server/ServerDataStore.d.ts.map +1 -0
- package/dist/server/ServerDataStore.js +88 -0
- package/dist/server/ServerDataStore.js.map +1 -0
- package/dist/server/SyncEventBus.d.ts +39 -0
- package/dist/server/SyncEventBus.d.ts.map +1 -0
- package/dist/server/SyncEventBus.js +41 -0
- package/dist/server/SyncEventBus.js.map +1 -0
- package/dist/server/SystemAdminAuth.d.ts +82 -0
- package/dist/server/SystemAdminAuth.d.ts.map +1 -0
- package/dist/server/SystemAdminAuth.js +227 -0
- package/dist/server/SystemAdminAuth.js.map +1 -0
- package/dist/server/TenantManager.d.ts +366 -0
- package/dist/server/TenantManager.d.ts.map +1 -0
- package/dist/server/TenantManager.js +1416 -0
- package/dist/server/TenantManager.js.map +1 -0
- package/dist/server/http/MindooDBServerCore.d.ts +313 -0
- package/dist/server/http/MindooDBServerCore.d.ts.map +1 -0
- package/dist/server/http/MindooDBServerCore.js +1410 -0
- package/dist/server/http/MindooDBServerCore.js.map +1 -0
- package/dist/server/http/types.d.ts +89 -0
- package/dist/server/http/types.d.ts.map +1 -0
- package/dist/server/http/types.js +29 -0
- package/dist/server/http/types.js.map +1 -0
- package/dist/server/index.d.ts +32 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +76 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/peer/ClusterAuditLog.d.ts +71 -0
- package/dist/server/peer/ClusterAuditLog.d.ts.map +1 -0
- package/dist/server/peer/ClusterAuditLog.js +81 -0
- package/dist/server/peer/ClusterAuditLog.js.map +1 -0
- package/dist/server/peer/ClusterJobRunner.d.ts +43 -0
- package/dist/server/peer/ClusterJobRunner.d.ts.map +1 -0
- package/dist/server/peer/ClusterJobRunner.js +119 -0
- package/dist/server/peer/ClusterJobRunner.js.map +1 -0
- package/dist/server/peer/ClusterManager.d.ts +149 -0
- package/dist/server/peer/ClusterManager.d.ts.map +1 -0
- package/dist/server/peer/ClusterManager.js +292 -0
- package/dist/server/peer/ClusterManager.js.map +1 -0
- package/dist/server/peer/PeerAuthService.d.ts +69 -0
- package/dist/server/peer/PeerAuthService.d.ts.map +1 -0
- package/dist/server/peer/PeerAuthService.js +207 -0
- package/dist/server/peer/PeerAuthService.js.map +1 -0
- package/dist/server/peer/PeerReplicator.d.ts +303 -0
- package/dist/server/peer/PeerReplicator.d.ts.map +1 -0
- package/dist/server/peer/PeerReplicator.js +1072 -0
- package/dist/server/peer/PeerReplicator.js.map +1 -0
- package/dist/server/peer/clusterHttp.d.ts +113 -0
- package/dist/server/peer/clusterHttp.d.ts.map +1 -0
- package/dist/server/peer/clusterHttp.js +290 -0
- package/dist/server/peer/clusterHttp.js.map +1 -0
- package/dist/server/peer/types.d.ts +244 -0
- package/dist/server/peer/types.d.ts.map +1 -0
- package/dist/server/peer/types.js +189 -0
- package/dist/server/peer/types.js.map +1 -0
- package/dist/server/rateLimits.d.ts +119 -0
- package/dist/server/rateLimits.d.ts.map +1 -0
- package/dist/server/rateLimits.js +159 -0
- package/dist/server/rateLimits.js.map +1 -0
- package/dist/server/serverConfig.d.ts +37 -0
- package/dist/server/serverConfig.d.ts.map +1 -0
- package/dist/server/serverConfig.js +251 -0
- package/dist/server/serverConfig.js.map +1 -0
- package/dist/server/timestampProxy.d.ts +129 -0
- package/dist/server/timestampProxy.d.ts.map +1 -0
- package/dist/server/timestampProxy.js +361 -0
- package/dist/server/timestampProxy.js.map +1 -0
- package/dist/server/types.d.ts +267 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +56 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/validation.d.ts +43 -0
- package/dist/server/validation.d.ts.map +1 -0
- package/dist/server/validation.js +104 -0
- package/dist/server/validation.js.map +1 -0
- package/package.json +9 -1
|
@@ -10,6 +10,9 @@ const EntrySignature_1 = require("../../core/crypto/EntrySignature");
|
|
|
10
10
|
const idGeneration_1 = require("../../core/utils/idGeneration");
|
|
11
11
|
const WitnessReceipt_1 = require("../../core/crypto/WitnessReceipt");
|
|
12
12
|
const logging_1 = require("../../core/logging");
|
|
13
|
+
const types_3 = require("../../core/accesscontrol/types");
|
|
14
|
+
const sealedTypes_1 = require("../../core/userkeys/sealedTypes");
|
|
15
|
+
const types_4 = require("../../core/types");
|
|
13
16
|
/**
|
|
14
17
|
* Server-side network handler for ContentAddressedStore operations.
|
|
15
18
|
*
|
|
@@ -22,6 +25,10 @@ const logging_1 = require("../../core/logging");
|
|
|
22
25
|
* This is not a ContentAddressedStore implementation itself, but rather
|
|
23
26
|
* a service that wraps a local ContentAddressedStore and handles network requests.
|
|
24
27
|
*/
|
|
28
|
+
/** Sliding window of the internal-mail rate limit. */
|
|
29
|
+
const INTERNAL_MAIL_WINDOW_MS = 60 * 60 * 1000;
|
|
30
|
+
/** How often the internal-mail counters are pruned at most. */
|
|
31
|
+
const INTERNAL_MAIL_PRUNE_INTERVAL_MS = 60 * 1000;
|
|
25
32
|
class ServerNetworkContentAddressedStore {
|
|
26
33
|
/**
|
|
27
34
|
* Create a new ServerNetworkContentAddressedStore.
|
|
@@ -33,6 +40,14 @@ class ServerNetworkContentAddressedStore {
|
|
|
33
40
|
* @param logger Optional logger instance
|
|
34
41
|
*/
|
|
35
42
|
constructor(localStore, directory, authService, cryptoAdapter, logger, accessControl) {
|
|
43
|
+
/** docId → creator of its `doc_create` (never changes once known). */
|
|
44
|
+
this.docCreatorByDocId = new Map();
|
|
45
|
+
/** Internal mail limits (§6.11): bytes per deposited document, accept times per sender. */
|
|
46
|
+
/** Bytes per internal mail doc and when it was last seen (pruned after an hour). */
|
|
47
|
+
this.internalMailBytesByDocId = new Map();
|
|
48
|
+
/** doc_create times per sender within the last hour (pruned, empty senders dropped). */
|
|
49
|
+
this.internalMailTimesBySender = new Map();
|
|
50
|
+
this.internalMailPrunedAt = 0;
|
|
36
51
|
/** Cached `username_hash` per userdirectory docId, filled as creates are seen. */
|
|
37
52
|
this.userdirectoryHashByDocId = new Map();
|
|
38
53
|
/** Cached create signing key per personal userdirectory docId (its owner). */
|
|
@@ -51,11 +66,15 @@ class ServerNetworkContentAddressedStore {
|
|
|
51
66
|
this.tier1Evaluator = accessControl?.tier1Evaluator;
|
|
52
67
|
this.wipeGrantDocIdResolver = accessControl?.wipeGrantDocIdResolver;
|
|
53
68
|
this.dbAccessEvaluator = accessControl?.dbAccessEvaluator;
|
|
69
|
+
this.dbAccessModeEvaluator = accessControl?.dbAccessModeEvaluator;
|
|
54
70
|
this.revokedKeyResolver = accessControl?.revokedKeyResolver;
|
|
55
71
|
this.purgedDocResolver = accessControl?.purgedDocResolver;
|
|
56
72
|
this.builtinWriteContext = accessControl?.builtinWriteContext;
|
|
57
73
|
this.peerTokenResolver = accessControl?.peerTokenResolver;
|
|
58
74
|
this.trustedWitnessResolver = accessControl?.trustedWitnessResolver;
|
|
75
|
+
this.readScopeResolver = accessControl?.readScopeResolver;
|
|
76
|
+
this.docCreatorResolver = accessControl?.docCreatorResolver;
|
|
77
|
+
this.purgeRequestValidator = accessControl?.purgeRequestValidator;
|
|
59
78
|
}
|
|
60
79
|
/**
|
|
61
80
|
* Resolve the reader's RSA encryption public key for transport encryption.
|
|
@@ -159,6 +178,97 @@ class ServerNetworkContentAddressedStore {
|
|
|
159
178
|
}
|
|
160
179
|
return kept;
|
|
161
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* The read scope of the request's principal (§6.12), or `null` when it reads
|
|
183
|
+
* everything. Peers mirror the store completely; the directory stores are
|
|
184
|
+
* never scoped.
|
|
185
|
+
*/
|
|
186
|
+
async resolveReadScope(payload) {
|
|
187
|
+
if (payload.peer)
|
|
188
|
+
return null;
|
|
189
|
+
if (!this.readScopeResolver)
|
|
190
|
+
return null;
|
|
191
|
+
const dbId = this.localStore.getId();
|
|
192
|
+
if (dbId === ServerNetworkContentAddressedStore.DIRECTORY_DB_ID || dbId === types_1.USER_DIRECTORY_DB_ID) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return this.readScopeResolver(this.readPrincipal(payload));
|
|
196
|
+
}
|
|
197
|
+
/** The creator of `docId`'s `doc_create`, cached once known. */
|
|
198
|
+
async docCreator(docId) {
|
|
199
|
+
const cached = this.docCreatorByDocId.get(docId);
|
|
200
|
+
if (cached)
|
|
201
|
+
return cached;
|
|
202
|
+
let found = null;
|
|
203
|
+
if (this.docCreatorResolver) {
|
|
204
|
+
found = await this.docCreatorResolver(docId);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const metas = await this.localStore.findNewEntriesForDoc([], docId);
|
|
208
|
+
const create = metas.find((m) => m.entryType === "doc_create");
|
|
209
|
+
if (create) {
|
|
210
|
+
found = { signingKey: create.createdByPublicKey, trustedTime: create.receivedAt ?? create.createdAt };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (found)
|
|
214
|
+
this.docCreatorByDocId.set(docId, found);
|
|
215
|
+
return found;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* A per-request decision cache for "may the principal see this document".
|
|
219
|
+
* Fail-closed: a document whose `doc_create` is unknown is hidden.
|
|
220
|
+
*/
|
|
221
|
+
scopeChecker(scope) {
|
|
222
|
+
const decided = new Map();
|
|
223
|
+
return (docId) => {
|
|
224
|
+
let decision = decided.get(docId);
|
|
225
|
+
if (!decision) {
|
|
226
|
+
decision = (async () => {
|
|
227
|
+
const creator = await this.docCreator(docId);
|
|
228
|
+
return creator ? scope.ownsDocument(creator.signingKey, creator.trustedTime) : false;
|
|
229
|
+
})();
|
|
230
|
+
decided.set(docId, decision);
|
|
231
|
+
}
|
|
232
|
+
return decision;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** Drop metadata of documents outside the principal's read scope (§6.12). */
|
|
236
|
+
async filterMetasByReadScope(metas, scope) {
|
|
237
|
+
if (!scope || metas.length === 0)
|
|
238
|
+
return metas;
|
|
239
|
+
const visible = this.scopeChecker(scope);
|
|
240
|
+
const kept = [];
|
|
241
|
+
for (const meta of metas) {
|
|
242
|
+
if (await visible(meta.docId))
|
|
243
|
+
kept.push(meta);
|
|
244
|
+
}
|
|
245
|
+
return kept;
|
|
246
|
+
}
|
|
247
|
+
/** Drop ids of entries outside the principal's read scope (§6.12). */
|
|
248
|
+
async filterIdsByReadScope(ids, scope) {
|
|
249
|
+
if (!scope || ids.length === 0)
|
|
250
|
+
return ids;
|
|
251
|
+
const visible = this.scopeChecker(scope);
|
|
252
|
+
const kept = [];
|
|
253
|
+
for (const id of ids) {
|
|
254
|
+
const meta = await this.localStore.getEntryMetadata(id);
|
|
255
|
+
if (meta && (await visible(meta.docId)))
|
|
256
|
+
kept.push(id);
|
|
257
|
+
}
|
|
258
|
+
return kept;
|
|
259
|
+
}
|
|
260
|
+
/** Both per-user read filters (§13 revoked keys, §6.12 read scope) on metadata. */
|
|
261
|
+
async filterMetasForReader(metas, payload) {
|
|
262
|
+
const revoked = await this.resolveRevokedKeyIds(payload);
|
|
263
|
+
const scope = await this.resolveReadScope(payload);
|
|
264
|
+
return this.filterMetasByReadScope(this.filterMetasByRevokedKeys(metas, revoked), scope);
|
|
265
|
+
}
|
|
266
|
+
/** Both per-user read filters (§13 revoked keys, §6.12 read scope) on ids. */
|
|
267
|
+
async filterIdsForReader(ids, payload) {
|
|
268
|
+
const revoked = await this.resolveRevokedKeyIds(payload);
|
|
269
|
+
const scope = await this.resolveReadScope(payload);
|
|
270
|
+
return this.filterIdsByReadScope(await this.filterIdsByRevokedKeys(ids, revoked), scope);
|
|
271
|
+
}
|
|
162
272
|
/**
|
|
163
273
|
* Get the ID of the underlying store.
|
|
164
274
|
*/
|
|
@@ -208,8 +318,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
208
318
|
this.logger.debug(`Found ${newEntries.length} new entries`);
|
|
209
319
|
const allowed = await this.wipeAllowedIds(tokenPayload);
|
|
210
320
|
const scoped = allowed ? newEntries.filter((e) => allowed.has(e.id)) : newEntries;
|
|
211
|
-
|
|
212
|
-
return this.filterMetasByRevokedKeys(scoped, revoked);
|
|
321
|
+
return this.filterMetasForReader(scoped, tokenPayload);
|
|
213
322
|
}
|
|
214
323
|
/**
|
|
215
324
|
* Handle a findNewEntriesForDoc request from a client.
|
|
@@ -230,8 +339,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
230
339
|
this.logger.debug(`Found ${newEntries.length} new entries for doc ${docId}`);
|
|
231
340
|
const allowed = await this.wipeAllowedIds(tokenPayload);
|
|
232
341
|
const scoped = allowed ? newEntries.filter((e) => allowed.has(e.id)) : newEntries;
|
|
233
|
-
|
|
234
|
-
return this.filterMetasByRevokedKeys(scoped, revoked);
|
|
342
|
+
return this.filterMetasForReader(scoped, tokenPayload);
|
|
235
343
|
}
|
|
236
344
|
/**
|
|
237
345
|
* Handle a findEntries request from a client.
|
|
@@ -252,8 +360,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
252
360
|
this.logger.debug(`Found ${entries.length} entries`);
|
|
253
361
|
const allowed = await this.wipeAllowedIds(tokenPayload);
|
|
254
362
|
const scoped = allowed ? entries.filter((e) => allowed.has(e.id)) : entries;
|
|
255
|
-
|
|
256
|
-
return this.filterMetasByRevokedKeys(scoped, revoked);
|
|
363
|
+
return this.filterMetasForReader(scoped, tokenPayload);
|
|
257
364
|
}
|
|
258
365
|
/**
|
|
259
366
|
* Handle a cursor-based scanEntriesSince request from a client.
|
|
@@ -286,10 +393,11 @@ class ServerNetworkContentAddressedStore {
|
|
|
286
393
|
// Per-user revoked-key blacklist (§13): silently omit revoked entries from
|
|
287
394
|
// the page. Filtering after paging only shrinks a page; the store's own
|
|
288
395
|
// cursor still advances so the client keeps making progress.
|
|
289
|
-
|
|
396
|
+
// The read scope (§6.12) filters the same way: a page may come back empty
|
|
397
|
+
// while `nextCursor`/`hasMore` still move on over hidden entries.
|
|
290
398
|
if (this.localStore.scanEntriesSince) {
|
|
291
399
|
const result = await this.localStore.scanEntriesSince(cursor, effectiveLimit, filters);
|
|
292
|
-
return { ...result, entries: this.
|
|
400
|
+
return { ...result, entries: await this.filterMetasForReader(result.entries, tokenPayload) };
|
|
293
401
|
}
|
|
294
402
|
// Fallback: emulate cursor scan using existing metadata query.
|
|
295
403
|
const all = filters?.docId
|
|
@@ -326,7 +434,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
326
434
|
const page = sorted.slice(startIndex, startIndex + max);
|
|
327
435
|
const last = page.length > 0 ? page[page.length - 1] : null;
|
|
328
436
|
return {
|
|
329
|
-
entries: this.
|
|
437
|
+
entries: await this.filterMetasForReader(page, tokenPayload),
|
|
330
438
|
nextCursor: last ? { receiptOrder: last.receiptOrder ?? 0, id: last.id } : cursor,
|
|
331
439
|
hasMore: startIndex + page.length < sorted.length,
|
|
332
440
|
};
|
|
@@ -348,7 +456,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
348
456
|
* Handle sync capability negotiation.
|
|
349
457
|
*/
|
|
350
458
|
async handleGetCapabilities(token) {
|
|
351
|
-
const tokenPayload = await this.validateToken(token);
|
|
459
|
+
const tokenPayload = await this.validateToken(token, true);
|
|
352
460
|
this.logger.debug(`Token validated for user: ${tokenPayload.sub}`);
|
|
353
461
|
return {
|
|
354
462
|
protocolVersion: "sync-v5",
|
|
@@ -380,11 +488,17 @@ class ServerNetworkContentAddressedStore {
|
|
|
380
488
|
* Cheap: token validation + an in-memory read on the local store.
|
|
381
489
|
*/
|
|
382
490
|
async handleGetStoreHead(token) {
|
|
383
|
-
await this.validateToken(token);
|
|
491
|
+
const tokenPayload = await this.validateToken(token);
|
|
384
492
|
if (!this.localStore.getStoreHead) {
|
|
385
493
|
throw new types_2.NetworkError(types_2.NetworkErrorType.SERVER_ERROR, "Local store does not support getStoreHead");
|
|
386
494
|
}
|
|
387
|
-
|
|
495
|
+
const head = await this.localStore.getStoreHead();
|
|
496
|
+
// A scoped reader (§6.12) gets its own epoch lineage: when the scope
|
|
497
|
+
// changes (mode switched, reader rule added or removed) the epoch differs
|
|
498
|
+
// and the client re-scans from the start instead of trusting a cursor that
|
|
499
|
+
// skipped entries it could not see before.
|
|
500
|
+
const scope = await this.resolveReadScope(tokenPayload);
|
|
501
|
+
return scope ? { ...head, epoch: `${head.epoch}:scope:${scope.scopeId}` } : head;
|
|
388
502
|
}
|
|
389
503
|
/**
|
|
390
504
|
* Handle a getCompactionStatus request from a client.
|
|
@@ -487,8 +601,8 @@ class ServerNetworkContentAddressedStore {
|
|
|
487
601
|
// path. Even if a client already knows a revoked entry's id, the server
|
|
488
602
|
// never hands back its bytes. A StoreEntry carries its own decryptionKeyId,
|
|
489
603
|
// so we filter without an extra metadata lookup.
|
|
490
|
-
|
|
491
|
-
const servableEntries = this.
|
|
604
|
+
// Read scope (§6.12): fail-closed as well, by the entry's document.
|
|
605
|
+
const servableEntries = await this.filterMetasForReader(entries, tokenPayload);
|
|
492
606
|
return { servableEntries, encryptionPublicKey, username };
|
|
493
607
|
}
|
|
494
608
|
async handleGetEntryMetadata(token, id) {
|
|
@@ -508,6 +622,11 @@ class ServerNetworkContentAddressedStore {
|
|
|
508
622
|
if (revoked && meta.decryptionKeyId && revoked.has(meta.decryptionKeyId)) {
|
|
509
623
|
return null;
|
|
510
624
|
}
|
|
625
|
+
// Read scope (§6.12): nothing about other people's documents.
|
|
626
|
+
const scope = await this.resolveReadScope(tokenPayload);
|
|
627
|
+
if (scope && (await this.filterMetasByReadScope([meta], scope)).length === 0) {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
511
630
|
return meta;
|
|
512
631
|
}
|
|
513
632
|
/**
|
|
@@ -531,7 +650,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
531
650
|
async handlePutEntries(token, entries) {
|
|
532
651
|
this.logger.debug(`Handling putEntries request for ${entries.length} entries`);
|
|
533
652
|
// Validate token
|
|
534
|
-
const tokenPayload = await this.validateToken(token);
|
|
653
|
+
const tokenPayload = await this.validateToken(token, true);
|
|
535
654
|
this.logger.debug(`Token validated for user: ${tokenPayload.sub}`);
|
|
536
655
|
// Server-to-server push: enables witness-receipt preservation below.
|
|
537
656
|
const peerIngest = tokenPayload.peer !== undefined;
|
|
@@ -552,6 +671,10 @@ class ServerNetworkContentAddressedStore {
|
|
|
552
671
|
const purgedDocIds = this.purgedDocResolver
|
|
553
672
|
? await this.purgedDocResolver()
|
|
554
673
|
: null;
|
|
674
|
+
// Author-scoped read (§6.12): a scoped principal may only write its own
|
|
675
|
+
// documents (create new ones, change its own), never into other people's.
|
|
676
|
+
const readScope = await this.resolveReadScope(tokenPayload);
|
|
677
|
+
const ownsForWrite = readScope ? this.scopeChecker(readScope) : null;
|
|
555
678
|
// A single acceptance time for this batch so receipts are consistent and a
|
|
556
679
|
// batch cannot interleave with the witness's own monotonic clock (§5.3).
|
|
557
680
|
const receivedAt = Date.now();
|
|
@@ -651,6 +774,27 @@ class ServerNetworkContentAddressedStore {
|
|
|
651
774
|
this.rejectEntry(rejected, entry.id, `Entry ${entry.id} has an invalid author signature`);
|
|
652
775
|
continue;
|
|
653
776
|
}
|
|
777
|
+
// Deposit-only principal (docs/accesscontrol.md §6.10): the deposit check
|
|
778
|
+
// replaces Tier 1 for its entries, because the doc_deposit rule is what
|
|
779
|
+
// authorizes them. Anything that is not a sealed create (or a chunk of
|
|
780
|
+
// one) is refused.
|
|
781
|
+
if (tokenPayload.deposit) {
|
|
782
|
+
const denied = (await this.evaluateDepositEntry(entry, tokenPayload, tokenPayload.deposit.maxRecipients, tokenPayload.deposit.docIdPrefixes)) ?? this.evaluateInternalMailLimits(entry, tokenPayload.deposit.internalMail);
|
|
783
|
+
if (denied) {
|
|
784
|
+
this.rejectEntry(rejected, entry.id, denied.reason, denied.rejectionClass);
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
// Purge requests in personal databases (§6.11): only by someone who may
|
|
789
|
+
// read the database, only for documents that are already deleted.
|
|
790
|
+
// Independent of the ACL master switch, like the builtin invariants.
|
|
791
|
+
if (this.purgeRequestValidator && (0, builtinDbInvariants_1.isPurgeRequestDocId)(entry.docId)) {
|
|
792
|
+
const denied = await this.purgeRequestValidator(entry, entries);
|
|
793
|
+
if (denied) {
|
|
794
|
+
this.rejectEntry(rejected, entry.id, `Entry ${entry.id}: ${denied}`, "policy");
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
654
798
|
// Builtin `userdirectory` write invariant (docs/userkeys.md §6.5). Runs
|
|
655
799
|
// before Tier 1 so it holds even when the ACL master switch is on.
|
|
656
800
|
const dbId = this.witnessDbid ?? this.localStore.getId();
|
|
@@ -664,13 +808,22 @@ class ServerNetworkContentAddressedStore {
|
|
|
664
808
|
// Rule-based Tier 1 enforcement (docs/accesscontrol.md §7). The server can
|
|
665
809
|
// only decide the identity tier; content-tier (Tier 2) gates are deferred
|
|
666
810
|
// to clients and treated as allowed here.
|
|
667
|
-
if (this.tier1Evaluator) {
|
|
811
|
+
if (this.tier1Evaluator && !tokenPayload.deposit) {
|
|
668
812
|
const decision = await this.tier1Evaluator(entry, this.witnessDbid ?? this.localStore.getId());
|
|
669
813
|
if (!decision.allowed) {
|
|
670
814
|
this.rejectEntry(rejected, entry.id, `Entry ${entry.id} denied by Tier 1 policy: ${decision.reason}`, "policy");
|
|
671
815
|
continue;
|
|
672
816
|
}
|
|
673
817
|
}
|
|
818
|
+
if (readScope && ownsForWrite) {
|
|
819
|
+
const ownsIt = entry.entryType === "doc_create"
|
|
820
|
+
? await readScope.ownsDocument(entry.createdByPublicKey, receivedAt)
|
|
821
|
+
: await this.ownsDocumentInBatch(entry, entries, readScope, ownsForWrite);
|
|
822
|
+
if (!ownsIt) {
|
|
823
|
+
this.rejectEntry(rejected, entry.id, `Entry ${entry.id} writes into a document outside the principal's read scope`, "policy");
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
674
827
|
// Stamp a witness receipt onto the accepted entry (§5.3). Self-authored
|
|
675
828
|
// entries (the witness pushing its own data) are not self-witnessed.
|
|
676
829
|
//
|
|
@@ -721,10 +874,125 @@ class ServerNetworkContentAddressedStore {
|
|
|
721
874
|
(rejected.length > 0 ? ` (${rejected.length} rejected)` : ""));
|
|
722
875
|
return { receipts: stampedMetadata, rejected };
|
|
723
876
|
}
|
|
877
|
+
/**
|
|
878
|
+
* What a deposit-only principal may push (docs/accesscontrol.md §6.10):
|
|
879
|
+
*
|
|
880
|
+
* - `doc_create` of a sealed document (`decryptionKeyId` is
|
|
881
|
+
* `$sealed:<docId>`) carrying 1..`maxRecipients` recipient wraps, signed
|
|
882
|
+
* by the depositing device itself, for a document id the store does not
|
|
883
|
+
* hold yet. An existing id is refused as `"exists"`, which a depositor
|
|
884
|
+
* reads as "already delivered".
|
|
885
|
+
* - `attachment_chunk` entries of such a document, signed by the depositor.
|
|
886
|
+
*
|
|
887
|
+
* Changes, deletes, undeletes and snapshots are always refused: a depositor
|
|
888
|
+
* cannot read the document, so it has no business editing it.
|
|
889
|
+
*/
|
|
890
|
+
async evaluateDepositEntry(entry, tokenPayload, maxRecipients, docIdPrefixes) {
|
|
891
|
+
const policy = (reason) => ({ reason: `Entry ${entry.id}: ${reason}`, rejectionClass: "policy" });
|
|
892
|
+
// Allowed document ids (§6.10 `docIdPrefixes`); applies to chunks too, which carry the docId.
|
|
893
|
+
if (entry.docId && !(0, types_3.docIdMatchesPrefixes)(entry.docId, docIdPrefixes)) {
|
|
894
|
+
return policy(`deposited document ids must start with ${docIdPrefixes.map((p) => `"${p}"`).join(" or ")}`);
|
|
895
|
+
}
|
|
896
|
+
if (!entry.docId || entry.decryptionKeyId !== (0, sealedTypes_1.sealedKeyId)(entry.docId)) {
|
|
897
|
+
return policy("deposit-only principals may only push documents sealed with their own key ($sealed:<docId>)");
|
|
898
|
+
}
|
|
899
|
+
if (tokenPayload.deviceSigningKey && entry.createdByPublicKey !== tokenPayload.deviceSigningKey) {
|
|
900
|
+
return policy("deposit-only principals may only push entries they signed themselves");
|
|
901
|
+
}
|
|
902
|
+
if (entry.entryType === "attachment_chunk") {
|
|
903
|
+
return this.localStore.getStoreKind() === types_4.StoreKind.attachments
|
|
904
|
+
? null
|
|
905
|
+
: policy("attachment chunks belong in the attachment store");
|
|
906
|
+
}
|
|
907
|
+
if (entry.entryType !== "doc_create") {
|
|
908
|
+
return policy(`deposit-only principals may only create documents, not push ${entry.entryType}`);
|
|
909
|
+
}
|
|
910
|
+
const wraps = entry.recipients?.wraps?.length ?? 0;
|
|
911
|
+
if (wraps < 1 || wraps > maxRecipients) {
|
|
912
|
+
return policy(`a deposited document needs 1 to ${maxRecipients} recipient wraps (has ${wraps})`);
|
|
913
|
+
}
|
|
914
|
+
const existing = await this.localStore.findNewEntriesForDoc([], entry.docId);
|
|
915
|
+
if (existing.length > 0) {
|
|
916
|
+
return { reason: `Entry ${entry.id}: document ${entry.docId} already exists`, rejectionClass: "exists" };
|
|
917
|
+
}
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Size and rate limits of internal mail (§6.11): per sender (signing key)
|
|
922
|
+
* and mailbox (this store), a document plus its attachment chunks may not
|
|
923
|
+
* exceed `maxBytesPerMail` (each store counts its own entries), and at most
|
|
924
|
+
* `maxMailsPerHour` documents are accepted per sender in a sliding hour.
|
|
925
|
+
* Counters are in memory: a server restart resets them.
|
|
926
|
+
*/
|
|
927
|
+
evaluateInternalMailLimits(entry, limits) {
|
|
928
|
+
if (!limits)
|
|
929
|
+
return null;
|
|
930
|
+
const now = Date.now();
|
|
931
|
+
this.pruneInternalMailCounters(now);
|
|
932
|
+
const policy = (reason) => ({ reason: `Entry ${entry.id}: ${reason}`, rejectionClass: "policy" });
|
|
933
|
+
const bytes = (this.internalMailBytesByDocId.get(entry.docId)?.bytes ?? 0) + entry.encryptedData.byteLength;
|
|
934
|
+
if (bytes > limits.maxBytesPerMail) {
|
|
935
|
+
return policy(`internal mail is limited to ${limits.maxBytesPerMail} bytes per mail`);
|
|
936
|
+
}
|
|
937
|
+
if (entry.entryType === "doc_create") {
|
|
938
|
+
const recent = (this.internalMailTimesBySender.get(entry.createdByPublicKey) ?? []).filter((t) => now - t < INTERNAL_MAIL_WINDOW_MS);
|
|
939
|
+
if (recent.length >= limits.maxMailsPerHour) {
|
|
940
|
+
this.internalMailTimesBySender.set(entry.createdByPublicKey, recent);
|
|
941
|
+
return policy(`internal mail is limited to ${limits.maxMailsPerHour} mails per hour per sender and mailbox`);
|
|
942
|
+
}
|
|
943
|
+
recent.push(now);
|
|
944
|
+
this.internalMailTimesBySender.set(entry.createdByPublicKey, recent);
|
|
945
|
+
}
|
|
946
|
+
this.internalMailBytesByDocId.set(entry.docId, { bytes, at: now });
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Drops counters outside the one-hour window, at most once a minute: byte
|
|
951
|
+
* counts of mails not touched for an hour (a mail's document and attachment
|
|
952
|
+
* chunks arrive within minutes) and senders without a create in the last hour.
|
|
953
|
+
* Keeps memory bounded by the traffic of the last hour, for the server's lifetime.
|
|
954
|
+
*/
|
|
955
|
+
pruneInternalMailCounters(now) {
|
|
956
|
+
if (now - this.internalMailPrunedAt < INTERNAL_MAIL_PRUNE_INTERVAL_MS)
|
|
957
|
+
return;
|
|
958
|
+
this.internalMailPrunedAt = now;
|
|
959
|
+
for (const [docId, counter] of this.internalMailBytesByDocId) {
|
|
960
|
+
if (now - counter.at >= INTERNAL_MAIL_WINDOW_MS)
|
|
961
|
+
this.internalMailBytesByDocId.delete(docId);
|
|
962
|
+
}
|
|
963
|
+
for (const [sender, times] of this.internalMailTimesBySender) {
|
|
964
|
+
const recent = times.filter((t) => now - t < INTERNAL_MAIL_WINDOW_MS);
|
|
965
|
+
if (recent.length === 0)
|
|
966
|
+
this.internalMailTimesBySender.delete(sender);
|
|
967
|
+
else if (recent.length !== times.length)
|
|
968
|
+
this.internalMailTimesBySender.set(sender, recent);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/** Test hook: sizes of the internal-mail counters. */
|
|
972
|
+
internalMailCounterSizes() {
|
|
973
|
+
return { docs: this.internalMailBytesByDocId.size, senders: this.internalMailTimesBySender.size };
|
|
974
|
+
}
|
|
724
975
|
/**
|
|
725
976
|
* Enforce the hard-wired `userdirectory` write invariant. Returns a denial
|
|
726
977
|
* message, or `null` when the entry is allowed.
|
|
727
978
|
*/
|
|
979
|
+
/**
|
|
980
|
+
* For a non-create entry under a read scope: is its document the principal's?
|
|
981
|
+
* The `doc_create` may be in the same batch (not yet stored).
|
|
982
|
+
*/
|
|
983
|
+
async ownsDocumentInBatch(entry, batch, scope, owns) {
|
|
984
|
+
const create = batch.find((e) => e.docId === entry.docId && e.entryType === "doc_create");
|
|
985
|
+
if (create && !(await this.localStore.hasEntries([create.id])).length) {
|
|
986
|
+
return scope.ownsDocument(create.createdByPublicKey, Date.now());
|
|
987
|
+
}
|
|
988
|
+
if (await this.docCreator(entry.docId)) {
|
|
989
|
+
return owns(entry.docId);
|
|
990
|
+
}
|
|
991
|
+
// The document is not on the server yet: attachment chunks are pushed
|
|
992
|
+
// before the document that references them. The entry's own author decides
|
|
993
|
+
// (the principal's own write); reads stay hidden until the doc_create is in.
|
|
994
|
+
return scope.ownsDocument(entry.createdByPublicKey, Date.now());
|
|
995
|
+
}
|
|
728
996
|
async evaluateUserdirectoryInvariant(entry, trustedTime, batch = []) {
|
|
729
997
|
const op = (0, builtinDbInvariants_1.entryTypeToBuiltinOp)(entry.entryType);
|
|
730
998
|
if (!op || !this.builtinWriteContext) {
|
|
@@ -882,7 +1150,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
882
1150
|
async handleHasEntries(token, ids) {
|
|
883
1151
|
this.logger.debug(`Handling hasEntries request for ${ids.length} IDs`);
|
|
884
1152
|
// Validate token
|
|
885
|
-
const tokenPayload = await this.validateToken(token);
|
|
1153
|
+
const tokenPayload = await this.validateToken(token, true);
|
|
886
1154
|
this.logger.debug(`Token validated for user: ${tokenPayload.sub}`);
|
|
887
1155
|
// Check which IDs exist in local store
|
|
888
1156
|
const existingIds = await this.localStore.hasEntries(ids);
|
|
@@ -909,8 +1177,7 @@ class ServerNetworkContentAddressedStore {
|
|
|
909
1177
|
this.logger.debug(`Returning ${allIds.length} entry IDs`);
|
|
910
1178
|
const allowed = await this.wipeAllowedIds(tokenPayload);
|
|
911
1179
|
const scoped = allowed ? allIds.filter((id) => allowed.has(id)) : allIds;
|
|
912
|
-
|
|
913
|
-
return this.filterIdsByRevokedKeys(scoped, revoked);
|
|
1180
|
+
return this.filterIdsForReader(scoped, tokenPayload);
|
|
914
1181
|
}
|
|
915
1182
|
/**
|
|
916
1183
|
* Handle a resolveDependencies request from a client.
|
|
@@ -934,18 +1201,35 @@ class ServerNetworkContentAddressedStore {
|
|
|
934
1201
|
// Resolve dependencies in local store
|
|
935
1202
|
const resolvedIds = await this.localStore.resolveDependencies(startId, options);
|
|
936
1203
|
this.logger.debug(`Resolved ${resolvedIds.length} dependencies`);
|
|
937
|
-
|
|
938
|
-
return this.filterIdsByRevokedKeys(resolvedIds, revoked);
|
|
1204
|
+
return this.filterIdsForReader(resolvedIds, tokenPayload);
|
|
939
1205
|
}
|
|
940
1206
|
async handlePlanDocumentMaterialization(token, docId, options) {
|
|
941
1207
|
const tokenPayload = await this.validateToken(token);
|
|
942
1208
|
this.logger.debug(`Token validated for user: ${tokenPayload.sub}`);
|
|
1209
|
+
const scope = await this.resolveReadScope(tokenPayload);
|
|
1210
|
+
if (scope && !(await this.scopeChecker(scope)(docId))) {
|
|
1211
|
+
// Read scope (§6.12): a document outside it looks like an unknown one.
|
|
1212
|
+
return { docId, snapshotEntryId: null, entryIdsToApply: [] };
|
|
1213
|
+
}
|
|
943
1214
|
return this.localStore.planDocumentMaterialization(docId, options);
|
|
944
1215
|
}
|
|
945
1216
|
async handlePlanDocumentMaterializationBatch(token, docIds, options) {
|
|
946
1217
|
const tokenPayload = await this.validateToken(token);
|
|
947
1218
|
this.logger.debug(`Token validated for user: ${tokenPayload.sub}`);
|
|
948
|
-
|
|
1219
|
+
const scope = await this.resolveReadScope(tokenPayload);
|
|
1220
|
+
if (!scope) {
|
|
1221
|
+
return this.localStore.planDocumentMaterializationBatch(docIds, options);
|
|
1222
|
+
}
|
|
1223
|
+
const visible = this.scopeChecker(scope);
|
|
1224
|
+
const shown = [];
|
|
1225
|
+
for (const docId of docIds)
|
|
1226
|
+
if (await visible(docId))
|
|
1227
|
+
shown.push(docId);
|
|
1228
|
+
const plan = await this.localStore.planDocumentMaterializationBatch(shown, options);
|
|
1229
|
+
const byId = new Map(plan.plans.map((p) => [p.docId, p]));
|
|
1230
|
+
return {
|
|
1231
|
+
plans: docIds.map((docId) => byId.get(docId) ?? { docId, snapshotEntryId: null, entryIdsToApply: [] }),
|
|
1232
|
+
};
|
|
949
1233
|
}
|
|
950
1234
|
async handlePlanAttachmentReadByWalkingMetadata(token, lastChunkId, attachmentSize, options) {
|
|
951
1235
|
const tokenPayload = await this.validateToken(token);
|
|
@@ -953,6 +1237,10 @@ class ServerNetworkContentAddressedStore {
|
|
|
953
1237
|
if (!this.localStore.planAttachmentReadByWalkingMetadata) {
|
|
954
1238
|
throw new Error("Remote store does not support attachment read planning");
|
|
955
1239
|
}
|
|
1240
|
+
const scope = await this.resolveReadScope(tokenPayload);
|
|
1241
|
+
if (scope && (await this.filterIdsByReadScope([lastChunkId], scope)).length === 0) {
|
|
1242
|
+
throw new types_2.NetworkError(types_2.NetworkErrorType.ACCESS_DENIED, "Attachment is outside the principal's read scope");
|
|
1243
|
+
}
|
|
956
1244
|
return this.localStore.planAttachmentReadByWalkingMetadata(lastChunkId, attachmentSize, options);
|
|
957
1245
|
}
|
|
958
1246
|
/**
|
|
@@ -962,7 +1250,13 @@ class ServerNetworkContentAddressedStore {
|
|
|
962
1250
|
* @returns The token payload
|
|
963
1251
|
* @throws NetworkError if token is invalid
|
|
964
1252
|
*/
|
|
965
|
-
|
|
1253
|
+
/**
|
|
1254
|
+
* @param allowDeposit Whether a deposit-only principal (§6.10) may perform
|
|
1255
|
+
* this operation. Only pushing (`putEntries`), the existence check push
|
|
1256
|
+
* relies on (`hasEntries`) and the capability probe pass it; every read is
|
|
1257
|
+
* refused for such a principal.
|
|
1258
|
+
*/
|
|
1259
|
+
async validateToken(token, allowDeposit = false) {
|
|
966
1260
|
const payload = await this.authService.validateToken(token);
|
|
967
1261
|
if (!payload) {
|
|
968
1262
|
// Server-to-server replication: a trusted peer authenticates once at the
|
|
@@ -992,7 +1286,29 @@ class ServerNetworkContentAddressedStore {
|
|
|
992
1286
|
// Applied here so it covers every authenticated sync operation (reads and
|
|
993
1287
|
// writes) in one place. The directory store is never wired with an
|
|
994
1288
|
// evaluator, so it always syncs.
|
|
995
|
-
if (this.
|
|
1289
|
+
if (this.dbAccessModeEvaluator) {
|
|
1290
|
+
const dbid = this.witnessDbid ?? this.localStore.getId();
|
|
1291
|
+
const access = await this.dbAccessModeEvaluator({ signingKey: payload.deviceSigningKey }, dbid);
|
|
1292
|
+
if (access.mode === "none") {
|
|
1293
|
+
throw new types_2.NetworkError(types_2.NetworkErrorType.ACCESS_DENIED, access.reason
|
|
1294
|
+
? `Access denied to database "${dbid}": ${access.reason}`
|
|
1295
|
+
: `Database "${dbid}" is not in the tenant's allowed database list`);
|
|
1296
|
+
}
|
|
1297
|
+
if (access.mode === "deposit") {
|
|
1298
|
+
if (!allowDeposit) {
|
|
1299
|
+
throw new types_2.NetworkError(types_2.NetworkErrorType.ACCESS_DENIED, `Database "${dbid}" allows this user to deposit sealed documents only, not to read`);
|
|
1300
|
+
}
|
|
1301
|
+
return {
|
|
1302
|
+
...payload,
|
|
1303
|
+
deposit: {
|
|
1304
|
+
maxRecipients: access.maxRecipients ?? types_3.DEFAULT_DEPOSIT_MAX_RECIPIENTS,
|
|
1305
|
+
...(access.docIdPrefixes?.length ? { docIdPrefixes: access.docIdPrefixes } : {}),
|
|
1306
|
+
...(access.internalMail ? { internalMail: access.internalMail } : {}),
|
|
1307
|
+
},
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
else if (this.dbAccessEvaluator) {
|
|
996
1312
|
const dbid = this.witnessDbid ?? this.localStore.getId();
|
|
997
1313
|
const allowed = await this.dbAccessEvaluator({ signingKey: payload.deviceSigningKey }, dbid);
|
|
998
1314
|
if (!allowed) {
|