mindoodb 0.0.1
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.md +263 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.d.ts +5 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.d.ts.map +1 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.js +17 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.js.map +1 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +111 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +237 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts +78 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -0
- package/dist/appendonlystores/network/HttpTransport.js +356 -0
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +131 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +248 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -0
- package/dist/appendonlystores/network/index.d.ts +8 -0
- package/dist/appendonlystores/network/index.d.ts.map +1 -0
- package/dist/appendonlystores/network/index.js +21 -0
- package/dist/appendonlystores/network/index.js.map +1 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.d.ts +14 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.d.ts.map +1 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.js +30 -0
- package/dist/browser/crypto/BrowserCryptoAdapter.js.map +1 -0
- package/dist/browser/index.d.ts +3 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +26 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/core/BaseMindooDB.d.ts +182 -0
- package/dist/core/BaseMindooDB.d.ts.map +1 -0
- package/dist/core/BaseMindooDB.js +1701 -0
- package/dist/core/BaseMindooDB.js.map +1 -0
- package/dist/core/BaseMindooTenant.d.ts +155 -0
- package/dist/core/BaseMindooTenant.d.ts.map +1 -0
- package/dist/core/BaseMindooTenant.js +673 -0
- package/dist/core/BaseMindooTenant.js.map +1 -0
- package/dist/core/BaseMindooTenantDirectory.d.ts +99 -0
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -0
- package/dist/core/BaseMindooTenantDirectory.js +776 -0
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -0
- package/dist/core/BaseMindooTenantFactory.d.ts +91 -0
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -0
- package/dist/core/BaseMindooTenantFactory.js +297 -0
- package/dist/core/BaseMindooTenantFactory.js.map +1 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts +111 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts.map +1 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.js +293 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.js.map +1 -0
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts +78 -0
- package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/AuthenticationService.js +297 -0
- package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -0
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts +174 -0
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/NetworkTransport.js +3 -0
- package/dist/core/appendonlystores/network/NetworkTransport.js.map +1 -0
- package/dist/core/appendonlystores/network/types.d.ts +135 -0
- package/dist/core/appendonlystores/network/types.d.ts.map +1 -0
- package/dist/core/appendonlystores/network/types.js +35 -0
- package/dist/core/appendonlystores/network/types.js.map +1 -0
- package/dist/core/appendonlystores/types.d.ts +157 -0
- package/dist/core/appendonlystores/types.d.ts.map +1 -0
- package/dist/core/appendonlystores/types.js +3 -0
- package/dist/core/appendonlystores/types.js.map +1 -0
- package/dist/core/crypto/CryptoAdapter.d.ts +19 -0
- package/dist/core/crypto/CryptoAdapter.d.ts.map +1 -0
- package/dist/core/crypto/CryptoAdapter.js +3 -0
- package/dist/core/crypto/CryptoAdapter.js.map +1 -0
- package/dist/core/crypto/MindooDocSigner.d.ts +106 -0
- package/dist/core/crypto/MindooDocSigner.d.ts.map +1 -0
- package/dist/core/crypto/MindooDocSigner.js +192 -0
- package/dist/core/crypto/MindooDocSigner.js.map +1 -0
- package/dist/core/crypto/RSAEncryption.d.ts +62 -0
- package/dist/core/crypto/RSAEncryption.d.ts.map +1 -0
- package/dist/core/crypto/RSAEncryption.js +167 -0
- package/dist/core/crypto/RSAEncryption.js.map +1 -0
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +17 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +53 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.d.ts +33 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.js +27 -0
- package/dist/core/indexing/virtualviews/IViewEntryAccessCheck.js.map +1 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.d.ts +31 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.js +3 -0
- package/dist/core/indexing/virtualviews/IVirtualViewDataProvider.js.map +1 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +52 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +106 -0
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.d.ts +44 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.js +107 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKey.js.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts +28 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js +133 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualView.d.ts +109 -0
- package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualView.js +504 -0
- package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.d.ts +59 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.js +70 -0
- package/dist/core/indexing/virtualviews/VirtualViewColumn.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts +49 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.js +69 -0
- package/dist/core/indexing/virtualviews/VirtualViewDataChange.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +121 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +365 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts +131 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js +236 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts +330 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.js +1010 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.js.map +1 -0
- package/dist/core/indexing/virtualviews/index.d.ts +53 -0
- package/dist/core/indexing/virtualviews/index.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/index.js +89 -0
- package/dist/core/indexing/virtualviews/index.js.map +1 -0
- package/dist/core/indexing/virtualviews/types.d.ts +115 -0
- package/dist/core/indexing/virtualviews/types.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/types.js +175 -0
- package/dist/core/indexing/virtualviews/types.js.map +1 -0
- package/dist/core/keys/KeyBag.d.ts +122 -0
- package/dist/core/keys/KeyBag.d.ts.map +1 -0
- package/dist/core/keys/KeyBag.js +401 -0
- package/dist/core/keys/KeyBag.js.map +1 -0
- package/dist/core/types.d.ts +1004 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/userid/index.d.ts +53 -0
- package/dist/core/userid/index.d.ts.map +1 -0
- package/dist/core/userid/index.js +3 -0
- package/dist/core/userid/index.js.map +1 -0
- package/dist/core/utils/idGeneration.d.ts +109 -0
- package/dist/core/utils/idGeneration.d.ts.map +1 -0
- package/dist/core/utils/idGeneration.js +182 -0
- package/dist/core/utils/idGeneration.js.map +1 -0
- package/dist/core/utils/index.d.ts +5 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/index.js +19 -0
- package/dist/core/utils/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/node/crypto/NodeCryptoAdapter.d.ts +16 -0
- package/dist/node/crypto/NodeCryptoAdapter.d.ts.map +1 -0
- package/dist/node/crypto/NodeCryptoAdapter.js +53 -0
- package/dist/node/crypto/NodeCryptoAdapter.js.map +1 -0
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +26 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# MindooDB - Your Data. Your Control.
|
|
2
|
+
|
|
3
|
+
**Sleep well, even if your hosting service gets hacked.** 🔒
|
|
4
|
+
|
|
5
|
+
MindooDB is an **end-to-end encrypted, offline-first sync database**.
|
|
6
|
+
It lets apps collaborate and sync data without giving servers access to the contents.
|
|
7
|
+
|
|
8
|
+
Even if someone has full access to your infrastructure — database dumps, backups, logs — all they get is ciphertext.
|
|
9
|
+
|
|
10
|
+
Your data is encrypted on the client before it ever touches a server. No plaintext. No server-side keys. No trust required.
|
|
11
|
+
|
|
12
|
+
> ⚠️ **Alpha software**: This project is in early development and not yet recommended for production use. APIs may change without notice.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## The Problem
|
|
16
|
+
|
|
17
|
+
Traditional databases trust the server. If your hosting provider is compromised, your data is exposed. Even "encrypted at rest" solutions decrypt data server-side for queries. **MindooDB takes a different approach**: encryption keys never leave your clients.
|
|
18
|
+
|
|
19
|
+
## How It Works
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
23
|
+
│ Your Clients │
|
|
24
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
25
|
+
│ │ Alice's │ │ Bob's │ │ Charlie's │ │
|
|
26
|
+
│ │ Device │ │ Device │ │ Device │ │
|
|
27
|
+
│ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │
|
|
28
|
+
│ │ │ Keys │ │ │ │ Keys │ │ │ │ Keys │ │ ← Keys stay │
|
|
29
|
+
│ │ │(private)│ │ │ │(private)│ │ │ │(private)│ │ on devices │
|
|
30
|
+
│ │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │ │
|
|
31
|
+
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
|
32
|
+
└─────────┼────────────────┼────────────────┼──────────────────────┘
|
|
33
|
+
│ encrypted │ encrypted │ encrypted
|
|
34
|
+
▼ ▼ ▼
|
|
35
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ Server (or P2P Peers) │
|
|
37
|
+
│ │
|
|
38
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
39
|
+
│ │ Encrypted Blobs (unreadable) │ │
|
|
40
|
+
│ │ 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 │ │
|
|
41
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
42
|
+
│ │
|
|
43
|
+
│ Server can sync & store data, but CANNOT read it │
|
|
44
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Sync happens through content-addressed stores**: clients exchange only the encrypted entries they're missing. Works peer-to-peer, client-server, or any combination.
|
|
48
|
+
|
|
49
|
+
## Key Features
|
|
50
|
+
|
|
51
|
+
| Feature | What It Means |
|
|
52
|
+
|---------|---------------|
|
|
53
|
+
| 🛡️ **End-to-End Encrypted** | Data encrypted on client before sync. Servers can't decrypt. |
|
|
54
|
+
| 📴 **Offline-First** | Create and edit documents without network. Sync when online. |
|
|
55
|
+
| ✍️ **Signed Changes** | Every change is digitally signed. Proves authorship, prevents tampering. |
|
|
56
|
+
| 🔗 **Tamperproof History** | Append-only, cryptographically chained. Like a blockchain for your docs. |
|
|
57
|
+
| 🤝 **Real-time Collaboration** | Built on [Automerge](https://automerge.org/) CRDTs. Conflicts resolve automatically. |
|
|
58
|
+
| 🔑 **Fine-grained Access** | Named encryption keys for sensitive documents. Share with specific users. |
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
### Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install mindoodb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Create a Tenant and Start Working
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import {
|
|
72
|
+
BaseMindooTenantFactory,
|
|
73
|
+
InMemoryAppendOnlyStoreFactory,
|
|
74
|
+
KeyBag
|
|
75
|
+
} from "mindoodb";
|
|
76
|
+
|
|
77
|
+
// 1. Set up storage (in-memory for demo; use file/server-backed for production)
|
|
78
|
+
const storeFactory = new InMemoryAppendOnlyStoreFactory();
|
|
79
|
+
const factory = new BaseMindooTenantFactory(storeFactory);
|
|
80
|
+
|
|
81
|
+
// 2. Create a user (generates signing + encryption key pairs)
|
|
82
|
+
const user = await factory.createUserId("CN=alice/O=acme", "user-password");
|
|
83
|
+
const keyBag = new KeyBag(user.userEncryptionKeyPair.privateKey, "user-password");
|
|
84
|
+
|
|
85
|
+
// 3. Create admin keys (for managing users)
|
|
86
|
+
const adminKeyPair = await factory.createSigningKeyPair("admin-password");
|
|
87
|
+
|
|
88
|
+
// 4. Create a tenant (an organization that shares documents)
|
|
89
|
+
const tenant = await factory.createTenant(
|
|
90
|
+
"acme-corp",
|
|
91
|
+
adminKeyPair.publicKey,
|
|
92
|
+
"tenant-key-password",
|
|
93
|
+
user,
|
|
94
|
+
"user-password",
|
|
95
|
+
keyBag
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// 5. Open a database and create documents
|
|
99
|
+
const db = await tenant.openDB("contacts");
|
|
100
|
+
const doc = await db.createDocument();
|
|
101
|
+
|
|
102
|
+
await db.changeDoc(doc, async (d) => {
|
|
103
|
+
const data = d.getData();
|
|
104
|
+
data.name = "John Doe";
|
|
105
|
+
data.email = "john@example.com";
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 6. Read it back
|
|
109
|
+
const contacts = await db.getAllDocumentIds();
|
|
110
|
+
const loaded = await db.getDocument(contacts[0]);
|
|
111
|
+
console.log(loaded.getData()); // { name: "John Doe", email: "john@example.com" }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Sync Between Users
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Alice creates a document
|
|
118
|
+
const aliceDB = await aliceTenant.openDB("projects");
|
|
119
|
+
const project = await aliceDB.createDocument();
|
|
120
|
+
await aliceDB.changeDoc(project, (d) => { d.getData().title = "Secret Project"; });
|
|
121
|
+
|
|
122
|
+
// Bob pulls Alice's changes
|
|
123
|
+
const bobDB = await bobTenant.openDB("projects");
|
|
124
|
+
await bobDB.pullChangesFrom(aliceDB.getStore());
|
|
125
|
+
|
|
126
|
+
// Bob edits the document
|
|
127
|
+
const projectDoc = await bobDB.getDocument(project.id);
|
|
128
|
+
await bobDB.changeDoc(projectDoc, (d) => { d.getData().status = "In Progress"; });
|
|
129
|
+
|
|
130
|
+
// Alice pulls Bob's changes
|
|
131
|
+
await aliceDB.pullChangesFrom(bobDB.getStore());
|
|
132
|
+
// Alice now sees: { title: "Secret Project", status: "In Progress" }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Core Concepts
|
|
136
|
+
|
|
137
|
+
### Tenants
|
|
138
|
+
An organization or team that shares access. Created client-side—no server registration needed.
|
|
139
|
+
- Has a **default encryption key** (a regular KeyBag key shared with all members)
|
|
140
|
+
- Has an **admin key** (for registering/revoking users)
|
|
141
|
+
- Contains multiple databases
|
|
142
|
+
|
|
143
|
+
### Users
|
|
144
|
+
Identified by cryptographic key pairs, registered by an admin:
|
|
145
|
+
- **Signing key** (Ed25519): Proves authorship of changes
|
|
146
|
+
- **Encryption key** (RSA-OAEP): Protects local key storage
|
|
147
|
+
- Keys generated locally; only public keys shared with admin
|
|
148
|
+
|
|
149
|
+
### Databases
|
|
150
|
+
Each tenant can have multiple databases, created on-demand:
|
|
151
|
+
```typescript
|
|
152
|
+
const contacts = await tenant.openDB("contacts");
|
|
153
|
+
const invoices = await tenant.openDB("invoices");
|
|
154
|
+
```
|
|
155
|
+
A special **directory** database stores user registrations (admin-only).
|
|
156
|
+
|
|
157
|
+
### Documents
|
|
158
|
+
[Automerge](https://automerge.org/) CRDTs with full history:
|
|
159
|
+
- Every change is signed and encrypted
|
|
160
|
+
- Automatic conflict resolution for concurrent edits
|
|
161
|
+
- Time travel: reconstruct any historical state of documents (e.g. run queries on historic data)
|
|
162
|
+
|
|
163
|
+
### Attachments
|
|
164
|
+
Files attached to documents:
|
|
165
|
+
- Chunked (256KB) and encrypted
|
|
166
|
+
- Streaming upload/download for large files
|
|
167
|
+
- Deduplication across the tenant
|
|
168
|
+
|
|
169
|
+
See: [Attachments Documentation](./docs/attachments.md)
|
|
170
|
+
|
|
171
|
+
### Document Indexing
|
|
172
|
+
MindooDB provides a flexible, **incremental indexing** facility:
|
|
173
|
+
- **Cursor-based processing**: Only index documents that changed since the last run—no full rescans
|
|
174
|
+
- **Pluggable indexers**: Add any indexer you need (fulltext search, aggregations, custom queries)
|
|
175
|
+
- **Built-in [Virtual Views](./docs/virtualview.md)**: Spreadsheet-like views that categorize, sort, and aggregate documents
|
|
176
|
+
- **Cross-boundary queries**: Virtual Views can span multiple databases, mix local and remote data, or even query across tenants
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Incremental indexing: process only what's new
|
|
180
|
+
let cursor = null;
|
|
181
|
+
while (true) {
|
|
182
|
+
const { documents, cursor: newCursor } = await db.processChangesSince(cursor);
|
|
183
|
+
for (const doc of documents) {
|
|
184
|
+
if (doc.isDeleted()) {
|
|
185
|
+
mySearchIndex.remove(doc.id);
|
|
186
|
+
} else {
|
|
187
|
+
mySearchIndex.update(doc); // Flexsearch, Lunr, or custom
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
cursor = newCursor;
|
|
191
|
+
await sleep(1000);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Encryption Model
|
|
196
|
+
|
|
197
|
+
All encryption keys are stored in the **KeyBag**—a local, password-protected key store.
|
|
198
|
+
|
|
199
|
+
| Key Type | Purpose | Who Has It |
|
|
200
|
+
|----------|---------|------------|
|
|
201
|
+
| **`default` key** | Used when no other key is specified | All tenant members |
|
|
202
|
+
| **Named keys** | Fine-grained access for sensitive docs | Only users you share it with |
|
|
203
|
+
|
|
204
|
+
Keys are distributed offline (email, phone, in-person). The `default` key is typically shared during onboarding; named keys are shared as needed for specific documents.
|
|
205
|
+
|
|
206
|
+
## Security
|
|
207
|
+
|
|
208
|
+
### Cryptographic Guarantees
|
|
209
|
+
- **Signatures**: Ed25519 on every change—proves who wrote it
|
|
210
|
+
- **Encryption**: AES-256-GCM—servers see only ciphertext
|
|
211
|
+
- **Integrity**: Changes are hash-chained—tampering breaks the chain
|
|
212
|
+
|
|
213
|
+
### User Revocation
|
|
214
|
+
Revoked users:
|
|
215
|
+
- ❌ Cannot sync with peers or servers
|
|
216
|
+
- ❌ Cannot make new changes (signatures rejected)
|
|
217
|
+
- ⚠️ Can still read previously-synced data (fundamental trade-off of E2E encryption)
|
|
218
|
+
|
|
219
|
+
MindooDB includes **revocation timestamp protection** to prevent backdated changes from revoked users. See: [Revocation Protection](./docs/revocation-timestamp-protection.md)
|
|
220
|
+
|
|
221
|
+
### Audit Trail
|
|
222
|
+
Append-only storage means nothing is ever deleted:
|
|
223
|
+
- Complete history of who changed what and when
|
|
224
|
+
- Cryptographic proof of all operations
|
|
225
|
+
- GDPR compliance via `purgeDocHistory()` when legally required
|
|
226
|
+
|
|
227
|
+
## Use Cases
|
|
228
|
+
|
|
229
|
+
- **Multi-Tenant SaaS**: Each customer isolated with encrypted data
|
|
230
|
+
- **Collaborative Editing**: Real-time co-editing with signed changes
|
|
231
|
+
- **Secure File Sharing**: Named keys for need-to-know access
|
|
232
|
+
- **Audit-Critical Systems**: Tamperproof history meets compliance requirements
|
|
233
|
+
- **Offline-First Apps**: Full functionality without network; sync when connected
|
|
234
|
+
|
|
235
|
+
See: [Use Cases Documentation](./docs/usecases/README.md)
|
|
236
|
+
|
|
237
|
+
## Documentation
|
|
238
|
+
|
|
239
|
+
- [Architecture Specification](./docs/specification.md) — Full technical details
|
|
240
|
+
- [Virtual Views](./docs/virtualview.md) — Aggregations and cross-database views
|
|
241
|
+
- [Data Indexing](./docs/dataindexing.md) — Incremental indexing and search integration
|
|
242
|
+
- [P2P Sync](./docs/p2psync.md) — Peer-to-peer synchronization
|
|
243
|
+
- [Attachments](./docs/attachments.md) — File storage and streaming
|
|
244
|
+
|
|
245
|
+
## Support
|
|
246
|
+
|
|
247
|
+
Need help, have questions, or want to request a feature? We're here to help! :-)
|
|
248
|
+
|
|
249
|
+
- 🐛 **Bug Reports**: [Open an issue on GitHub](https://github.com/klehmann/mindoodb/issues)
|
|
250
|
+
- 💬 **Questions & Discussions**: [GitHub Discussions](https://github.com/klehmann/mindoodb/discussions)
|
|
251
|
+
- ✨ **Feature Requests**: [Create a feature request](https://github.com/klehmann/mindoodb/issues/new?template=feature_request.md)
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
Apache 2.0
|
|
256
|
+
|
|
257
|
+
## Author
|
|
258
|
+
|
|
259
|
+
Mindoo GmbH
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
**Your data. Your keys. Your control.** With MindooDB, even a complete server breach doesn't expose your documents. Sleep well! 😴🔒
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ContentAddressedStoreFactory, CreateStoreResult, OpenStoreOptions } from "../core/types";
|
|
2
|
+
export declare class InMemoryContentAddressedStoreFactory implements ContentAddressedStoreFactory {
|
|
3
|
+
createStore(dbId: string, _options?: OpenStoreOptions): CreateStoreResult;
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=InMemoryContentAddressedStoreFactory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InMemoryContentAddressedStoreFactory.d.ts","sourceRoot":"","sources":["../../src/appendonlystores/InMemoryContentAddressedStoreFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElG,qBAAa,oCAAqC,YAAW,4BAA4B;IACvF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,iBAAiB;CAS1E"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InMemoryContentAddressedStoreFactory = void 0;
|
|
4
|
+
const InMemoryContentAddressedStore_1 = require("../core/appendonlystores/InMemoryContentAddressedStore");
|
|
5
|
+
class InMemoryContentAddressedStoreFactory {
|
|
6
|
+
createStore(dbId, _options) {
|
|
7
|
+
// For in-memory stores, we use a single store for both documents and attachments
|
|
8
|
+
// (no separate attachment store needed for simple/test scenarios)
|
|
9
|
+
// Note: options are ignored for in-memory stores but accepted for interface compatibility
|
|
10
|
+
return {
|
|
11
|
+
docStore: new InMemoryContentAddressedStore_1.InMemoryContentAddressedStore(dbId),
|
|
12
|
+
// attachmentStore not provided - will use docStore for attachments
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.InMemoryContentAddressedStoreFactory = InMemoryContentAddressedStoreFactory;
|
|
17
|
+
//# sourceMappingURL=InMemoryContentAddressedStoreFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InMemoryContentAddressedStoreFactory.js","sourceRoot":"","sources":["../../src/appendonlystores/InMemoryContentAddressedStoreFactory.ts"],"names":[],"mappings":";;;AAAA,0GAAuG;AAGvG,MAAa,oCAAoC;IAC/C,WAAW,CAAC,IAAY,EAAE,QAA2B;QACnD,iFAAiF;QACjF,kEAAkE;QAClE,0FAA0F;QAC1F,OAAO;YACL,QAAQ,EAAE,IAAI,6DAA6B,CAAC,IAAI,CAAC;YACjD,mEAAmE;SACpE,CAAC;IACJ,CAAC;CACF;AAVD,oFAUC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ContentAddressedStore } from "../../core/types";
|
|
2
|
+
import type { StoreEntry, StoreEntryMetadata } from "../../core/types";
|
|
3
|
+
import type { CryptoAdapter } from "../../core/crypto/CryptoAdapter";
|
|
4
|
+
import type { NetworkTransport } from "../../core/appendonlystores/network/NetworkTransport";
|
|
5
|
+
/**
|
|
6
|
+
* Client-side network ContentAddressedStore that forwards all operations to a remote server.
|
|
7
|
+
*
|
|
8
|
+
* This store acts as a pure remote proxy:
|
|
9
|
+
* 1. All read/write operations are forwarded to the remote server
|
|
10
|
+
* 2. Authenticates with the remote peer using challenge-response
|
|
11
|
+
* 3. Decrypts RSA-encrypted entries received from the network
|
|
12
|
+
*
|
|
13
|
+
* Usage patterns:
|
|
14
|
+
* 1. **Direct remote**: Instantiate MindooDB with ClientNetworkContentAddressedStore for
|
|
15
|
+
* transparent remote read/write operations.
|
|
16
|
+
* 2. **Sync-based**: Use a local store (e.g., InMemoryContentAddressedStore) with MindooDB,
|
|
17
|
+
* then sync with a ClientNetworkContentAddressedStore via pullChangesFrom/pushChangesTo.
|
|
18
|
+
*/
|
|
19
|
+
export declare class ClientNetworkContentAddressedStore implements ContentAddressedStore {
|
|
20
|
+
private dbId;
|
|
21
|
+
private transport;
|
|
22
|
+
private rsaEncryption;
|
|
23
|
+
private privateEncryptionKey;
|
|
24
|
+
private username;
|
|
25
|
+
private signingKey;
|
|
26
|
+
private cryptoAdapter;
|
|
27
|
+
private accessToken;
|
|
28
|
+
private tokenExpiry;
|
|
29
|
+
/**
|
|
30
|
+
* Create a new ClientNetworkContentAddressedStore.
|
|
31
|
+
*
|
|
32
|
+
* @param dbId The database ID
|
|
33
|
+
* @param transport The network transport for remote communication
|
|
34
|
+
* @param cryptoAdapter The crypto adapter for encryption
|
|
35
|
+
* @param username The username for authentication
|
|
36
|
+
* @param signingKey The user's private signing key (for signing challenges)
|
|
37
|
+
* @param privateEncryptionKey The user's private RSA key (for decrypting received entries)
|
|
38
|
+
*/
|
|
39
|
+
constructor(dbId: string, transport: NetworkTransport, cryptoAdapter: CryptoAdapter, username: string, signingKey: CryptoKey, privateEncryptionKey: CryptoKey | string);
|
|
40
|
+
getId(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Store entries to the remote store.
|
|
43
|
+
* The entries are pushed to the server immediately.
|
|
44
|
+
*/
|
|
45
|
+
putEntries(entries: StoreEntry[]): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Get entries from the remote store by their IDs.
|
|
48
|
+
*
|
|
49
|
+
* @param ids The IDs of entries to retrieve
|
|
50
|
+
* @returns The entries with decrypted payloads
|
|
51
|
+
*/
|
|
52
|
+
getEntries(ids: string[]): Promise<StoreEntry[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Check which IDs exist in the remote store.
|
|
55
|
+
*
|
|
56
|
+
* @param ids The IDs to check
|
|
57
|
+
* @returns List of IDs that exist in the remote store
|
|
58
|
+
*/
|
|
59
|
+
hasEntries(ids: string[]): Promise<string[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Find new entries from the remote store.
|
|
62
|
+
*
|
|
63
|
+
* @param knownIds The list of entry IDs we already have
|
|
64
|
+
* @returns List of new entry metadata from the remote store
|
|
65
|
+
*/
|
|
66
|
+
findNewEntries(knownIds: string[]): Promise<StoreEntryMetadata[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Find new entries for a specific document from the remote store.
|
|
69
|
+
*/
|
|
70
|
+
findNewEntriesForDoc(knownIds: string[], docId: string): Promise<StoreEntryMetadata[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Get all entry IDs from the remote store.
|
|
73
|
+
* Used for synchronization to identify which entries the remote has.
|
|
74
|
+
*/
|
|
75
|
+
getAllIds(): Promise<string[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Resolve dependencies for an entry.
|
|
78
|
+
*
|
|
79
|
+
* Delegates to the remote server for efficient server-side traversal.
|
|
80
|
+
*/
|
|
81
|
+
resolveDependencies(startId: string, options?: Record<string, unknown>): Promise<string[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Purge document history from the store.
|
|
84
|
+
*
|
|
85
|
+
* Note: Network stores are proxies that forward operations to remote servers.
|
|
86
|
+
* Purging should be done on local stores after syncing data from remote stores.
|
|
87
|
+
* This method is a no-op for network stores.
|
|
88
|
+
*
|
|
89
|
+
* @param docId The document ID whose entry history should be purged
|
|
90
|
+
*/
|
|
91
|
+
purgeDocHistory(docId: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Ensure we have a valid access token, authenticating if necessary.
|
|
94
|
+
*/
|
|
95
|
+
private ensureAuthenticated;
|
|
96
|
+
/**
|
|
97
|
+
* Sign a challenge with the user's private signing key.
|
|
98
|
+
*/
|
|
99
|
+
private signChallenge;
|
|
100
|
+
/**
|
|
101
|
+
* Decrypt network-encrypted entries.
|
|
102
|
+
* Transforms NetworkEncryptedEntry[] to StoreEntry[].
|
|
103
|
+
*/
|
|
104
|
+
private decryptNetworkEntries;
|
|
105
|
+
/**
|
|
106
|
+
* Clear the cached access token.
|
|
107
|
+
* Call this if you need to force re-authentication.
|
|
108
|
+
*/
|
|
109
|
+
clearAuthCache(): void;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=ClientNetworkContentAddressedStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientNetworkContentAddressedStore.d.ts","sourceRoot":"","sources":["../../../src/appendonlystores/network/ClientNetworkContentAddressedStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AAK7F;;;;;;;;;;;;;GAaG;AACH,qBAAa,kCAAmC,YAAW,qBAAqB;IAC9E,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,aAAa,CAAgB;IAGrC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;;;;OASG;gBAED,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,gBAAgB,EAC3B,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,SAAS,EACrB,oBAAoB,EAAE,SAAS,GAAG,MAAM;IAW1C,KAAK,IAAI,MAAM;IAIf;;;OAGG;IACG,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAatD;;;;;OAKG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAmBtD;;;;;OAKG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAclD;;;;;OAKG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAUvE;;OAEG;IACG,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAU5F;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAUpC;;;;OAIG;IACG,mBAAmB,CACvB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,MAAM,EAAE,CAAC;IAUpB;;;;;;;;OAQG;IACG,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD;;OAEG;YACW,mBAAmB;IA8CjC;;OAEG;YACW,aAAa;IAa3B;;;OAGG;YACW,qBAAqB;IAkCnC;;;OAGG;IACH,cAAc,IAAI,IAAI;CAIvB"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClientNetworkContentAddressedStore = void 0;
|
|
4
|
+
const types_1 = require("../../core/appendonlystores/network/types");
|
|
5
|
+
const RSAEncryption_1 = require("../../core/crypto/RSAEncryption");
|
|
6
|
+
/**
|
|
7
|
+
* Client-side network ContentAddressedStore that forwards all operations to a remote server.
|
|
8
|
+
*
|
|
9
|
+
* This store acts as a pure remote proxy:
|
|
10
|
+
* 1. All read/write operations are forwarded to the remote server
|
|
11
|
+
* 2. Authenticates with the remote peer using challenge-response
|
|
12
|
+
* 3. Decrypts RSA-encrypted entries received from the network
|
|
13
|
+
*
|
|
14
|
+
* Usage patterns:
|
|
15
|
+
* 1. **Direct remote**: Instantiate MindooDB with ClientNetworkContentAddressedStore for
|
|
16
|
+
* transparent remote read/write operations.
|
|
17
|
+
* 2. **Sync-based**: Use a local store (e.g., InMemoryContentAddressedStore) with MindooDB,
|
|
18
|
+
* then sync with a ClientNetworkContentAddressedStore via pullChangesFrom/pushChangesTo.
|
|
19
|
+
*/
|
|
20
|
+
class ClientNetworkContentAddressedStore {
|
|
21
|
+
/**
|
|
22
|
+
* Create a new ClientNetworkContentAddressedStore.
|
|
23
|
+
*
|
|
24
|
+
* @param dbId The database ID
|
|
25
|
+
* @param transport The network transport for remote communication
|
|
26
|
+
* @param cryptoAdapter The crypto adapter for encryption
|
|
27
|
+
* @param username The username for authentication
|
|
28
|
+
* @param signingKey The user's private signing key (for signing challenges)
|
|
29
|
+
* @param privateEncryptionKey The user's private RSA key (for decrypting received entries)
|
|
30
|
+
*/
|
|
31
|
+
constructor(dbId, transport, cryptoAdapter, username, signingKey, privateEncryptionKey) {
|
|
32
|
+
// Cached access token
|
|
33
|
+
this.accessToken = null;
|
|
34
|
+
this.tokenExpiry = 0;
|
|
35
|
+
this.dbId = dbId;
|
|
36
|
+
this.transport = transport;
|
|
37
|
+
this.cryptoAdapter = cryptoAdapter;
|
|
38
|
+
this.rsaEncryption = new RSAEncryption_1.RSAEncryption(cryptoAdapter);
|
|
39
|
+
this.username = username;
|
|
40
|
+
this.signingKey = signingKey;
|
|
41
|
+
this.privateEncryptionKey = privateEncryptionKey;
|
|
42
|
+
}
|
|
43
|
+
getId() {
|
|
44
|
+
return this.dbId;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Store entries to the remote store.
|
|
48
|
+
* The entries are pushed to the server immediately.
|
|
49
|
+
*/
|
|
50
|
+
async putEntries(entries) {
|
|
51
|
+
if (entries.length === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Pushing ${entries.length} entries to remote`);
|
|
55
|
+
const token = await this.ensureAuthenticated();
|
|
56
|
+
await this.transport.putEntries(token, entries);
|
|
57
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Successfully pushed ${entries.length} entries`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get entries from the remote store by their IDs.
|
|
61
|
+
*
|
|
62
|
+
* @param ids The IDs of entries to retrieve
|
|
63
|
+
* @returns The entries with decrypted payloads
|
|
64
|
+
*/
|
|
65
|
+
async getEntries(ids) {
|
|
66
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Getting ${ids.length} entries from remote`);
|
|
67
|
+
if (ids.length === 0) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const token = await this.ensureAuthenticated();
|
|
71
|
+
const encryptedEntries = await this.transport.getEntries(token, ids);
|
|
72
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Received ${encryptedEntries.length} encrypted entries from remote`);
|
|
73
|
+
// Decrypt the RSA layer for each entry
|
|
74
|
+
const entries = await this.decryptNetworkEntries(encryptedEntries);
|
|
75
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Decrypted ${entries.length} entries`);
|
|
76
|
+
return entries;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check which IDs exist in the remote store.
|
|
80
|
+
*
|
|
81
|
+
* @param ids The IDs to check
|
|
82
|
+
* @returns List of IDs that exist in the remote store
|
|
83
|
+
*/
|
|
84
|
+
async hasEntries(ids) {
|
|
85
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Checking ${ids.length} IDs in remote`);
|
|
86
|
+
if (ids.length === 0) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const token = await this.ensureAuthenticated();
|
|
90
|
+
const existingIds = await this.transport.hasEntries(token, ids);
|
|
91
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Found ${existingIds.length} existing entries`);
|
|
92
|
+
return existingIds;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Find new entries from the remote store.
|
|
96
|
+
*
|
|
97
|
+
* @param knownIds The list of entry IDs we already have
|
|
98
|
+
* @returns List of new entry metadata from the remote store
|
|
99
|
+
*/
|
|
100
|
+
async findNewEntries(knownIds) {
|
|
101
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Finding new entries from remote`);
|
|
102
|
+
const token = await this.ensureAuthenticated();
|
|
103
|
+
const newEntries = await this.transport.findNewEntries(token, knownIds);
|
|
104
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Found ${newEntries.length} new entries from remote`);
|
|
105
|
+
return newEntries;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Find new entries for a specific document from the remote store.
|
|
109
|
+
*/
|
|
110
|
+
async findNewEntriesForDoc(knownIds, docId) {
|
|
111
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Finding new entries for doc ${docId} from remote`);
|
|
112
|
+
const token = await this.ensureAuthenticated();
|
|
113
|
+
const newEntries = await this.transport.findNewEntriesForDoc(token, knownIds, docId);
|
|
114
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Found ${newEntries.length} new entries for doc ${docId}`);
|
|
115
|
+
return newEntries;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get all entry IDs from the remote store.
|
|
119
|
+
* Used for synchronization to identify which entries the remote has.
|
|
120
|
+
*/
|
|
121
|
+
async getAllIds() {
|
|
122
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Getting all entry IDs from remote`);
|
|
123
|
+
const token = await this.ensureAuthenticated();
|
|
124
|
+
const allIds = await this.transport.getAllIds(token);
|
|
125
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Remote has ${allIds.length} entry IDs`);
|
|
126
|
+
return allIds;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Resolve dependencies for an entry.
|
|
130
|
+
*
|
|
131
|
+
* Delegates to the remote server for efficient server-side traversal.
|
|
132
|
+
*/
|
|
133
|
+
async resolveDependencies(startId, options) {
|
|
134
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Resolving dependencies for ${startId}`);
|
|
135
|
+
const token = await this.ensureAuthenticated();
|
|
136
|
+
const resolvedIds = await this.transport.resolveDependencies(token, startId, options);
|
|
137
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Resolved ${resolvedIds.length} dependencies`);
|
|
138
|
+
return resolvedIds;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Purge document history from the store.
|
|
142
|
+
*
|
|
143
|
+
* Note: Network stores are proxies that forward operations to remote servers.
|
|
144
|
+
* Purging should be done on local stores after syncing data from remote stores.
|
|
145
|
+
* This method is a no-op for network stores.
|
|
146
|
+
*
|
|
147
|
+
* @param docId The document ID whose entry history should be purged
|
|
148
|
+
*/
|
|
149
|
+
async purgeDocHistory(docId) {
|
|
150
|
+
console.warn(`[ClientNetworkContentAddressedStore:${this.dbId}] purgeDocHistory() called on network store for doc ${docId}. Network stores do not support purging directly. Purge should be done on local stores after syncing from remote.`);
|
|
151
|
+
// No-op: Network stores forward to server, purging should be done on local stores
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Ensure we have a valid access token, authenticating if necessary.
|
|
155
|
+
*/
|
|
156
|
+
async ensureAuthenticated() {
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
// Check if we have a valid token
|
|
159
|
+
if (this.accessToken && this.tokenExpiry > now + 60000) { // 1 minute buffer
|
|
160
|
+
return this.accessToken;
|
|
161
|
+
}
|
|
162
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Authenticating user: ${this.username}`);
|
|
163
|
+
// Request a challenge
|
|
164
|
+
const challenge = await this.transport.requestChallenge(this.username);
|
|
165
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Received challenge: ${challenge}`);
|
|
166
|
+
// Sign the challenge
|
|
167
|
+
const signature = await this.signChallenge(challenge);
|
|
168
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Signed challenge`);
|
|
169
|
+
// Authenticate
|
|
170
|
+
const result = await this.transport.authenticate(challenge, signature);
|
|
171
|
+
if (!result.success || !result.token) {
|
|
172
|
+
throw new types_1.NetworkError(types_1.NetworkErrorType.INVALID_SIGNATURE, result.error || "Authentication failed");
|
|
173
|
+
}
|
|
174
|
+
this.accessToken = result.token;
|
|
175
|
+
// Parse token expiry from JWT (simple extraction)
|
|
176
|
+
try {
|
|
177
|
+
const parts = result.token.split(".");
|
|
178
|
+
if (parts.length === 3) {
|
|
179
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
180
|
+
this.tokenExpiry = payload.exp * 1000; // Convert to milliseconds
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Default to 1 hour if we can't parse
|
|
185
|
+
this.tokenExpiry = now + 3600000;
|
|
186
|
+
}
|
|
187
|
+
console.log(`[ClientNetworkContentAddressedStore:${this.dbId}] Authentication successful`);
|
|
188
|
+
return this.accessToken;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Sign a challenge with the user's private signing key.
|
|
192
|
+
*/
|
|
193
|
+
async signChallenge(challenge) {
|
|
194
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
195
|
+
const messageBytes = new TextEncoder().encode(challenge);
|
|
196
|
+
const signature = await subtle.sign({ name: "Ed25519" }, this.signingKey, messageBytes);
|
|
197
|
+
return new Uint8Array(signature);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Decrypt network-encrypted entries.
|
|
201
|
+
* Transforms NetworkEncryptedEntry[] to StoreEntry[].
|
|
202
|
+
*/
|
|
203
|
+
async decryptNetworkEntries(encryptedEntries) {
|
|
204
|
+
const results = [];
|
|
205
|
+
for (const enc of encryptedEntries) {
|
|
206
|
+
// Decrypt the RSA layer to get the original symmetric-encrypted payload
|
|
207
|
+
const encryptedData = await this.rsaEncryption.decrypt(enc.rsaEncryptedPayload, this.privateEncryptionKey);
|
|
208
|
+
// Create the full StoreEntry using metadata from the network entry
|
|
209
|
+
const entry = {
|
|
210
|
+
entryType: enc.entryType,
|
|
211
|
+
id: enc.id,
|
|
212
|
+
contentHash: enc.contentHash,
|
|
213
|
+
docId: enc.docId,
|
|
214
|
+
dependencyIds: enc.dependencyIds,
|
|
215
|
+
createdAt: enc.createdAt,
|
|
216
|
+
createdByPublicKey: enc.createdByPublicKey,
|
|
217
|
+
decryptionKeyId: enc.decryptionKeyId,
|
|
218
|
+
signature: enc.signature,
|
|
219
|
+
originalSize: enc.originalSize,
|
|
220
|
+
encryptedSize: enc.encryptedSize,
|
|
221
|
+
encryptedData,
|
|
222
|
+
};
|
|
223
|
+
results.push(entry);
|
|
224
|
+
}
|
|
225
|
+
return results;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Clear the cached access token.
|
|
229
|
+
* Call this if you need to force re-authentication.
|
|
230
|
+
*/
|
|
231
|
+
clearAuthCache() {
|
|
232
|
+
this.accessToken = null;
|
|
233
|
+
this.tokenExpiry = 0;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
exports.ClientNetworkContentAddressedStore = ClientNetworkContentAddressedStore;
|
|
237
|
+
//# sourceMappingURL=ClientNetworkContentAddressedStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientNetworkContentAddressedStore.js","sourceRoot":"","sources":["../../../src/appendonlystores/network/ClientNetworkContentAddressedStore.ts"],"names":[],"mappings":"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"}
|