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
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
import { NodeCryptoAdapter } from "../crypto/NodeCryptoAdapter";
|
|
20
20
|
import { AuthenticationService } from "../../core/appendonlystores/network/AuthenticationService";
|
|
21
21
|
import { ServerNetworkContentAddressedStore } from "../../appendonlystores/network/ServerNetworkContentAddressedStore";
|
|
22
|
+
import type { ServerPeerTokenResolver, ServerTrustedWitnessResolver } from "../../appendonlystores/network/ServerNetworkContentAddressedStore";
|
|
22
23
|
import type { WitnessSigner } from "../../core/crypto/WitnessReceipt";
|
|
23
24
|
import type { TimestampProvider } from "../../core/accesscontrol/timestamp/TimestampProvider";
|
|
24
25
|
import type { MindooTenant, MindooTenantDirectory, ContentAddressedStore, DirectoryUserLookup } from "../../core/types";
|
|
25
26
|
import { StoreKind } from "../../core/types";
|
|
26
27
|
import type { PrivateUserId } from "../../core/userid";
|
|
27
28
|
import { StoreFactory } from "./StoreFactory";
|
|
28
|
-
import type { TenantConfig, TenantContext, RegisterTenantRequest, TrustedServer
|
|
29
|
+
import type { TenantConfig, TenantContext, RegisterTenantRequest, TrustedServer } from "./types";
|
|
29
30
|
interface LoadedTenant {
|
|
30
31
|
context: TenantContext;
|
|
31
32
|
storeFactory: StoreFactory;
|
|
@@ -55,6 +56,7 @@ export declare class SimpleMindooDirectory implements Pick<MindooTenantDirectory
|
|
|
55
56
|
encryptionPublicKey: string;
|
|
56
57
|
} | null>;
|
|
57
58
|
getUserBySigningPublicKey(publicKey: string): Promise<DirectoryUserLookup | null>;
|
|
59
|
+
private toLookup;
|
|
58
60
|
isUserRevoked(username: string): Promise<boolean>;
|
|
59
61
|
validatePublicSigningKey(publicKey: string, _opts?: {
|
|
60
62
|
forceRefresh?: boolean;
|
|
@@ -79,6 +81,10 @@ export declare class TenantManager {
|
|
|
79
81
|
private trustedServers;
|
|
80
82
|
/** Lazily-built, cached witness signer (server's Ed25519 signing identity). */
|
|
81
83
|
private witnessSigner;
|
|
84
|
+
/** Lazily-imported RSA private key for peer replication (see getServerEncryptionPrivateKey). */
|
|
85
|
+
private serverEncryptionPrivateKey;
|
|
86
|
+
/** Peer wiring handed down to every server store; see setPeerAccessControl. */
|
|
87
|
+
private peerAccessControl;
|
|
82
88
|
/** Per-tenant persistent registry of executed purges (docs/accesscontrol.md §13). */
|
|
83
89
|
private purgedDocRegistries;
|
|
84
90
|
/** In-flight purge executions, keyed by normalized tenant id (serializes runs). */
|
|
@@ -86,6 +92,44 @@ export declare class TenantManager {
|
|
|
86
92
|
constructor(dataDir: string, serverPassword?: string);
|
|
87
93
|
getServerIdentity(): PrivateUserId | null;
|
|
88
94
|
getServerPublicInfo(): TrustedServer | null;
|
|
95
|
+
getDataDir(): string;
|
|
96
|
+
/**
|
|
97
|
+
* Teach every server store how to recognise a trusted peer.
|
|
98
|
+
*
|
|
99
|
+
* `MindooDBServer` calls this once at startup. Two hooks, both required for
|
|
100
|
+
* peer replication to be correct:
|
|
101
|
+
*
|
|
102
|
+
* - `peerTokenResolver` lets a peer's cluster-level JWT through the tenant
|
|
103
|
+
* sync routes. A peer holds no per-tenant grant, so without it the mirror
|
|
104
|
+
* would need a second replication protocol; with it the peer reuses the
|
|
105
|
+
* client one and the store skips the per-user gates that do not apply to a
|
|
106
|
+
* full replica.
|
|
107
|
+
* - `trustedWitnessResolver` decides whose witness receipts may be preserved
|
|
108
|
+
* instead of re-stamped (convergence invariant 3). Without it a replicated
|
|
109
|
+
* entry would get a fresh `receivedAt` on every hop and "trusted time"
|
|
110
|
+
* would mean "time it reached the last server".
|
|
111
|
+
*
|
|
112
|
+
* Must be called before the first {@link getServerStore}, since stores are
|
|
113
|
+
* cached per tenant and database.
|
|
114
|
+
*/
|
|
115
|
+
setPeerAccessControl(options: {
|
|
116
|
+
peerTokenResolver?: ServerPeerTokenResolver;
|
|
117
|
+
trustedWitnessResolver?: ServerTrustedWitnessResolver;
|
|
118
|
+
}): void;
|
|
119
|
+
/**
|
|
120
|
+
* Database ids this server holds for a tenant, read from
|
|
121
|
+
* `{dataDir}/{tenantId}/stores`. Used by peer replication to mirror every
|
|
122
|
+
* database rather than a configured subset.
|
|
123
|
+
*/
|
|
124
|
+
listDatabases(tenantId: string): string[];
|
|
125
|
+
/**
|
|
126
|
+
* The server's own RSA private key, imported for decryption.
|
|
127
|
+
*
|
|
128
|
+
* Peer replication needs it because the remote wraps transported entries to
|
|
129
|
+
* this server's `encryptionPublicKey` (the one in its `trusted-servers.json`),
|
|
130
|
+
* exactly as it would wrap them for a client.
|
|
131
|
+
*/
|
|
132
|
+
getServerEncryptionPrivateKey(): Promise<CryptoKey | undefined>;
|
|
89
133
|
/**
|
|
90
134
|
* Build (and cache) the server's witness identity used to stamp receipts on
|
|
91
135
|
* accepted entries (docs/accesscontrol.md §5.3). Returns undefined when the
|
|
@@ -102,6 +146,15 @@ export declare class TenantManager {
|
|
|
102
146
|
getTimestampProvider(): Promise<TimestampProvider | undefined>;
|
|
103
147
|
listTrustedServers(): TrustedServer[];
|
|
104
148
|
addTrustedServer(server: TrustedServer): void;
|
|
149
|
+
/**
|
|
150
|
+
* Add or replace a trusted server by name.
|
|
151
|
+
*
|
|
152
|
+
* Peer CRUD needs an upsert: `addTrustedServer` throws on an existing name,
|
|
153
|
+
* which is right for `add-to-network` (a silent overwrite of a peer's keys
|
|
154
|
+
* would be a trust change disguised as a config edit) but wrong for the admin
|
|
155
|
+
* API, where changing a peer's url or role is the common case.
|
|
156
|
+
*/
|
|
157
|
+
saveTrustedServer(server: TrustedServer): void;
|
|
105
158
|
removeTrustedServer(name: string): boolean;
|
|
106
159
|
private persistTrustedServers;
|
|
107
160
|
private getServerKeyBagPath;
|
|
@@ -117,20 +170,38 @@ export declare class TenantManager {
|
|
|
117
170
|
private resolveIncomingPublicInfosKey;
|
|
118
171
|
private getStoredPublicInfosKeys;
|
|
119
172
|
private persistTenantPublicInfosKey;
|
|
173
|
+
/**
|
|
174
|
+
* All stored `$publicinfos` AES key versions for a tenant from `server.keybag`.
|
|
175
|
+
* Empty when the tenant was never published or the server keybag is locked.
|
|
176
|
+
*/
|
|
177
|
+
getPublicInfosKeysForTenant(tenantId: string): Promise<Uint8Array[]>;
|
|
120
178
|
listTenantPublicInfosFingerprints(tenantId: string): Promise<string[]>;
|
|
121
179
|
registerTenant(request: RegisterTenantRequest): Promise<RegisterTenantResult>;
|
|
122
180
|
getTenant(tenantId: string): Promise<LoadedTenant>;
|
|
123
181
|
tenantExists(tenantId: string): boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Read the tenant's registered admin signing key without fully loading the
|
|
184
|
+
* tenant. Returns `null` if the tenant is missing or the config is unreadable.
|
|
185
|
+
*/
|
|
186
|
+
getTenantAdminSigningPublicKey(tenantId: string): string | null;
|
|
187
|
+
/**
|
|
188
|
+
* True when `publicsignkey` is the registered tenant-admin signing key for
|
|
189
|
+
* `tenantId` (PEM whitespace-insensitive).
|
|
190
|
+
*/
|
|
191
|
+
isTenantAdminSigningKeyFor(tenantId: string, publicsignkey: string): boolean;
|
|
192
|
+
/**
|
|
193
|
+
* True when `publicsignkey` is the registered admin signing key of any
|
|
194
|
+
* tenant on this server. Used to issue a system JWT so that admin can
|
|
195
|
+
* DELETE their own tenant; other `/system/*` routes stay capability-gated.
|
|
196
|
+
*/
|
|
197
|
+
isRegisteredTenantAdminSigningKey(publicsignkey: string): boolean;
|
|
124
198
|
listTenants(): string[];
|
|
125
199
|
/**
|
|
126
200
|
* Update operator-owned fields of an existing tenant configuration.
|
|
127
201
|
* Only the fields present in `updates` are overwritten.
|
|
128
202
|
*/
|
|
129
|
-
updateTenantConfig(tenantId: string, updates: Partial<Pick<TenantConfig, "defaultStoreType"
|
|
203
|
+
updateTenantConfig(tenantId: string, updates: Partial<Pick<TenantConfig, "defaultStoreType">>): void;
|
|
130
204
|
removeTenant(tenantId: string): Promise<void>;
|
|
131
|
-
getTenantSyncServers(tenantId: string): NamedRemoteServerConfig[];
|
|
132
|
-
addTenantSyncServer(tenantId: string, server: NamedRemoteServerConfig): void;
|
|
133
|
-
removeTenantSyncServer(tenantId: string, serverName: string): boolean;
|
|
134
205
|
getServerStore(tenantId: string, dbId: string, storeKind: StoreKind): Promise<ServerNetworkContentAddressedStore>;
|
|
135
206
|
/**
|
|
136
207
|
* Build a Tier 1 evaluator closure for a server store, or undefined when the
|
|
@@ -140,6 +211,16 @@ export declare class TenantManager {
|
|
|
140
211
|
* store (metadata only, no decryption) and comparing it to the change author.
|
|
141
212
|
*/
|
|
142
213
|
private buildTier1Evaluator;
|
|
214
|
+
/**
|
|
215
|
+
* Builtin `userdirectory` write invariant: admin may create/delete, only the
|
|
216
|
+
* owning person may change. `username_hash` is read from the decrypted
|
|
217
|
+
* `doc_create` payload (`$publicinfos`). Personal documents are sealed to
|
|
218
|
+
* their owner and therefore undecryptable here, so they are owned by whoever
|
|
219
|
+
* signed their `doc_create` — `resolveCreatorSigningKey` answers that from
|
|
220
|
+
* the entry chain alone.
|
|
221
|
+
*/
|
|
222
|
+
private buildUserdirectoryWriteContext;
|
|
223
|
+
private findCreateEntry;
|
|
143
224
|
/**
|
|
144
225
|
* Build a database-open evaluator closure for a server store, or undefined
|
|
145
226
|
* when the directory cannot evaluate the database-open policy (e.g. a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TenantManager.d.ts","sourceRoot":"","sources":["../../../src/node/server/TenantManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2DAA2D,CAAC;AAClG,OAAO,EAAE,kCAAkC,EAAE,MAAM,mEAAmE,CAAC;
|
|
1
|
+
{"version":3,"file":"TenantManager.d.ts","sourceRoot":"","sources":["../../../src/node/server/TenantManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,9 +31,9 @@ const RSAEncryption_1 = require("../../core/crypto/RSAEncryption");
|
|
|
31
31
|
const privateKeyEncryption_1 = require("../../core/crypto/privateKeyEncryption");
|
|
32
32
|
const KeyBag_1 = require("../../core/keys/KeyBag");
|
|
33
33
|
const types_1 = require("../../core/types");
|
|
34
|
+
const builtinDbInvariants_1 = require("../../core/builtinDbInvariants");
|
|
35
|
+
const DirectoryStateNode_1 = require("../../core/accesscontrol/DirectoryStateNode");
|
|
34
36
|
const StoreFactory_1 = require("./StoreFactory");
|
|
35
|
-
const types_2 = require("./types");
|
|
36
|
-
const urlSafety_1 = require("../../core/utils/urlSafety");
|
|
37
37
|
// ---------------------------------------------------------------------------
|
|
38
38
|
// StoreFactoryAdapter
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
@@ -89,10 +89,21 @@ class SimpleMindooDirectory {
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
async getUserBySigningPublicKey(publicKey) {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
92
|
+
const exact = this.usersByKey.get(publicKey);
|
|
93
|
+
if (exact) {
|
|
94
|
+
return this.toLookup(exact);
|
|
95
|
+
}
|
|
96
|
+
const needle = publicKey.replace(/\s+/g, "");
|
|
97
|
+
if (!needle)
|
|
94
98
|
return null;
|
|
99
|
+
for (const [key, user] of this.usersByKey) {
|
|
100
|
+
if (key.replace(/\s+/g, "") === needle) {
|
|
101
|
+
return this.toLookup(user);
|
|
102
|
+
}
|
|
95
103
|
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
toLookup(user) {
|
|
96
107
|
if (typeof user.username === "string" &&
|
|
97
108
|
this.revokedUsers.has(user.username.toLowerCase())) {
|
|
98
109
|
return null;
|
|
@@ -176,7 +187,8 @@ class CompositeMindooDirectory {
|
|
|
176
187
|
return this.inner.isUserRevoked(username);
|
|
177
188
|
}
|
|
178
189
|
async validatePublicSigningKey(publicKey, opts) {
|
|
179
|
-
if (this.adminBootstrapIdentity &&
|
|
190
|
+
if (this.adminBootstrapIdentity &&
|
|
191
|
+
(0, DirectoryStateNode_1.signingKeysEqual)(this.adminBootstrapIdentity.signingPublicKey, publicKey)) {
|
|
180
192
|
return true;
|
|
181
193
|
}
|
|
182
194
|
const innerResult = await this.inner.validatePublicSigningKey(publicKey, opts);
|
|
@@ -208,7 +220,8 @@ class CompositeMindooDirectory {
|
|
|
208
220
|
// server, mirroring getUserPublicKeys. Without this, key-based identity
|
|
209
221
|
// resolution (and the validateToken active-key check below) would treat the
|
|
210
222
|
// admin's own key as unknown the moment the directory has no admin grant.
|
|
211
|
-
if (this.adminBootstrapIdentity &&
|
|
223
|
+
if (this.adminBootstrapIdentity &&
|
|
224
|
+
(0, DirectoryStateNode_1.signingKeysEqual)(this.adminBootstrapIdentity.signingPublicKey, publicKey)) {
|
|
212
225
|
return {
|
|
213
226
|
username: this.adminBootstrapIdentity.username,
|
|
214
227
|
signingPublicKey: this.adminBootstrapIdentity.signingPublicKey,
|
|
@@ -270,6 +283,19 @@ class CompositeMindooDirectory {
|
|
|
270
283
|
// Config-based directories carry no database-open policy -> unrestricted.
|
|
271
284
|
return true;
|
|
272
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Forwarded so server-side `userdirectory` writes can resolve the signer's
|
|
288
|
+
* grant. Without this, `evaluateUserdirectoryInvariant` sees a missing
|
|
289
|
+
* method, leaves `signerUsernameHash` null, and denies every non-admin
|
|
290
|
+
* change as "signer is not a granted device" — including the founder
|
|
291
|
+
* wrapping a second device of their own User-Key.
|
|
292
|
+
*/
|
|
293
|
+
async resolveUsernameHashForSigningKey(signingKey, trustedTime) {
|
|
294
|
+
if (typeof this.inner.resolveUsernameHashForSigningKey === "function") {
|
|
295
|
+
return this.inner.resolveUsernameHashForSigningKey(signingKey, trustedTime);
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
273
299
|
}
|
|
274
300
|
// ---------------------------------------------------------------------------
|
|
275
301
|
// TenantManager
|
|
@@ -289,6 +315,8 @@ class TenantManager {
|
|
|
289
315
|
this.loadedTenants = new Map();
|
|
290
316
|
this.serverIdentity = null;
|
|
291
317
|
this.trustedServers = [];
|
|
318
|
+
/** Peer wiring handed down to every server store; see setPeerAccessControl. */
|
|
319
|
+
this.peerAccessControl = {};
|
|
292
320
|
/** Per-tenant persistent registry of executed purges (docs/accesscontrol.md §13). */
|
|
293
321
|
this.purgedDocRegistries = new Map();
|
|
294
322
|
/** In-flight purge executions, keyed by normalized tenant id (serializes runs). */
|
|
@@ -331,6 +359,66 @@ class TenantManager {
|
|
|
331
359
|
encryptionPublicKey: this.serverIdentity.userEncryptionKeyPair.publicKey,
|
|
332
360
|
};
|
|
333
361
|
}
|
|
362
|
+
getDataDir() {
|
|
363
|
+
return this.dataDir;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Teach every server store how to recognise a trusted peer.
|
|
367
|
+
*
|
|
368
|
+
* `MindooDBServer` calls this once at startup. Two hooks, both required for
|
|
369
|
+
* peer replication to be correct:
|
|
370
|
+
*
|
|
371
|
+
* - `peerTokenResolver` lets a peer's cluster-level JWT through the tenant
|
|
372
|
+
* sync routes. A peer holds no per-tenant grant, so without it the mirror
|
|
373
|
+
* would need a second replication protocol; with it the peer reuses the
|
|
374
|
+
* client one and the store skips the per-user gates that do not apply to a
|
|
375
|
+
* full replica.
|
|
376
|
+
* - `trustedWitnessResolver` decides whose witness receipts may be preserved
|
|
377
|
+
* instead of re-stamped (convergence invariant 3). Without it a replicated
|
|
378
|
+
* entry would get a fresh `receivedAt` on every hop and "trusted time"
|
|
379
|
+
* would mean "time it reached the last server".
|
|
380
|
+
*
|
|
381
|
+
* Must be called before the first {@link getServerStore}, since stores are
|
|
382
|
+
* cached per tenant and database.
|
|
383
|
+
*/
|
|
384
|
+
setPeerAccessControl(options) {
|
|
385
|
+
this.peerAccessControl = options;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Database ids this server holds for a tenant, read from
|
|
389
|
+
* `{dataDir}/{tenantId}/stores`. Used by peer replication to mirror every
|
|
390
|
+
* database rather than a configured subset.
|
|
391
|
+
*/
|
|
392
|
+
listDatabases(tenantId) {
|
|
393
|
+
const storesDir = (0, path_1.join)(this.resolveTenantDir(tenantId.toLowerCase()), "stores");
|
|
394
|
+
if (!(0, fs_1.existsSync)(storesDir)) {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
return (0, fs_1.readdirSync)(storesDir).filter((entry) => (0, fs_1.statSync)((0, path_1.join)(storesDir, entry)).isDirectory());
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
console.warn(`[TenantManager] Could not list databases for ${tenantId}:`, error);
|
|
402
|
+
return [];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* The server's own RSA private key, imported for decryption.
|
|
407
|
+
*
|
|
408
|
+
* Peer replication needs it because the remote wraps transported entries to
|
|
409
|
+
* this server's `encryptionPublicKey` (the one in its `trusted-servers.json`),
|
|
410
|
+
* exactly as it would wrap them for a client.
|
|
411
|
+
*/
|
|
412
|
+
async getServerEncryptionPrivateKey() {
|
|
413
|
+
if (this.serverEncryptionPrivateKey)
|
|
414
|
+
return this.serverEncryptionPrivateKey;
|
|
415
|
+
if (!this.serverIdentity || !this.serverPassword)
|
|
416
|
+
return undefined;
|
|
417
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
418
|
+
const keyBuffer = await (0, privateKeyEncryption_1.decryptPrivateKey)(this.cryptoAdapter, this.serverIdentity.userEncryptionKeyPair.privateKey, this.serverPassword, "encryption");
|
|
419
|
+
this.serverEncryptionPrivateKey = await subtle.importKey("pkcs8", keyBuffer, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
|
|
420
|
+
return this.serverEncryptionPrivateKey;
|
|
421
|
+
}
|
|
334
422
|
/**
|
|
335
423
|
* Build (and cache) the server's witness identity used to stamp receipts on
|
|
336
424
|
* accepted entries (docs/accesscontrol.md §5.3). Returns undefined when the
|
|
@@ -379,6 +467,25 @@ class TenantManager {
|
|
|
379
467
|
this.persistTrustedServers();
|
|
380
468
|
console.log(`[TenantManager] Added trusted server: ${server.name}`);
|
|
381
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* Add or replace a trusted server by name.
|
|
472
|
+
*
|
|
473
|
+
* Peer CRUD needs an upsert: `addTrustedServer` throws on an existing name,
|
|
474
|
+
* which is right for `add-to-network` (a silent overwrite of a peer's keys
|
|
475
|
+
* would be a trust change disguised as a config edit) but wrong for the admin
|
|
476
|
+
* API, where changing a peer's url or role is the common case.
|
|
477
|
+
*/
|
|
478
|
+
saveTrustedServer(server) {
|
|
479
|
+
const idx = this.trustedServers.findIndex((s) => s.name.toLowerCase() === server.name.toLowerCase());
|
|
480
|
+
if (idx === -1) {
|
|
481
|
+
this.trustedServers.push(server);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
this.trustedServers[idx] = server;
|
|
485
|
+
}
|
|
486
|
+
this.persistTrustedServers();
|
|
487
|
+
console.log(`[TenantManager] Saved trusted server: ${server.name}`);
|
|
488
|
+
}
|
|
382
489
|
removeTrustedServer(name) {
|
|
383
490
|
const idx = this.trustedServers.findIndex((s) => s.name.toLowerCase() === name.toLowerCase());
|
|
384
491
|
if (idx === -1)
|
|
@@ -493,6 +600,13 @@ class TenantManager {
|
|
|
493
600
|
await this.saveServerKeyBag(keyBag);
|
|
494
601
|
}
|
|
495
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* All stored `$publicinfos` AES key versions for a tenant from `server.keybag`.
|
|
605
|
+
* Empty when the tenant was never published or the server keybag is locked.
|
|
606
|
+
*/
|
|
607
|
+
async getPublicInfosKeysForTenant(tenantId) {
|
|
608
|
+
return this.getStoredPublicInfosKeys(tenantId.toLowerCase());
|
|
609
|
+
}
|
|
496
610
|
async listTenantPublicInfosFingerprints(tenantId) {
|
|
497
611
|
const normalizedId = tenantId.toLowerCase();
|
|
498
612
|
const configPath = this.resolveTenantConfigPath(normalizedId);
|
|
@@ -564,6 +678,52 @@ class TenantManager {
|
|
|
564
678
|
const configPath = this.resolveTenantConfigPath(normalizedId);
|
|
565
679
|
return (0, fs_1.existsSync)(configPath);
|
|
566
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Read the tenant's registered admin signing key without fully loading the
|
|
683
|
+
* tenant. Returns `null` if the tenant is missing or the config is unreadable.
|
|
684
|
+
*/
|
|
685
|
+
getTenantAdminSigningPublicKey(tenantId) {
|
|
686
|
+
try {
|
|
687
|
+
const normalizedId = tenantId.toLowerCase();
|
|
688
|
+
const cached = this.loadedTenants.get(normalizedId);
|
|
689
|
+
if (cached?.context.config.adminSigningPublicKey) {
|
|
690
|
+
return cached.context.config.adminSigningPublicKey;
|
|
691
|
+
}
|
|
692
|
+
const configPath = this.resolveTenantConfigPath(normalizedId);
|
|
693
|
+
if (!(0, fs_1.existsSync)(configPath)) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
|
|
697
|
+
return typeof config.adminSigningPublicKey === "string" &&
|
|
698
|
+
config.adminSigningPublicKey.length > 0
|
|
699
|
+
? config.adminSigningPublicKey
|
|
700
|
+
: null;
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* True when `publicsignkey` is the registered tenant-admin signing key for
|
|
708
|
+
* `tenantId` (PEM whitespace-insensitive).
|
|
709
|
+
*/
|
|
710
|
+
isTenantAdminSigningKeyFor(tenantId, publicsignkey) {
|
|
711
|
+
const adminKey = this.getTenantAdminSigningPublicKey(tenantId);
|
|
712
|
+
return adminKey !== null && (0, DirectoryStateNode_1.signingKeysEqual)(adminKey, publicsignkey);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* True when `publicsignkey` is the registered admin signing key of any
|
|
716
|
+
* tenant on this server. Used to issue a system JWT so that admin can
|
|
717
|
+
* DELETE their own tenant; other `/system/*` routes stay capability-gated.
|
|
718
|
+
*/
|
|
719
|
+
isRegisteredTenantAdminSigningKey(publicsignkey) {
|
|
720
|
+
for (const tenantId of this.listTenants()) {
|
|
721
|
+
if (this.isTenantAdminSigningKeyFor(tenantId, publicsignkey)) {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
567
727
|
listTenants() {
|
|
568
728
|
if (!(0, fs_1.existsSync)(this.dataDir)) {
|
|
569
729
|
return [];
|
|
@@ -593,9 +753,6 @@ class TenantManager {
|
|
|
593
753
|
if (updates.defaultStoreType !== undefined) {
|
|
594
754
|
config.defaultStoreType = updates.defaultStoreType;
|
|
595
755
|
}
|
|
596
|
-
if (updates.remoteServers !== undefined) {
|
|
597
|
-
config.remoteServers = updates.remoteServers;
|
|
598
|
-
}
|
|
599
756
|
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
600
757
|
this.loadedTenants.delete(normalizedId);
|
|
601
758
|
console.log(`[TenantManager] Updated tenant config: ${normalizedId}`);
|
|
@@ -616,65 +773,6 @@ class TenantManager {
|
|
|
616
773
|
console.log(`[TenantManager] Removed tenant: ${normalizedId}`);
|
|
617
774
|
}
|
|
618
775
|
// =======================================================================
|
|
619
|
-
// Per-tenant sync server management
|
|
620
|
-
// =======================================================================
|
|
621
|
-
getTenantSyncServers(tenantId) {
|
|
622
|
-
const normalizedId = tenantId.toLowerCase();
|
|
623
|
-
const configPath = this.resolveTenantConfigPath(normalizedId);
|
|
624
|
-
if (!(0, fs_1.existsSync)(configPath)) {
|
|
625
|
-
throw new Error(`Tenant ${normalizedId} not found`);
|
|
626
|
-
}
|
|
627
|
-
const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
|
|
628
|
-
return (config.remoteServers || []);
|
|
629
|
-
}
|
|
630
|
-
addTenantSyncServer(tenantId, server) {
|
|
631
|
-
const normalizedId = tenantId.toLowerCase();
|
|
632
|
-
// SSRF guard (defense in depth alongside the HTTP route): a configured sync
|
|
633
|
-
// URL is fetched server-side, so reject plaintext/internal targets unless
|
|
634
|
-
// explicitly allowed for local development.
|
|
635
|
-
const allowInsecure = /^(1|true)$/i.test(process.env[types_2.ENV_VARS.ALLOW_INSECURE_SYNC_URLS] ?? "");
|
|
636
|
-
(0, urlSafety_1.assertSafeSyncUrl)(server.url, {
|
|
637
|
-
requireHttps: !allowInsecure,
|
|
638
|
-
allowPrivate: allowInsecure,
|
|
639
|
-
});
|
|
640
|
-
const configPath = this.resolveTenantConfigPath(normalizedId);
|
|
641
|
-
if (!(0, fs_1.existsSync)(configPath)) {
|
|
642
|
-
throw new Error(`Tenant ${normalizedId} not found`);
|
|
643
|
-
}
|
|
644
|
-
const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
|
|
645
|
-
const servers = (config.remoteServers || []);
|
|
646
|
-
const idx = servers.findIndex((s) => s.name?.toLowerCase() === server.name.toLowerCase());
|
|
647
|
-
if (idx >= 0) {
|
|
648
|
-
servers[idx] = server;
|
|
649
|
-
console.log(`[TenantManager] Updated sync server "${server.name}" for tenant ${normalizedId}`);
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
servers.push(server);
|
|
653
|
-
console.log(`[TenantManager] Added sync server "${server.name}" for tenant ${normalizedId}`);
|
|
654
|
-
}
|
|
655
|
-
config.remoteServers = servers;
|
|
656
|
-
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
657
|
-
this.loadedTenants.delete(normalizedId);
|
|
658
|
-
}
|
|
659
|
-
removeTenantSyncServer(tenantId, serverName) {
|
|
660
|
-
const normalizedId = tenantId.toLowerCase();
|
|
661
|
-
const configPath = this.resolveTenantConfigPath(normalizedId);
|
|
662
|
-
if (!(0, fs_1.existsSync)(configPath)) {
|
|
663
|
-
throw new Error(`Tenant ${normalizedId} not found`);
|
|
664
|
-
}
|
|
665
|
-
const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
|
|
666
|
-
const servers = (config.remoteServers || []);
|
|
667
|
-
const idx = servers.findIndex((s) => s.name?.toLowerCase() === serverName.toLowerCase());
|
|
668
|
-
if (idx === -1)
|
|
669
|
-
return false;
|
|
670
|
-
servers.splice(idx, 1);
|
|
671
|
-
config.remoteServers = servers;
|
|
672
|
-
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
673
|
-
this.loadedTenants.delete(normalizedId);
|
|
674
|
-
console.log(`[TenantManager] Removed sync server "${serverName}" from tenant ${normalizedId}`);
|
|
675
|
-
return true;
|
|
676
|
-
}
|
|
677
|
-
// =======================================================================
|
|
678
776
|
// Server stores
|
|
679
777
|
// =======================================================================
|
|
680
778
|
async getServerStore(tenantId, dbId, storeKind) {
|
|
@@ -705,6 +803,9 @@ class TenantManager {
|
|
|
705
803
|
const purgedDocResolver = dbId === "directory"
|
|
706
804
|
? undefined
|
|
707
805
|
: () => this.getPurgedDocRegistry(tenantId).getPurgedDocIds(dbId);
|
|
806
|
+
const builtinWriteContext = dbId === types_1.USER_DIRECTORY_DB_ID
|
|
807
|
+
? this.buildUserdirectoryWriteContext(tenant, localStore)
|
|
808
|
+
: undefined;
|
|
708
809
|
const serverStore = new ServerNetworkContentAddressedStore_1.ServerNetworkContentAddressedStore(localStore, directory, tenant.authService, this.cryptoAdapter, undefined, {
|
|
709
810
|
timestampProvider,
|
|
710
811
|
witnessDbid: dbId,
|
|
@@ -713,6 +814,8 @@ class TenantManager {
|
|
|
713
814
|
dbAccessEvaluator,
|
|
714
815
|
revokedKeyResolver,
|
|
715
816
|
purgedDocResolver,
|
|
817
|
+
builtinWriteContext,
|
|
818
|
+
...this.peerAccessControl,
|
|
716
819
|
});
|
|
717
820
|
tenant.serverStores.set(cacheKey, serverStore);
|
|
718
821
|
console.log(`[TenantManager] Created server store for ${tenantId}/${dbId}/${storeKind}`);
|
|
@@ -734,15 +837,27 @@ class TenantManager {
|
|
|
734
837
|
// The witness evaluates Tier 1 at its acceptance time (now), the same time
|
|
735
838
|
// it will stamp into the receipt (docs/accesscontrol.md §5.3, §7).
|
|
736
839
|
const trustedTime = Date.now();
|
|
737
|
-
// Resolve `$author
|
|
738
|
-
//
|
|
840
|
+
// Resolve `$author` at grant level: creator at the doc_create trusted
|
|
841
|
+
// time, signer at the witness's acceptance time. Falls back to device-key
|
|
842
|
+
// equality when the directory cannot answer.
|
|
739
843
|
let isAuthor = entry.entryType === "doc_create";
|
|
740
844
|
if (!isAuthor) {
|
|
741
845
|
try {
|
|
742
846
|
const docEntries = await localStore.findNewEntriesForDoc([], entry.docId);
|
|
743
847
|
const createEntry = docEntries.find((m) => m.entryType === "doc_create");
|
|
744
848
|
if (createEntry) {
|
|
745
|
-
|
|
849
|
+
const samePerson = directory.isSamePerson;
|
|
850
|
+
if (typeof samePerson === "function") {
|
|
851
|
+
isAuthor = await samePerson({
|
|
852
|
+
creatorSigningKey: createEntry.createdByPublicKey,
|
|
853
|
+
signerSigningKey: entry.createdByPublicKey,
|
|
854
|
+
creatorTrustedTime: createEntry.receivedAt ?? createEntry.createdAt,
|
|
855
|
+
signerTrustedTime: trustedTime,
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
isAuthor = createEntry.createdByPublicKey === entry.createdByPublicKey;
|
|
860
|
+
}
|
|
746
861
|
}
|
|
747
862
|
}
|
|
748
863
|
catch {
|
|
@@ -759,6 +874,55 @@ class TenantManager {
|
|
|
759
874
|
});
|
|
760
875
|
};
|
|
761
876
|
}
|
|
877
|
+
/**
|
|
878
|
+
* Builtin `userdirectory` write invariant: admin may create/delete, only the
|
|
879
|
+
* owning person may change. `username_hash` is read from the decrypted
|
|
880
|
+
* `doc_create` payload (`$publicinfos`). Personal documents are sealed to
|
|
881
|
+
* their owner and therefore undecryptable here, so they are owned by whoever
|
|
882
|
+
* signed their `doc_create` — `resolveCreatorSigningKey` answers that from
|
|
883
|
+
* the entry chain alone.
|
|
884
|
+
*/
|
|
885
|
+
buildUserdirectoryWriteContext(tenant, localStore) {
|
|
886
|
+
const adminPublicKey = tenant.context.config.adminSigningPublicKey;
|
|
887
|
+
const mindooTenant = tenant.mindooTenant;
|
|
888
|
+
return {
|
|
889
|
+
adminPublicKey,
|
|
890
|
+
resolveCreatorSigningKey: async (entry) => {
|
|
891
|
+
const createEntry = await this.findCreateEntry(localStore, entry);
|
|
892
|
+
return createEntry?.createdByPublicKey ?? null;
|
|
893
|
+
},
|
|
894
|
+
resolveDocumentUsernameHash: mindooTenant
|
|
895
|
+
? async (entry) => {
|
|
896
|
+
const createEntry = await this.findCreateEntry(localStore, entry);
|
|
897
|
+
if (!createEntry)
|
|
898
|
+
return null;
|
|
899
|
+
try {
|
|
900
|
+
const plaintext = await mindooTenant.decryptPayload(createEntry.encryptedData, createEntry.decryptionKeyId);
|
|
901
|
+
return (0, builtinDbInvariants_1.usernameHashFromCreateChangeBytes)(plaintext);
|
|
902
|
+
}
|
|
903
|
+
catch {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
: undefined,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
async findCreateEntry(localStore, entry) {
|
|
911
|
+
if (entry.entryType === "doc_create") {
|
|
912
|
+
return entry;
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const metas = await localStore.findNewEntriesForDoc([], entry.docId);
|
|
916
|
+
const createMeta = metas.find((m) => m.entryType === "doc_create");
|
|
917
|
+
if (!createMeta)
|
|
918
|
+
return null;
|
|
919
|
+
const loaded = await localStore.getEntries([createMeta.id]);
|
|
920
|
+
return loaded[0] ?? null;
|
|
921
|
+
}
|
|
922
|
+
catch {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
762
926
|
/**
|
|
763
927
|
* Build a database-open evaluator closure for a server store, or undefined
|
|
764
928
|
* when the directory cannot evaluate the database-open policy (e.g. a
|