mindoodb 0.0.53 → 0.0.55
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 +125 -69
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +17 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +22 -0
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +58 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +156 -3
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
- package/dist/browser/index.mjs +2 -0
- package/dist/core/BaseMindooDB.d.ts +73 -74
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +263 -509
- package/dist/core/BaseMindooDB.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/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 +30 -0
- package/dist/core/builtinDbInvariants.d.ts.map +1 -1
- package/dist/core/builtinDbInvariants.js +97 -0
- package/dist/core/builtinDbInvariants.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/types.d.ts +42 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/node/server/CapabilityMatcher.d.ts +2 -2
- package/dist/node/server/CapabilityMatcher.js +2 -2
- package/dist/node/server/MindooDBServer.d.ts +14 -1
- package/dist/node/server/MindooDBServer.d.ts.map +1 -1
- package/dist/node/server/MindooDBServer.js +166 -159
- 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/TenantManager.d.ts +58 -6
- package/dist/node/server/TenantManager.d.ts.map +1 -1
- package/dist/node/server/TenantManager.js +90 -65
- 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 +1 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Owns everything cluster-shaped on one node: the peer session authenticator,
|
|
4
|
+
* one {@link PeerReplicator} per trusted server that has a `url`, the job
|
|
5
|
+
* registry behind the mutating admin actions, and the audit log.
|
|
6
|
+
*
|
|
7
|
+
* `MindooDBServer` holds exactly one of these and routes both `/system/peer/*`
|
|
8
|
+
* (server-to-server protocol) and `/system/cluster/*` (admin surface) into it.
|
|
9
|
+
* Keeping the two prefixes on one object is fine — they never share an auth
|
|
10
|
+
* class, and the read model both need is the same in-memory replication state.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ClusterManager = void 0;
|
|
14
|
+
const logging_1 = require("../../../core/logging");
|
|
15
|
+
const PeerAuthService_1 = require("./PeerAuthService");
|
|
16
|
+
const PeerReplicator_1 = require("./PeerReplicator");
|
|
17
|
+
const ClusterJobRunner_1 = require("./ClusterJobRunner");
|
|
18
|
+
const ClusterAuditLog_1 = require("./ClusterAuditLog");
|
|
19
|
+
class ClusterManager {
|
|
20
|
+
constructor(deps) {
|
|
21
|
+
this.deps = deps;
|
|
22
|
+
this.jobs = new ClusterJobRunner_1.ClusterJobRunner();
|
|
23
|
+
this.replicators = new Map();
|
|
24
|
+
this.started = false;
|
|
25
|
+
/**
|
|
26
|
+
* Tenants each peer is allowed to hear about, from the last Bloom handshake
|
|
27
|
+
* that peer performed against us.
|
|
28
|
+
*
|
|
29
|
+
* Inbound state, distinct from a replicator's own outbound intersection: this
|
|
30
|
+
* is what *they* asked about, and it exists so the peer event stream can be
|
|
31
|
+
* filtered. Without it the feed would announce every tenant we host, which
|
|
32
|
+
* would hand a peer the tenant list the Bloom exchange is designed to keep
|
|
33
|
+
* from it.
|
|
34
|
+
*/
|
|
35
|
+
this.peerIntersections = new Map();
|
|
36
|
+
this.logger =
|
|
37
|
+
deps.logger?.createChild("Cluster") ??
|
|
38
|
+
new logging_1.MindooLogger((0, logging_1.getDefaultLogLevel)(), "Cluster", true);
|
|
39
|
+
this.auth = new PeerAuthService_1.PeerAuthService(deps.cryptoAdapter, () => deps.listTrustedServers());
|
|
40
|
+
this.audit = new ClusterAuditLog_1.ClusterAuditLog(deps.dataDir);
|
|
41
|
+
}
|
|
42
|
+
get localRole() {
|
|
43
|
+
return this.deps.localRole ?? "peer";
|
|
44
|
+
}
|
|
45
|
+
// ------------------------------------------------------------- lifecycle
|
|
46
|
+
/**
|
|
47
|
+
* Bring the replicator set in line with `trusted-servers.json`.
|
|
48
|
+
*
|
|
49
|
+
* Called at startup and after any peer CRUD. Existing replicators are updated
|
|
50
|
+
* in place rather than recreated so their scan cursors and echo-suppression
|
|
51
|
+
* state survive an edit to, say, the attachment mode.
|
|
52
|
+
*/
|
|
53
|
+
syncReplicators() {
|
|
54
|
+
const configured = new Map(this.deps
|
|
55
|
+
.listTrustedServers()
|
|
56
|
+
.filter((peer) => typeof peer.url === "string" && peer.url.length > 0)
|
|
57
|
+
.map((peer) => [peer.name, peer]));
|
|
58
|
+
for (const [name, replicator] of this.replicators) {
|
|
59
|
+
if (!configured.has(name)) {
|
|
60
|
+
void replicator.stop();
|
|
61
|
+
this.replicators.delete(name);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const [name, peer] of configured) {
|
|
65
|
+
const existing = this.replicators.get(name);
|
|
66
|
+
if (existing) {
|
|
67
|
+
existing.updatePeer(peer);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const replicator = new PeerReplicator_1.PeerReplicator(peer, {
|
|
71
|
+
cryptoAdapter: this.deps.cryptoAdapter,
|
|
72
|
+
localServerName: this.deps.localServerName() ?? "",
|
|
73
|
+
localSigningPublicKey: this.deps.localSigningPublicKey() ?? "",
|
|
74
|
+
localRole: this.localRole,
|
|
75
|
+
getSigningKey: () => this.deps.getSigningKey(),
|
|
76
|
+
getEncryptionKey: () => this.deps.getEncryptionKey(),
|
|
77
|
+
listTenants: () => this.deps.listTenants(),
|
|
78
|
+
listDatabases: (tenantId) => this.deps.listDatabases(tenantId),
|
|
79
|
+
getLocalStore: (tenantId, dbId, storeKind) => this.deps.getLocalStore(tenantId, dbId, storeKind),
|
|
80
|
+
eventBus: this.deps.eventBus,
|
|
81
|
+
logger: this.logger,
|
|
82
|
+
});
|
|
83
|
+
this.replicators.set(name, replicator);
|
|
84
|
+
if (this.started)
|
|
85
|
+
replicator.start();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
start() {
|
|
89
|
+
if (this.started)
|
|
90
|
+
return;
|
|
91
|
+
if (!this.deps.localServerName()) {
|
|
92
|
+
this.logger.info("No server identity; peer replication stays off");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.started = true;
|
|
96
|
+
this.syncReplicators();
|
|
97
|
+
for (const replicator of this.replicators.values()) {
|
|
98
|
+
replicator.start();
|
|
99
|
+
}
|
|
100
|
+
this.logger.info(`Peer replication started for ${this.replicators.size} peer(s)`);
|
|
101
|
+
}
|
|
102
|
+
async stop() {
|
|
103
|
+
this.started = false;
|
|
104
|
+
await Promise.all([...this.replicators.values()].map((peer) => peer.stop()));
|
|
105
|
+
this.replicators.clear();
|
|
106
|
+
}
|
|
107
|
+
get autoSyncEnabled() {
|
|
108
|
+
return this.started;
|
|
109
|
+
}
|
|
110
|
+
getReplicator(name) {
|
|
111
|
+
return this.replicators.get(name) ?? null;
|
|
112
|
+
}
|
|
113
|
+
/** Record what a peer's Bloom handshake matched; see {@link peerIntersections}. */
|
|
114
|
+
recordPeerIntersection(peerName, tenantIds) {
|
|
115
|
+
this.peerIntersections.set(peerName, new Set(tenantIds));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* May this peer be told that something changed in this tenant?
|
|
119
|
+
*
|
|
120
|
+
* Closed by default: a peer that has not run the Bloom handshake in this
|
|
121
|
+
* process gets nothing, rather than everything.
|
|
122
|
+
*/
|
|
123
|
+
peerMaySeeTenant(peerName, tenantId) {
|
|
124
|
+
return this.peerIntersections.get(peerName)?.has(tenantId) ?? false;
|
|
125
|
+
}
|
|
126
|
+
// ------------------------------------------------------------ read model
|
|
127
|
+
topology() {
|
|
128
|
+
return {
|
|
129
|
+
server: { name: this.deps.localServerName(), role: this.localRole },
|
|
130
|
+
peers: [...this.replicators.values()].map((peer) => peer.topologyEntry()),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
status() {
|
|
134
|
+
const peers = [...this.replicators.values()].map((peer) => peer.snapshot());
|
|
135
|
+
// Node health is the worst peer's health: one stalled link is enough to
|
|
136
|
+
// mean this node is not fully converged with the mesh.
|
|
137
|
+
const { health, reasons } = (0, PeerReplicator_1.rollUpHealth)([], peers.flatMap((peer) => peer.reasons));
|
|
138
|
+
return {
|
|
139
|
+
server: this.deps.localServerName(),
|
|
140
|
+
health: peers.length === 0 ? "ok" : health,
|
|
141
|
+
reasons: peers.length === 0 ? [] : reasons,
|
|
142
|
+
autoSyncEnabled: this.started,
|
|
143
|
+
generatedAt: new Date().toISOString(),
|
|
144
|
+
peers,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
tenantStatus(tenantId) {
|
|
148
|
+
return {
|
|
149
|
+
tenantId,
|
|
150
|
+
generatedAt: new Date().toISOString(),
|
|
151
|
+
peers: [...this.replicators.values()].map((peer) => ({
|
|
152
|
+
name: peer.name,
|
|
153
|
+
tenant: peer.tenantSnapshot(tenantId),
|
|
154
|
+
})),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// --------------------------------------------------------------- actions
|
|
158
|
+
/**
|
|
159
|
+
* Dispatch an action as a job across the peers the scope selects.
|
|
160
|
+
*
|
|
161
|
+
* Records the request in the audit log first, with the job id, so an action
|
|
162
|
+
* that is issued is on record even if the node dies mid-run.
|
|
163
|
+
*/
|
|
164
|
+
runAction(kind, scope, requestedBy) {
|
|
165
|
+
const targets = this.targetsFor(scope);
|
|
166
|
+
if (targets.length === 0) {
|
|
167
|
+
this.audit.append({
|
|
168
|
+
at: new Date().toISOString(),
|
|
169
|
+
actor: requestedBy,
|
|
170
|
+
action: kind,
|
|
171
|
+
scope,
|
|
172
|
+
jobId: null,
|
|
173
|
+
outcome: "rejected",
|
|
174
|
+
detail: scope.peer ? "unknown peer" : "no peers configured",
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
accepted: false,
|
|
178
|
+
error: scope.peer ? `Unknown peer: ${scope.peer}` : "No peers configured",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const job = this.jobs.start(kind, scope, requestedBy, async (ctx) => {
|
|
182
|
+
const result = {
|
|
183
|
+
transferredEntries: 0,
|
|
184
|
+
transferredBytes: 0,
|
|
185
|
+
rejectedEntries: 0,
|
|
186
|
+
tenantsProcessed: 0,
|
|
187
|
+
databasesProcessed: 0,
|
|
188
|
+
};
|
|
189
|
+
let completed = 0;
|
|
190
|
+
const peerScope = { tenantId: scope.tenantId, dbId: scope.dbId };
|
|
191
|
+
for (const replicator of targets) {
|
|
192
|
+
switch (kind) {
|
|
193
|
+
case "sync":
|
|
194
|
+
case "retry-rejected": {
|
|
195
|
+
const run = kind === "sync"
|
|
196
|
+
? await replicator.syncNow(peerScope)
|
|
197
|
+
: await replicator.retryRejected(peerScope);
|
|
198
|
+
result.transferredEntries += run.pushed + run.pulled;
|
|
199
|
+
result.rejectedEntries += run.rejected;
|
|
200
|
+
result.tenantsProcessed += run.tenants;
|
|
201
|
+
result.databasesProcessed += run.databases;
|
|
202
|
+
if (run.errors.length > 0) {
|
|
203
|
+
throw new Error(run.errors.join("; "));
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case "refresh-intersection": {
|
|
208
|
+
const shared = await replicator.refreshIntersection();
|
|
209
|
+
result.tenantsProcessed += shared.length;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "pause":
|
|
213
|
+
replicator.pause();
|
|
214
|
+
break;
|
|
215
|
+
case "resume":
|
|
216
|
+
replicator.resume();
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
completed += 1;
|
|
220
|
+
ctx.progress(completed, targets.length);
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
this.audit.append({
|
|
225
|
+
at: new Date().toISOString(),
|
|
226
|
+
actor: requestedBy,
|
|
227
|
+
action: kind,
|
|
228
|
+
scope,
|
|
229
|
+
jobId: job.id,
|
|
230
|
+
outcome: "accepted",
|
|
231
|
+
});
|
|
232
|
+
return { accepted: true, job };
|
|
233
|
+
}
|
|
234
|
+
targetsFor(scope) {
|
|
235
|
+
if (!scope.peer)
|
|
236
|
+
return [...this.replicators.values()];
|
|
237
|
+
const replicator = this.replicators.get(scope.peer);
|
|
238
|
+
return replicator ? [replicator] : [];
|
|
239
|
+
}
|
|
240
|
+
/** Record a peer CRUD change; the caller has already persisted it. */
|
|
241
|
+
auditPeerChange(action, peerName, actor) {
|
|
242
|
+
this.audit.append({
|
|
243
|
+
at: new Date().toISOString(),
|
|
244
|
+
actor,
|
|
245
|
+
action,
|
|
246
|
+
scope: { peer: peerName },
|
|
247
|
+
jobId: null,
|
|
248
|
+
outcome: "accepted",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Mirrors of one tenant, for `GET /.well-known/mindoodb-cluster`.
|
|
253
|
+
*
|
|
254
|
+
* Only peers that actually hold the requested tenant are listed — a client
|
|
255
|
+
* asking about tenant A must not learn that this server also replicates
|
|
256
|
+
* tenants B and C to some other node.
|
|
257
|
+
*/
|
|
258
|
+
mirrorsForTenant(tenantId) {
|
|
259
|
+
const mirrors = [];
|
|
260
|
+
for (const replicator of this.replicators.values()) {
|
|
261
|
+
// No snapshot for this tenant means the Bloom intersection did not put it
|
|
262
|
+
// on both sides, so the peer is not a mirror a client could fail over to.
|
|
263
|
+
if (!replicator.tenantSnapshot(tenantId))
|
|
264
|
+
continue;
|
|
265
|
+
const entry = replicator.topologyEntry();
|
|
266
|
+
if (!entry.url)
|
|
267
|
+
continue;
|
|
268
|
+
mirrors.push({ name: entry.name, url: entry.url, role: entry.role });
|
|
269
|
+
}
|
|
270
|
+
return mirrors;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
exports.ClusterManager = ClusterManager;
|
|
274
|
+
//# sourceMappingURL=ClusterManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClusterManager.js","sourceRoot":"","sources":["../../../../src/node/server/peer/ClusterManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Peer (server-to-server) authentication for `/system/peer/*`.
|
|
3
|
+
*
|
|
4
|
+
* Same Ed25519 challenge-response as the tenant sync handshake, but the key
|
|
5
|
+
* lookup goes to `trusted-servers.json` instead of a tenant directory. The
|
|
6
|
+
* issued JWT is deliberately a **different token class** from the system-admin
|
|
7
|
+
* JWT: `/system/peer/*` accepts only peer tokens and `/system/cluster/*` only
|
|
8
|
+
* admin tokens, so a peer can never administer this node and an admin can never
|
|
9
|
+
* write as a peer.
|
|
10
|
+
*/
|
|
11
|
+
import type { CryptoAdapter } from "../../../core/crypto/CryptoAdapter";
|
|
12
|
+
import type { TrustedServer } from "../types";
|
|
13
|
+
export interface PeerTokenPayload {
|
|
14
|
+
/** Trusted-server name that authenticated. */
|
|
15
|
+
sub: string;
|
|
16
|
+
/** The Ed25519 key that signed the challenge, i.e. the peer's identity. */
|
|
17
|
+
publicsignkey: string;
|
|
18
|
+
/** Marks this as a peer token; absent/false tokens are rejected by peer routes. */
|
|
19
|
+
peer: true;
|
|
20
|
+
iat: number;
|
|
21
|
+
exp: number;
|
|
22
|
+
}
|
|
23
|
+
export declare class PeerAuthService {
|
|
24
|
+
private readonly cryptoAdapter;
|
|
25
|
+
/** Live view of `trusted-servers.json` — re-read on every handshake. */
|
|
26
|
+
private readonly listTrustedServers;
|
|
27
|
+
private challenges;
|
|
28
|
+
/** Challenge creation is unauthenticated, so the map is bounded (DoS guard). */
|
|
29
|
+
private static readonly MAX_CHALLENGES;
|
|
30
|
+
private readonly jwtSecret;
|
|
31
|
+
private readonly challengeExpirationMs;
|
|
32
|
+
private readonly tokenExpirationMs;
|
|
33
|
+
constructor(cryptoAdapter: CryptoAdapter,
|
|
34
|
+
/** Live view of `trusted-servers.json` — re-read on every handshake. */
|
|
35
|
+
listTrustedServers: () => TrustedServer[], options?: {
|
|
36
|
+
jwtSecret?: Uint8Array;
|
|
37
|
+
challengeExpirationMs?: number;
|
|
38
|
+
tokenExpirationMs?: number;
|
|
39
|
+
});
|
|
40
|
+
/** Resolve a trusted server by its signing key (the canonical identity). */
|
|
41
|
+
findTrustedServerByKey(publicsignkey: string): TrustedServer | null;
|
|
42
|
+
/**
|
|
43
|
+
* Issue a challenge for a peer identified by its signing key.
|
|
44
|
+
*
|
|
45
|
+
* @throws Error("Unknown peer") when the key is not in trusted-servers.json.
|
|
46
|
+
*/
|
|
47
|
+
generateChallenge(publicsignkey: string): Promise<string>;
|
|
48
|
+
authenticate(challenge: string, signature: Uint8Array): Promise<{
|
|
49
|
+
success: boolean;
|
|
50
|
+
token?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Validate a peer JWT. Returns null unless the token carries `peer: true`
|
|
55
|
+
* *and* the signing key is still trusted — revoking trust must cut off
|
|
56
|
+
* outstanding tokens, not just future handshakes.
|
|
57
|
+
*/
|
|
58
|
+
validateToken(token: string): Promise<PeerTokenPayload | null>;
|
|
59
|
+
private verifySignature;
|
|
60
|
+
private generateToken;
|
|
61
|
+
private cleanupExpiredChallenges;
|
|
62
|
+
private evictChallengesOverCap;
|
|
63
|
+
private base64UrlEncode;
|
|
64
|
+
private base64UrlDecode;
|
|
65
|
+
private uint8ArrayToBase64Url;
|
|
66
|
+
private base64UrlToUint8Array;
|
|
67
|
+
private base64ToUint8Array;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=PeerAuthService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PeerAuthService.d.ts","sourceRoot":"","sources":["../../../../src/node/server/peer/PeerAuthService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,IAAI,EAAE,IAAI,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAeD,qBAAa,eAAe;IAUxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IAXrC,OAAO,CAAC,UAAU,CAA2C;IAC7D,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAU;IAEhD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAGxB,aAAa,EAAE,aAAa;IAC7C,wEAAwE;IACvD,kBAAkB,EAAE,MAAM,aAAa,EAAE,EAC1D,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,UAAU,CAAC;QACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B;IAQH,4EAA4E;IAC5E,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IASnE;;;;OAIG;IACG,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBzD,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IA6BhE;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;YAsCtD,eAAe;YAkCf,aAAa;IAyB3B,OAAO,CAAC,wBAAwB;IAShC,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,kBAAkB;CAQ3B"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Peer (server-to-server) authentication for `/system/peer/*`.
|
|
4
|
+
*
|
|
5
|
+
* Same Ed25519 challenge-response as the tenant sync handshake, but the key
|
|
6
|
+
* lookup goes to `trusted-servers.json` instead of a tenant directory. The
|
|
7
|
+
* issued JWT is deliberately a **different token class** from the system-admin
|
|
8
|
+
* JWT: `/system/peer/*` accepts only peer tokens and `/system/cluster/*` only
|
|
9
|
+
* admin tokens, so a peer can never administer this node and an admin can never
|
|
10
|
+
* write as a peer.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.PeerAuthService = void 0;
|
|
14
|
+
const uuid_1 = require("uuid");
|
|
15
|
+
/** Whitespace-insensitive PEM comparison (JSON transport reflows line endings). */
|
|
16
|
+
function normalizePem(pem) {
|
|
17
|
+
return pem.replace(/\s+/g, "");
|
|
18
|
+
}
|
|
19
|
+
class PeerAuthService {
|
|
20
|
+
constructor(cryptoAdapter,
|
|
21
|
+
/** Live view of `trusted-servers.json` — re-read on every handshake. */
|
|
22
|
+
listTrustedServers, options) {
|
|
23
|
+
this.cryptoAdapter = cryptoAdapter;
|
|
24
|
+
this.listTrustedServers = listTrustedServers;
|
|
25
|
+
this.challenges = new Map();
|
|
26
|
+
this.jwtSecret =
|
|
27
|
+
options?.jwtSecret ?? cryptoAdapter.getRandomValues(new Uint8Array(32));
|
|
28
|
+
this.challengeExpirationMs = options?.challengeExpirationMs ?? 5 * 60 * 1000;
|
|
29
|
+
this.tokenExpirationMs = options?.tokenExpirationMs ?? 60 * 60 * 1000;
|
|
30
|
+
}
|
|
31
|
+
/** Resolve a trusted server by its signing key (the canonical identity). */
|
|
32
|
+
findTrustedServerByKey(publicsignkey) {
|
|
33
|
+
const wanted = normalizePem(publicsignkey);
|
|
34
|
+
return (this.listTrustedServers().find((server) => normalizePem(server.signingPublicKey) === wanted) ?? null);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Issue a challenge for a peer identified by its signing key.
|
|
38
|
+
*
|
|
39
|
+
* @throws Error("Unknown peer") when the key is not in trusted-servers.json.
|
|
40
|
+
*/
|
|
41
|
+
async generateChallenge(publicsignkey) {
|
|
42
|
+
const server = this.findTrustedServerByKey(publicsignkey);
|
|
43
|
+
if (!server) {
|
|
44
|
+
throw new Error("Unknown peer");
|
|
45
|
+
}
|
|
46
|
+
const challenge = (0, uuid_1.v7)();
|
|
47
|
+
this.challenges.set(challenge, {
|
|
48
|
+
challenge,
|
|
49
|
+
serverName: server.name,
|
|
50
|
+
publicsignkey: server.signingPublicKey,
|
|
51
|
+
expiresAt: Date.now() + this.challengeExpirationMs,
|
|
52
|
+
used: false,
|
|
53
|
+
});
|
|
54
|
+
this.cleanupExpiredChallenges();
|
|
55
|
+
this.evictChallengesOverCap();
|
|
56
|
+
return challenge;
|
|
57
|
+
}
|
|
58
|
+
async authenticate(challenge, signature) {
|
|
59
|
+
const stored = this.challenges.get(challenge);
|
|
60
|
+
if (!stored) {
|
|
61
|
+
return { success: false, error: "Challenge not found or expired" };
|
|
62
|
+
}
|
|
63
|
+
if (Date.now() > stored.expiresAt) {
|
|
64
|
+
this.challenges.delete(challenge);
|
|
65
|
+
return { success: false, error: "Challenge expired" };
|
|
66
|
+
}
|
|
67
|
+
if (stored.used) {
|
|
68
|
+
return { success: false, error: "Challenge already used" };
|
|
69
|
+
}
|
|
70
|
+
stored.used = true;
|
|
71
|
+
// Re-resolve against the live trust list: an operator may have removed the
|
|
72
|
+
// peer between challenge and response.
|
|
73
|
+
if (!this.findTrustedServerByKey(stored.publicsignkey)) {
|
|
74
|
+
return { success: false, error: "Peer is no longer trusted" };
|
|
75
|
+
}
|
|
76
|
+
const valid = await this.verifySignature(challenge, signature, stored.publicsignkey);
|
|
77
|
+
if (!valid) {
|
|
78
|
+
return { success: false, error: "Invalid signature" };
|
|
79
|
+
}
|
|
80
|
+
const token = await this.generateToken(stored.serverName, stored.publicsignkey);
|
|
81
|
+
return { success: true, token };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validate a peer JWT. Returns null unless the token carries `peer: true`
|
|
85
|
+
* *and* the signing key is still trusted — revoking trust must cut off
|
|
86
|
+
* outstanding tokens, not just future handshakes.
|
|
87
|
+
*/
|
|
88
|
+
async validateToken(token) {
|
|
89
|
+
const parts = token.split(".");
|
|
90
|
+
if (parts.length !== 3)
|
|
91
|
+
return null;
|
|
92
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
93
|
+
try {
|
|
94
|
+
// Pin the algorithm to HS256 to block algorithm-confusion attacks.
|
|
95
|
+
const header = JSON.parse(this.base64UrlDecode(headerB64));
|
|
96
|
+
if (header.alg !== "HS256")
|
|
97
|
+
return null;
|
|
98
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
99
|
+
const signingKey = await subtle.importKey("raw", this.jwtSecret.buffer, { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
|
|
100
|
+
const dataToVerify = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
101
|
+
const sig = this.base64UrlToUint8Array(signatureB64);
|
|
102
|
+
const isValid = await subtle.verify("HMAC", signingKey, sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength), dataToVerify.buffer);
|
|
103
|
+
if (!isValid)
|
|
104
|
+
return null;
|
|
105
|
+
const payload = JSON.parse(this.base64UrlDecode(payloadB64));
|
|
106
|
+
if (payload.peer !== true)
|
|
107
|
+
return null;
|
|
108
|
+
if (payload.exp < Math.floor(Date.now() / 1000))
|
|
109
|
+
return null;
|
|
110
|
+
if (!this.findTrustedServerByKey(payload.publicsignkey))
|
|
111
|
+
return null;
|
|
112
|
+
return payload;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async verifySignature(message, signature, publicKeyPem) {
|
|
119
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
120
|
+
try {
|
|
121
|
+
const pemContents = publicKeyPem
|
|
122
|
+
.replace(/-----BEGIN PUBLIC KEY-----/g, "")
|
|
123
|
+
.replace(/-----END PUBLIC KEY-----/g, "")
|
|
124
|
+
.replace(/\s/g, "");
|
|
125
|
+
const keyData = this.base64ToUint8Array(pemContents);
|
|
126
|
+
const publicKey = await subtle.importKey("spki", keyData.buffer, { name: "Ed25519" }, false, ["verify"]);
|
|
127
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
128
|
+
return await subtle.verify({ name: "Ed25519" }, publicKey, signature.buffer.slice(signature.byteOffset, signature.byteOffset + signature.byteLength), messageBytes.buffer);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async generateToken(serverName, publicsignkey) {
|
|
135
|
+
const subtle = this.cryptoAdapter.getSubtle();
|
|
136
|
+
const now = Math.floor(Date.now() / 1000);
|
|
137
|
+
const payload = {
|
|
138
|
+
sub: serverName,
|
|
139
|
+
publicsignkey,
|
|
140
|
+
peer: true,
|
|
141
|
+
iat: now,
|
|
142
|
+
exp: now + Math.floor(this.tokenExpirationMs / 1000),
|
|
143
|
+
};
|
|
144
|
+
const headerB64 = this.base64UrlEncode(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
145
|
+
const payloadB64 = this.base64UrlEncode(JSON.stringify(payload));
|
|
146
|
+
const signingKey = await subtle.importKey("raw", this.jwtSecret.buffer, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
147
|
+
const dataToSign = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
148
|
+
const signature = await subtle.sign("HMAC", signingKey, dataToSign.buffer);
|
|
149
|
+
return `${headerB64}.${payloadB64}.${this.uint8ArrayToBase64Url(new Uint8Array(signature))}`;
|
|
150
|
+
}
|
|
151
|
+
cleanupExpiredChallenges() {
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
for (const [key, challenge] of this.challenges) {
|
|
154
|
+
if (challenge.expiresAt < now) {
|
|
155
|
+
this.challenges.delete(key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
evictChallengesOverCap() {
|
|
160
|
+
let overflow = this.challenges.size - PeerAuthService.MAX_CHALLENGES;
|
|
161
|
+
if (overflow <= 0)
|
|
162
|
+
return;
|
|
163
|
+
for (const key of this.challenges.keys()) {
|
|
164
|
+
if (overflow-- <= 0)
|
|
165
|
+
break;
|
|
166
|
+
this.challenges.delete(key);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
base64UrlEncode(str) {
|
|
170
|
+
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
171
|
+
}
|
|
172
|
+
base64UrlDecode(str) {
|
|
173
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
174
|
+
while (base64.length % 4)
|
|
175
|
+
base64 += "=";
|
|
176
|
+
return atob(base64);
|
|
177
|
+
}
|
|
178
|
+
uint8ArrayToBase64Url(bytes) {
|
|
179
|
+
let binary = "";
|
|
180
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
181
|
+
binary += String.fromCharCode(bytes[i]);
|
|
182
|
+
}
|
|
183
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
184
|
+
}
|
|
185
|
+
base64UrlToUint8Array(str) {
|
|
186
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
187
|
+
while (base64.length % 4)
|
|
188
|
+
base64 += "=";
|
|
189
|
+
const binary = atob(base64);
|
|
190
|
+
const bytes = new Uint8Array(binary.length);
|
|
191
|
+
for (let i = 0; i < binary.length; i++) {
|
|
192
|
+
bytes[i] = binary.charCodeAt(i);
|
|
193
|
+
}
|
|
194
|
+
return bytes;
|
|
195
|
+
}
|
|
196
|
+
base64ToUint8Array(base64) {
|
|
197
|
+
const binary = atob(base64);
|
|
198
|
+
const bytes = new Uint8Array(binary.length);
|
|
199
|
+
for (let i = 0; i < binary.length; i++) {
|
|
200
|
+
bytes[i] = binary.charCodeAt(i);
|
|
201
|
+
}
|
|
202
|
+
return bytes;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.PeerAuthService = PeerAuthService;
|
|
206
|
+
/** Challenge creation is unauthenticated, so the map is bounded (DoS guard). */
|
|
207
|
+
PeerAuthService.MAX_CHALLENGES = 10000;
|
|
208
|
+
//# sourceMappingURL=PeerAuthService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PeerAuthService.js","sourceRoot":"","sources":["../../../../src/node/server/peer/PeerAuthService.ts"],"names":[],"mappings":"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"}
|