mindoodb 0.0.19 → 0.0.21
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/LICENSE +200 -0
- package/README-server.md +14 -14
- package/README.md +12 -10
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts +6 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts.map +1 -1
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js +519 -373
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js.map +1 -1
- package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts +12 -0
- package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts.map +1 -1
- package/dist/browser/cache/IndexedDBLocalCacheStore.js +152 -65
- package/dist/browser/cache/IndexedDBLocalCacheStore.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +2 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +14 -0
- package/dist/core/BaseMindooDB.d.ts +594 -6
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +2478 -191
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +65 -1
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +250 -35
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantFactory.js +47 -18
- package/dist/core/BaseMindooTenantFactory.js.map +1 -1
- package/dist/core/DocumentConflictAnalysis.d.ts +22 -0
- package/dist/core/DocumentConflictAnalysis.d.ts.map +1 -0
- package/dist/core/DocumentConflictAnalysis.js +101 -0
- package/dist/core/DocumentConflictAnalysis.js.map +1 -0
- package/dist/core/MindooDBServerAdmin.d.ts +26 -1
- package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
- package/dist/core/MindooDBServerAdmin.js +19 -3
- package/dist/core/MindooDBServerAdmin.js.map +1 -1
- package/dist/core/appendonlystores/types.d.ts +9 -0
- package/dist/core/appendonlystores/types.d.ts.map +1 -1
- package/dist/core/automerge-adapter.d.ts.map +1 -1
- package/dist/core/automerge-adapter.js +26 -14
- package/dist/core/automerge-adapter.js.map +1 -1
- package/dist/core/cache/EncryptedLocalCacheStore.d.ts +51 -1
- package/dist/core/cache/EncryptedLocalCacheStore.d.ts.map +1 -1
- package/dist/core/cache/EncryptedLocalCacheStore.js +48 -9
- package/dist/core/cache/EncryptedLocalCacheStore.js.map +1 -1
- package/dist/core/cache/LocalCacheStore.d.ts +34 -0
- package/dist/core/cache/LocalCacheStore.d.ts.map +1 -1
- package/dist/core/cache/LocalCacheStore.js +16 -0
- package/dist/core/cache/LocalCacheStore.js.map +1 -1
- package/dist/core/databaseIdValidation.d.ts +13 -0
- package/dist/core/databaseIdValidation.d.ts.map +1 -0
- package/dist/core/databaseIdValidation.js +38 -0
- package/dist/core/databaseIdValidation.js.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +5 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualView.d.ts +14 -0
- package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualView.js +38 -6
- package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts +9 -2
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.js +10 -3
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +23 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +17 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
- package/dist/core/indexing/virtualviews/types.d.ts +8 -0
- package/dist/core/indexing/virtualviews/types.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/types.js.map +1 -1
- package/dist/core/keys/KeyBag.d.ts +244 -7
- package/dist/core/keys/KeyBag.d.ts.map +1 -1
- package/dist/core/keys/KeyBag.js +299 -82
- package/dist/core/keys/KeyBag.js.map +1 -1
- package/dist/core/tenantIdValidation.d.ts +19 -0
- package/dist/core/tenantIdValidation.d.ts.map +1 -0
- package/dist/core/tenantIdValidation.js +43 -0
- package/dist/core/tenantIdValidation.js.map +1 -0
- package/dist/core/types.d.ts +703 -12
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/node/cache/FileSystemLocalCacheStore.d.ts +2 -1
- package/dist/node/cache/FileSystemLocalCacheStore.d.ts.map +1 -1
- package/dist/node/cache/FileSystemLocalCacheStore.js +4 -0
- package/dist/node/cache/FileSystemLocalCacheStore.js.map +1 -1
- package/dist/node/server/ServerSync.d.ts.map +1 -1
- package/dist/node/server/ServerSync.js +3 -1
- package/dist/node/server/ServerSync.js.map +1 -1
- package/dist/node/server/TenantManager.d.ts +1 -0
- package/dist/node/server/TenantManager.d.ts.map +1 -1
- package/dist/node/server/TenantManager.js +11 -3
- package/dist/node/server/TenantManager.js.map +1 -1
- package/dist/node/server/validation.d.ts +2 -6
- package/dist/node/server/validation.d.ts.map +1 -1
- package/dist/node/server/validation.js +14 -24
- package/dist/node/server/validation.js.map +1 -1
- package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts +1 -0
- package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts.map +1 -1
- package/dist/reactnative/cache/MMKVLocalCacheStore.js +14 -0
- package/dist/reactnative/cache/MMKVLocalCacheStore.js.map +1 -1
- package/package.json +25 -23
|
@@ -33,6 +33,24 @@ export declare class BaseMindooTenant implements MindooTenant {
|
|
|
33
33
|
private databaseCache;
|
|
34
34
|
private directoryCache;
|
|
35
35
|
private remoteStoreCache;
|
|
36
|
+
/**
|
|
37
|
+
* Cursor into the local KeyBag changes feed. Tracks the last event
|
|
38
|
+
* consumed by {@link reconcileKeyBagChanges} so we can replay only the
|
|
39
|
+
* deltas and stay idempotent across both live notifications and
|
|
40
|
+
* explicit reconcile calls.
|
|
41
|
+
*/
|
|
42
|
+
private keyBagChangeCursor;
|
|
43
|
+
/**
|
|
44
|
+
* Disposer returned by {@link KeyBag.onChanges} for live notifications.
|
|
45
|
+
* `null` once the tenant has been torn down via {@link disposeCacheManager}.
|
|
46
|
+
*/
|
|
47
|
+
private unsubscribeKeyBagChanges;
|
|
48
|
+
/**
|
|
49
|
+
* Single-flight latch for {@link reconcileKeyBagChanges}. Concurrent calls
|
|
50
|
+
* coalesce onto the same in-flight reconcile so live listener firings and
|
|
51
|
+
* explicit calls cannot race against each other or duplicate work.
|
|
52
|
+
*/
|
|
53
|
+
private keyBagReconcilePromise;
|
|
36
54
|
private decryptedTenantKeyCache?;
|
|
37
55
|
private decryptedUserSigningKeyCache?;
|
|
38
56
|
private decryptedUserEncryptionKeyCache?;
|
|
@@ -49,7 +67,21 @@ export declare class BaseMindooTenant implements MindooTenant {
|
|
|
49
67
|
* Map value: true if trusted, false if explicitly revoked
|
|
50
68
|
*/
|
|
51
69
|
private additionalTrustedKeys?;
|
|
52
|
-
constructor(factory: MindooTenantFactory, tenantId: string, administrationPublicKey: string, administrationEncryptionPublicKey: string, currentUser: PrivateUserId, currentUserPassword: string, keyBag: KeyBag, storeFactory: ContentAddressedStoreFactory, cryptoAdapter: CryptoAdapter, logger?: Logger, additionalTrustedKeys?: ReadonlyMap<string, boolean>, localCacheStore?: LocalCacheStore
|
|
70
|
+
constructor(factory: MindooTenantFactory, tenantId: string, administrationPublicKey: string, administrationEncryptionPublicKey: string, currentUser: PrivateUserId, currentUserPassword: string, keyBag: KeyBag, storeFactory: ContentAddressedStoreFactory, cryptoAdapter: CryptoAdapter, logger?: Logger, additionalTrustedKeys?: ReadonlyMap<string, boolean>, localCacheStore?: LocalCacheStore,
|
|
71
|
+
/**
|
|
72
|
+
* Optional pre-derived crypto material for the current user. When the
|
|
73
|
+
* caller has already decrypted the user's signing/encryption private
|
|
74
|
+
* keys (e.g. once at login) and/or derived the cache encryption key,
|
|
75
|
+
* passing them here skips the per-instance PBKDF2 + decrypt path. The
|
|
76
|
+
* `currentUserPassword` argument may then be empty and is never used
|
|
77
|
+
* downstream (as long as the requested key is already present in the
|
|
78
|
+
* cache).
|
|
79
|
+
*/
|
|
80
|
+
preDecryptedMaterial?: {
|
|
81
|
+
signingKey?: CryptoKey;
|
|
82
|
+
encryptionKey?: CryptoKey;
|
|
83
|
+
cacheEncryptionKey?: CryptoKey;
|
|
84
|
+
});
|
|
53
85
|
/**
|
|
54
86
|
* Initialize the tenant.
|
|
55
87
|
*/
|
|
@@ -64,6 +96,7 @@ export declare class BaseMindooTenant implements MindooTenant {
|
|
|
64
96
|
*/
|
|
65
97
|
disposeCacheManager(): Promise<void>;
|
|
66
98
|
getCryptoAdapter(): CryptoAdapter;
|
|
99
|
+
private normalizeTimeTravelDate;
|
|
67
100
|
getFactory(): MindooTenantFactory;
|
|
68
101
|
getId(): string;
|
|
69
102
|
/**
|
|
@@ -95,7 +128,30 @@ export declare class BaseMindooTenant implements MindooTenant {
|
|
|
95
128
|
openDirectory(): Promise<MindooTenantDirectory>;
|
|
96
129
|
private assertCurrentUserCanOpenDB;
|
|
97
130
|
openDB(id: string, options?: OpenDBOptions): Promise<MindooDB>;
|
|
131
|
+
/**
|
|
132
|
+
* Replay any pending KeyBag mutations for this tenant and refresh open
|
|
133
|
+
* live databases so document visibility matches the current key set.
|
|
134
|
+
*
|
|
135
|
+
* Designed as the single barrier callers can `await` to be certain that
|
|
136
|
+
* subsequent reads observe the latest add/remove of doc keys. Behaviour:
|
|
137
|
+
*
|
|
138
|
+
* - Idempotent and concurrency-safe. Concurrent callers share the same
|
|
139
|
+
* in-flight reconcile via {@link keyBagReconcilePromise}, and a second
|
|
140
|
+
* call after the cursor catches up is a fast no-op.
|
|
141
|
+
* - Cursor-based. Replays from the persisted cursor in
|
|
142
|
+
* {@link keyBagChangeCursor}, so missed live notifications are
|
|
143
|
+
* automatically recovered.
|
|
144
|
+
* - Tenant-scoped. Events for other tenants in a shared KeyBag are
|
|
145
|
+
* consumed (the cursor still advances) but ignored.
|
|
146
|
+
* - Invalidates the cached tenant default key when its add/remove was
|
|
147
|
+
* observed, so the next encrypt/decrypt call re-reads the bag.
|
|
148
|
+
* - Skips time-travel databases. Those views are immutable historical
|
|
149
|
+
* cuts and never participate in visibility reconciliation.
|
|
150
|
+
*/
|
|
151
|
+
reconcileKeyBagChanges(): Promise<void>;
|
|
98
152
|
createDocSignerFor(signKey: SigningKeyPair): MindooDocSigner;
|
|
153
|
+
private exportJoinResponseDocKeyVersions;
|
|
154
|
+
private resolveJoinResponseDocKeyIds;
|
|
99
155
|
/**
|
|
100
156
|
* Approve a join request and produce a join response.
|
|
101
157
|
*/
|
|
@@ -171,6 +227,14 @@ export declare class BaseMindooTenant implements MindooTenant {
|
|
|
171
227
|
* @returns true if the key can be resolved, false otherwise
|
|
172
228
|
*/
|
|
173
229
|
hasDecryptionKey(decryptionKeyId: string): Promise<boolean>;
|
|
230
|
+
/**
|
|
231
|
+
* Stable fingerprint of the doc-type keys this tenant currently has
|
|
232
|
+
* access to, delegating to {@link KeyBag.getDocKeyFingerprint} scoped
|
|
233
|
+
* to this tenant. Persisted by {@link BaseMindooDB} alongside its cache
|
|
234
|
+
* checkpoint so warm starts can skip a full metadata scan when the
|
|
235
|
+
* KeyBag composition has not changed.
|
|
236
|
+
*/
|
|
237
|
+
getDocKeyFingerprint(): Promise<string>;
|
|
174
238
|
/**
|
|
175
239
|
* Get the symmetric key for a given key ID.
|
|
176
240
|
* This is a protected helper method that can be used by subclasses.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseMindooTenant.d.ts","sourceRoot":"","sources":["../../src/core/BaseMindooTenant.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,qBAAqB,EACrB,4BAA4B,EAE5B,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,WAAW,EACX,YAAY,
|
|
1
|
+
{"version":3,"file":"BaseMindooTenant.d.ts","sourceRoot":"","sources":["../../src/core/BaseMindooTenant.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ const logging_1 = require("./logging");
|
|
|
12
12
|
const MindooURI_1 = require("./uri/MindooURI");
|
|
13
13
|
const EncryptedLocalCacheStore_1 = require("./cache/EncryptedLocalCacheStore");
|
|
14
14
|
const CacheManager_1 = require("./cache/CacheManager");
|
|
15
|
+
const databaseIdValidation_1 = require("./databaseIdValidation");
|
|
15
16
|
/**
|
|
16
17
|
* BaseMindooTenant is a platform-agnostic implementation of MindooTenant
|
|
17
18
|
* that works in both browser and server environments.
|
|
@@ -27,10 +28,38 @@ const CacheManager_1 = require("./cache/CacheManager");
|
|
|
27
28
|
* - PBKDF2 key derivation (for password-based key derivation)
|
|
28
29
|
*/
|
|
29
30
|
class BaseMindooTenant {
|
|
30
|
-
constructor(factory, tenantId, administrationPublicKey, administrationEncryptionPublicKey, currentUser, currentUserPassword, keyBag, storeFactory, cryptoAdapter, logger, additionalTrustedKeys, localCacheStore
|
|
31
|
+
constructor(factory, tenantId, administrationPublicKey, administrationEncryptionPublicKey, currentUser, currentUserPassword, keyBag, storeFactory, cryptoAdapter, logger, additionalTrustedKeys, localCacheStore,
|
|
32
|
+
/**
|
|
33
|
+
* Optional pre-derived crypto material for the current user. When the
|
|
34
|
+
* caller has already decrypted the user's signing/encryption private
|
|
35
|
+
* keys (e.g. once at login) and/or derived the cache encryption key,
|
|
36
|
+
* passing them here skips the per-instance PBKDF2 + decrypt path. The
|
|
37
|
+
* `currentUserPassword` argument may then be empty and is never used
|
|
38
|
+
* downstream (as long as the requested key is already present in the
|
|
39
|
+
* cache).
|
|
40
|
+
*/
|
|
41
|
+
preDecryptedMaterial) {
|
|
31
42
|
this.databaseCache = new Map();
|
|
32
43
|
this.directoryCache = null;
|
|
33
44
|
this.remoteStoreCache = new Map();
|
|
45
|
+
/**
|
|
46
|
+
* Cursor into the local KeyBag changes feed. Tracks the last event
|
|
47
|
+
* consumed by {@link reconcileKeyBagChanges} so we can replay only the
|
|
48
|
+
* deltas and stay idempotent across both live notifications and
|
|
49
|
+
* explicit reconcile calls.
|
|
50
|
+
*/
|
|
51
|
+
this.keyBagChangeCursor = null;
|
|
52
|
+
/**
|
|
53
|
+
* Disposer returned by {@link KeyBag.onChanges} for live notifications.
|
|
54
|
+
* `null` once the tenant has been torn down via {@link disposeCacheManager}.
|
|
55
|
+
*/
|
|
56
|
+
this.unsubscribeKeyBagChanges = null;
|
|
57
|
+
/**
|
|
58
|
+
* Single-flight latch for {@link reconcileKeyBagChanges}. Concurrent calls
|
|
59
|
+
* coalesce onto the same in-flight reconcile so live listener firings and
|
|
60
|
+
* explicit calls cannot race against each other or duplicate work.
|
|
61
|
+
*/
|
|
62
|
+
this.keyBagReconcilePromise = null;
|
|
34
63
|
// Local cache support
|
|
35
64
|
this.cacheManager = null;
|
|
36
65
|
this.factory = factory;
|
|
@@ -50,9 +79,44 @@ class BaseMindooTenant {
|
|
|
50
79
|
this.logger =
|
|
51
80
|
logger ||
|
|
52
81
|
new logging_1.MindooLogger((0, logging_1.getDefaultLogLevel)(), `Tenant:${tenantId}`, true);
|
|
53
|
-
//
|
|
82
|
+
// Hook the local KeyBag changes feed:
|
|
83
|
+
// - Snapshot the cursor so any pre-existing key state is treated as
|
|
84
|
+
// "already seen" and only future mutations trigger reconciliation.
|
|
85
|
+
// - Subscribe to live events scoped to this tenant; missed events
|
|
86
|
+
// are still recovered when callers invoke
|
|
87
|
+
// {@link reconcileKeyBagChanges} explicitly (it replays from the
|
|
88
|
+
// cursor regardless of whether the listener fired).
|
|
89
|
+
this.keyBagChangeCursor = this.keyBag.getLatestChangeCursor();
|
|
90
|
+
this.unsubscribeKeyBagChanges = this.keyBag.onChanges((event) => {
|
|
91
|
+
if (event.type !== "doc" || event.tenantId !== this.tenantId) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Fire-and-forget: errors are surfaced via the reconcile path itself.
|
|
95
|
+
// We intentionally do not await here to keep the mutation call site
|
|
96
|
+
// synchronous from the listener's perspective.
|
|
97
|
+
void this.reconcileKeyBagChanges();
|
|
98
|
+
});
|
|
99
|
+
// Pre-warm the per-user CryptoKey caches so getDecryptedSigningKey()
|
|
100
|
+
// and getDecryptedEncryptionKey() never run PBKDF2 + decryptPrivateKey
|
|
101
|
+
// when the caller has already derived these keys at login.
|
|
102
|
+
if (preDecryptedMaterial?.signingKey) {
|
|
103
|
+
this.decryptedUserSigningKeyCache = preDecryptedMaterial.signingKey;
|
|
104
|
+
}
|
|
105
|
+
if (preDecryptedMaterial?.encryptionKey) {
|
|
106
|
+
this.decryptedUserEncryptionKeyCache = preDecryptedMaterial.encryptionKey;
|
|
107
|
+
}
|
|
108
|
+
// Set up cache if a local cache store is provided. When a pre-derived
|
|
109
|
+
// cache encryption key is available, prefer that to skip the password
|
|
110
|
+
// path and avoid retaining the plaintext password inside the encrypted
|
|
111
|
+
// cache wrapper.
|
|
54
112
|
if (localCacheStore) {
|
|
55
|
-
const encryptedStore =
|
|
113
|
+
const encryptedStore = preDecryptedMaterial?.cacheEncryptionKey
|
|
114
|
+
? new EncryptedLocalCacheStore_1.EncryptedLocalCacheStore(localCacheStore, {
|
|
115
|
+
cacheKey: preDecryptedMaterial.cacheEncryptionKey,
|
|
116
|
+
cryptoAdapter: this.cryptoAdapter,
|
|
117
|
+
logger: this.logger.createChild("EncryptedCache"),
|
|
118
|
+
})
|
|
119
|
+
: new EncryptedLocalCacheStore_1.EncryptedLocalCacheStore(localCacheStore, currentUserPassword, this.cryptoAdapter, this.logger.createChild("EncryptedCache"));
|
|
56
120
|
this.cacheManager = new CacheManager_1.CacheManager(encryptedStore, undefined, this.logger.createChild("CacheManager"));
|
|
57
121
|
}
|
|
58
122
|
}
|
|
@@ -76,6 +140,12 @@ class BaseMindooTenant {
|
|
|
76
140
|
* Should be called when the tenant is being closed.
|
|
77
141
|
*/
|
|
78
142
|
async disposeCacheManager() {
|
|
143
|
+
// Unsubscribe from KeyBag changes alongside cache teardown. Callers
|
|
144
|
+
// already use `disposeCacheManager` as the "tenant is closing" signal,
|
|
145
|
+
// so this keeps lifecycle hooks in one place. Safe to call multiple
|
|
146
|
+
// times - the disposer is nulled out after the first call.
|
|
147
|
+
this.unsubscribeKeyBagChanges?.();
|
|
148
|
+
this.unsubscribeKeyBagChanges = null;
|
|
79
149
|
if (this.cacheManager) {
|
|
80
150
|
await this.cacheManager.dispose();
|
|
81
151
|
this.cacheManager = null;
|
|
@@ -84,6 +154,16 @@ class BaseMindooTenant {
|
|
|
84
154
|
getCryptoAdapter() {
|
|
85
155
|
return this.cryptoAdapter;
|
|
86
156
|
}
|
|
157
|
+
normalizeTimeTravelDate(value) {
|
|
158
|
+
if (value == null || value === "") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const timestamp = value instanceof Date ? value.getTime() : typeof value === "number" ? value : Date.parse(value);
|
|
162
|
+
if (!Number.isFinite(timestamp)) {
|
|
163
|
+
throw new Error(`Invalid timeTravelDate: ${String(value)}`);
|
|
164
|
+
}
|
|
165
|
+
return timestamp;
|
|
166
|
+
}
|
|
87
167
|
getFactory() {
|
|
88
168
|
return this.factory;
|
|
89
169
|
}
|
|
@@ -356,40 +436,171 @@ class BaseMindooTenant {
|
|
|
356
436
|
throw new Error(`User "${currentUser.username}" does not have tenant access yet; the tenant admin must grant access first.`);
|
|
357
437
|
}
|
|
358
438
|
async openDB(id, options) {
|
|
439
|
+
const validDbId = (0, databaseIdValidation_1.validateDatabaseId)(id, "dbId");
|
|
359
440
|
// Enforce admin-only mode for directory database - this is a security invariant
|
|
360
441
|
// The directory database must only accept entries signed by the admin key
|
|
361
|
-
const effectiveOptions =
|
|
442
|
+
const effectiveOptions = validDbId === "directory"
|
|
362
443
|
? { ...options, adminOnlyDb: true }
|
|
363
444
|
: options ?? {};
|
|
364
|
-
|
|
445
|
+
const normalizedTimeTravelDate = this.normalizeTimeTravelDate(effectiveOptions.timeTravelDate);
|
|
446
|
+
const databaseCacheKey = normalizedTimeTravelDate == null
|
|
447
|
+
? validDbId
|
|
448
|
+
: `${validDbId}::tt:${normalizedTimeTravelDate}`;
|
|
449
|
+
await this.assertCurrentUserCanOpenDB(validDbId);
|
|
365
450
|
// Return cached database if it exists
|
|
366
|
-
const cached = this.databaseCache.get(
|
|
451
|
+
const cached = this.databaseCache.get(databaseCacheKey);
|
|
367
452
|
if (cached) {
|
|
368
453
|
// For directory DB, verify admin-only flag matches (defensive check)
|
|
369
|
-
if (
|
|
454
|
+
if (validDbId === "directory" && !cached.isAdminOnlyDb()) {
|
|
370
455
|
throw new Error("Directory database was cached without adminOnlyDb - this should never happen");
|
|
371
456
|
}
|
|
457
|
+
// Live databases must reflect the current KeyBag state before they
|
|
458
|
+
// are handed back to callers. Time-travel snapshots are immutable
|
|
459
|
+
// historical views and intentionally do not participate in
|
|
460
|
+
// visibility reconciliation, so we skip the call for them.
|
|
461
|
+
if (normalizedTimeTravelDate == null) {
|
|
462
|
+
await this.reconcileKeyBagChanges();
|
|
463
|
+
}
|
|
372
464
|
return cached;
|
|
373
465
|
}
|
|
374
466
|
// Extract store options and DB-specific options
|
|
375
|
-
const { adminOnlyDb, attachmentConfig, documentCacheConfig, snapshotConfig, performanceCallback, ...storeOptions } = effectiveOptions;
|
|
376
|
-
//
|
|
377
|
-
|
|
467
|
+
const { adminOnlyDb, timeTravelDate: _timeTravelDate, attachmentConfig, documentCacheConfig, snapshotConfig, performanceCallback, ...storeOptions } = effectiveOptions;
|
|
468
|
+
// Snapshot opens can share an already-open live store instance. This keeps
|
|
469
|
+
// in-memory factories usable while the snapshot DB maintains isolated state.
|
|
470
|
+
const liveDbForSnapshot = normalizedTimeTravelDate == null
|
|
471
|
+
? null
|
|
472
|
+
: this.databaseCache.get(validDbId);
|
|
473
|
+
const { docStore, attachmentStore } = liveDbForSnapshot
|
|
474
|
+
? {
|
|
475
|
+
docStore: liveDbForSnapshot.getStore(),
|
|
476
|
+
attachmentStore: liveDbForSnapshot.getAttachmentStore(),
|
|
477
|
+
}
|
|
478
|
+
: this.storeFactory.createStore(validDbId, storeOptions);
|
|
378
479
|
const dbLogger = this.logger.createChild("BaseMindooDB");
|
|
379
|
-
const db = new BaseMindooDB_1.BaseMindooDB(this, docStore, attachmentStore, attachmentConfig, documentCacheConfig, snapshotConfig, adminOnlyDb ?? false, dbLogger, performanceCallback);
|
|
380
|
-
if (this.cacheManager) {
|
|
480
|
+
const db = new BaseMindooDB_1.BaseMindooDB(this, docStore, attachmentStore, attachmentConfig, documentCacheConfig, snapshotConfig, adminOnlyDb ?? false, dbLogger, performanceCallback, normalizedTimeTravelDate);
|
|
481
|
+
if (this.cacheManager && normalizedTimeTravelDate == null) {
|
|
381
482
|
db.setCacheManager(this.cacheManager);
|
|
382
483
|
}
|
|
383
484
|
await db.initialize();
|
|
384
485
|
// Cache the database for future use
|
|
385
|
-
this.databaseCache.set(
|
|
486
|
+
this.databaseCache.set(databaseCacheKey, db);
|
|
386
487
|
return db;
|
|
387
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Replay any pending KeyBag mutations for this tenant and refresh open
|
|
491
|
+
* live databases so document visibility matches the current key set.
|
|
492
|
+
*
|
|
493
|
+
* Designed as the single barrier callers can `await` to be certain that
|
|
494
|
+
* subsequent reads observe the latest add/remove of doc keys. Behaviour:
|
|
495
|
+
*
|
|
496
|
+
* - Idempotent and concurrency-safe. Concurrent callers share the same
|
|
497
|
+
* in-flight reconcile via {@link keyBagReconcilePromise}, and a second
|
|
498
|
+
* call after the cursor catches up is a fast no-op.
|
|
499
|
+
* - Cursor-based. Replays from the persisted cursor in
|
|
500
|
+
* {@link keyBagChangeCursor}, so missed live notifications are
|
|
501
|
+
* automatically recovered.
|
|
502
|
+
* - Tenant-scoped. Events for other tenants in a shared KeyBag are
|
|
503
|
+
* consumed (the cursor still advances) but ignored.
|
|
504
|
+
* - Invalidates the cached tenant default key when its add/remove was
|
|
505
|
+
* observed, so the next encrypt/decrypt call re-reads the bag.
|
|
506
|
+
* - Skips time-travel databases. Those views are immutable historical
|
|
507
|
+
* cuts and never participate in visibility reconciliation.
|
|
508
|
+
*/
|
|
509
|
+
async reconcileKeyBagChanges() {
|
|
510
|
+
if (this.keyBagReconcilePromise) {
|
|
511
|
+
return this.keyBagReconcilePromise;
|
|
512
|
+
}
|
|
513
|
+
this.keyBagReconcilePromise = (async () => {
|
|
514
|
+
let sawTenantDocKeyChange = false;
|
|
515
|
+
let latestCursor = this.keyBagChangeCursor;
|
|
516
|
+
// Consume every pending event so the cursor advances even for
|
|
517
|
+
// unrelated tenants sharing the same bag. We only need to react to
|
|
518
|
+
// doc keys for our own tenant though.
|
|
519
|
+
for await (const event of this.keyBag.iterateChangesSince(this.keyBagChangeCursor)) {
|
|
520
|
+
latestCursor = { changeSeq: event.changeSeq };
|
|
521
|
+
if (event.type !== "doc" || event.tenantId !== this.tenantId) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
sawTenantDocKeyChange = true;
|
|
525
|
+
if (event.keyId === types_1.DEFAULT_TENANT_KEY_ID) {
|
|
526
|
+
// The cached plaintext default key may now refer to a rotated
|
|
527
|
+
// or removed version; drop it so the next access re-resolves.
|
|
528
|
+
this.decryptedTenantKeyCache = undefined;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
this.keyBagChangeCursor = latestCursor;
|
|
532
|
+
if (!sawTenantDocKeyChange) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
// Notify every open live database; time-travel views are read-only
|
|
536
|
+
// historical snapshots and intentionally skip reconciliation.
|
|
537
|
+
for (const db of this.databaseCache.values()) {
|
|
538
|
+
if (db.isTimeTravelMode()) {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const reconcile = db.reconcileKeyVisibility;
|
|
542
|
+
if (typeof reconcile === "function") {
|
|
543
|
+
await reconcile.call(db);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
})();
|
|
547
|
+
try {
|
|
548
|
+
await this.keyBagReconcilePromise;
|
|
549
|
+
}
|
|
550
|
+
finally {
|
|
551
|
+
this.keyBagReconcilePromise = null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
388
554
|
createDocSignerFor(signKey) {
|
|
389
555
|
this.logger.debug(`Creating MindooDocSigner for signing key pair`);
|
|
390
556
|
const signerLogger = this.logger.createChild("MindooDocSigner");
|
|
391
557
|
return new MindooDocSigner_1.MindooDocSigner(this, signKey, signerLogger);
|
|
392
558
|
}
|
|
559
|
+
async exportJoinResponseDocKeyVersions(keyId, sharePassword) {
|
|
560
|
+
const scopedKeyId = `doc:${this.tenantId}:${keyId}`;
|
|
561
|
+
const details = (await this.keyBag.listKeyDetails())
|
|
562
|
+
.filter((detail) => detail.scopedKeyId === scopedKeyId)
|
|
563
|
+
.sort((a, b) => a.versionIndex - b.versionIndex);
|
|
564
|
+
if (details.length === 0) {
|
|
565
|
+
throw new Error(`Failed to export document key "${keyId}" for tenant "${this.tenantId}"`);
|
|
566
|
+
}
|
|
567
|
+
const versions = [];
|
|
568
|
+
for (const detail of details) {
|
|
569
|
+
const encryptedKey = await this.keyBag.encryptAndExportKeyVersion("doc", this.tenantId, keyId, detail.versionIndex, sharePassword);
|
|
570
|
+
if (!encryptedKey) {
|
|
571
|
+
throw new Error(`Failed to export document key "${keyId}" version ${detail.versionIndex} for tenant "${this.tenantId}"`);
|
|
572
|
+
}
|
|
573
|
+
const { createdAt: encryptedKeyCreatedAt, ...encryptedKeyPayload } = encryptedKey;
|
|
574
|
+
versions.push({
|
|
575
|
+
createdAt: detail.createdAt ?? encryptedKeyCreatedAt,
|
|
576
|
+
encryptedKey: encryptedKeyPayload,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return { keyId, versions };
|
|
580
|
+
}
|
|
581
|
+
resolveJoinResponseDocKeyIds(options) {
|
|
582
|
+
const requestedKeyIds = options.sharedDocKeyIds ?? [types_1.PUBLIC_INFOS_KEY_ID, types_1.DEFAULT_TENANT_KEY_ID];
|
|
583
|
+
const keyIds = new Set();
|
|
584
|
+
keyIds.add(types_1.PUBLIC_INFOS_KEY_ID);
|
|
585
|
+
for (const keyId of requestedKeyIds) {
|
|
586
|
+
const normalizedKeyId = keyId.trim();
|
|
587
|
+
if (normalizedKeyId) {
|
|
588
|
+
keyIds.add(normalizedKeyId);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return [
|
|
592
|
+
types_1.PUBLIC_INFOS_KEY_ID,
|
|
593
|
+
...Array.from(keyIds)
|
|
594
|
+
.filter((keyId) => keyId !== types_1.PUBLIC_INFOS_KEY_ID)
|
|
595
|
+
.sort((a, b) => {
|
|
596
|
+
if (a === types_1.DEFAULT_TENANT_KEY_ID)
|
|
597
|
+
return -1;
|
|
598
|
+
if (b === types_1.DEFAULT_TENANT_KEY_ID)
|
|
599
|
+
return 1;
|
|
600
|
+
return a.localeCompare(b);
|
|
601
|
+
}),
|
|
602
|
+
];
|
|
603
|
+
}
|
|
393
604
|
async approveJoinRequest(joinRequest, options) {
|
|
394
605
|
// Parse the join request if it's a URI string
|
|
395
606
|
let request;
|
|
@@ -416,24 +627,17 @@ class BaseMindooTenant {
|
|
|
416
627
|
userEncryptionPublicKey: request.encryptionPublicKey,
|
|
417
628
|
};
|
|
418
629
|
await directory.registerUser(publicUserId, options.adminSigningKey, options.adminPassword);
|
|
419
|
-
// 2. Export
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
// 3. Export the $publicinfos key encrypted with the share password
|
|
425
|
-
const encryptedPublicInfosKey = await this.keyBag.encryptAndExportKey("doc", this.tenantId, types_1.PUBLIC_INFOS_KEY_ID, options.sharePassword);
|
|
426
|
-
if (!encryptedPublicInfosKey) {
|
|
427
|
-
throw new Error(`Failed to export $publicinfos key`);
|
|
428
|
-
}
|
|
429
|
-
// 4. Build the join response
|
|
630
|
+
// 2. Export selected document keys encrypted with the share password.
|
|
631
|
+
// `$publicinfos` is mandatory because the joining user needs it for
|
|
632
|
+
// directory access; all selected key ids include every rotated version.
|
|
633
|
+
const encryptedDocKeys = await Promise.all(this.resolveJoinResponseDocKeyIds(options).map((keyId) => this.exportJoinResponseDocKeyVersions(keyId, options.sharePassword)));
|
|
634
|
+
// 3. Build the join response
|
|
430
635
|
const joinResponse = {
|
|
431
|
-
v:
|
|
636
|
+
v: 2,
|
|
432
637
|
tenantId: this.tenantId,
|
|
433
638
|
adminSigningPublicKey: this.administrationPublicKey,
|
|
434
639
|
adminEncryptionPublicKey: this.administrationEncryptionPublicKey,
|
|
435
|
-
|
|
436
|
-
encryptedPublicInfosKey,
|
|
640
|
+
encryptedDocKeys,
|
|
437
641
|
};
|
|
438
642
|
if (options.serverUrl) {
|
|
439
643
|
joinResponse.serverUrl = options.serverUrl;
|
|
@@ -580,13 +784,14 @@ class BaseMindooTenant {
|
|
|
580
784
|
* Create a remote store connected to a MindooDB server, ready for sync.
|
|
581
785
|
*/
|
|
582
786
|
async connectToServer(serverUrl, dbId, storeKind = types_1.StoreKind.docs) {
|
|
583
|
-
|
|
584
|
-
|
|
787
|
+
const validDbId = (0, databaseIdValidation_1.validateDatabaseId)(dbId, "dbId");
|
|
788
|
+
console.log(`[connectToServer] Connecting to server: ${serverUrl}, db: ${validDbId}, storeKind: ${storeKind}`);
|
|
789
|
+
this.logger.info(`Connecting to server: ${serverUrl}, db: ${validDbId}, storeKind: ${storeKind}`);
|
|
585
790
|
const normalizedServerUrl = serverUrl.replace(/\/$/, "");
|
|
586
|
-
const cacheKey = `${normalizedServerUrl}::${
|
|
791
|
+
const cacheKey = `${normalizedServerUrl}::${validDbId}::${storeKind}`;
|
|
587
792
|
const cachedStore = this.remoteStoreCache.get(cacheKey);
|
|
588
793
|
if (cachedStore) {
|
|
589
|
-
this.logger.debug(`Reusing cached remote store for ${normalizedServerUrl}, db: ${
|
|
794
|
+
this.logger.debug(`Reusing cached remote store for ${normalizedServerUrl}, db: ${validDbId}`);
|
|
590
795
|
return cachedStore;
|
|
591
796
|
}
|
|
592
797
|
const remoteStorePromise = (async () => {
|
|
@@ -598,7 +803,7 @@ class BaseMindooTenant {
|
|
|
598
803
|
const transport = new HttpTransport({
|
|
599
804
|
baseUrl,
|
|
600
805
|
tenantId: this.tenantId,
|
|
601
|
-
dbId,
|
|
806
|
+
dbId: validDbId,
|
|
602
807
|
storeKind,
|
|
603
808
|
}, this.logger.createChild("HttpTransport"));
|
|
604
809
|
// Get the current user's decrypted signing key
|
|
@@ -606,9 +811,9 @@ class BaseMindooTenant {
|
|
|
606
811
|
// Get the current user's decrypted RSA encryption private key (for decrypting entries)
|
|
607
812
|
const decryptedEncryptionKey = await this.getDecryptedEncryptionKey();
|
|
608
813
|
// Create the client network store
|
|
609
|
-
const store = new ClientNetworkContentAddressedStore(
|
|
610
|
-
console.log(`[connectToServer] ✓ Connected to server for db "${
|
|
611
|
-
this.logger.info(`Connected to server for db "${
|
|
814
|
+
const store = new ClientNetworkContentAddressedStore(validDbId, storeKind, transport, this.cryptoAdapter, this.currentUser.username, signingKey, decryptedEncryptionKey, this.logger.createChild(`ClientNetworkStore:${validDbId}:${storeKind}`));
|
|
815
|
+
console.log(`[connectToServer] ✓ Connected to server for db "${validDbId}"`);
|
|
816
|
+
this.logger.info(`Connected to server for db "${validDbId}"`);
|
|
612
817
|
return store;
|
|
613
818
|
})();
|
|
614
819
|
this.remoteStoreCache.set(cacheKey, remoteStorePromise);
|
|
@@ -752,6 +957,16 @@ class BaseMindooTenant {
|
|
|
752
957
|
throw error;
|
|
753
958
|
}
|
|
754
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Stable fingerprint of the doc-type keys this tenant currently has
|
|
962
|
+
* access to, delegating to {@link KeyBag.getDocKeyFingerprint} scoped
|
|
963
|
+
* to this tenant. Persisted by {@link BaseMindooDB} alongside its cache
|
|
964
|
+
* checkpoint so warm starts can skip a full metadata scan when the
|
|
965
|
+
* KeyBag composition has not changed.
|
|
966
|
+
*/
|
|
967
|
+
async getDocKeyFingerprint() {
|
|
968
|
+
return this.keyBag.getDocKeyFingerprint(this.tenantId);
|
|
969
|
+
}
|
|
755
970
|
/**
|
|
756
971
|
* Get the symmetric key for a given key ID.
|
|
757
972
|
* This is a protected helper method that can be used by subclasses.
|