@peerbit/server 8.0.33 → 8.0.35
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/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +26 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -7
- package/src/cli.ts +28 -8
- package/dist/test/api.spec.d.ts +0 -2
- package/dist/test/api.spec.d.ts.map +0 -1
- package/dist/test/api.spec.js +0 -667
- package/dist/test/api.spec.js.map +0 -1
- package/dist/test/cli.spec.d.ts +0 -2
- package/dist/test/cli.spec.d.ts.map +0 -1
- package/dist/test/cli.spec.js +0 -406
- package/dist/test/cli.spec.js.map +0 -1
- package/dist/test/client.spec.d.ts +0 -2
- package/dist/test/client.spec.d.ts.map +0 -1
- package/dist/test/client.spec.js +0 -23
- package/dist/test/client.spec.js.map +0 -1
- package/dist/test/config.spec.d.ts +0 -2
- package/dist/test/config.spec.d.ts.map +0 -1
- package/dist/test/config.spec.js +0 -10
- package/dist/test/config.spec.js.map +0 -1
- package/dist/test/docker.spec.d.ts +0 -2
- package/dist/test/docker.spec.d.ts.map +0 -1
- package/dist/test/docker.spec.js +0 -822
- package/dist/test/docker.spec.js.map +0 -1
- package/dist/test/domain-lease.spec.d.ts +0 -2
- package/dist/test/domain-lease.spec.d.ts.map +0 -1
- package/dist/test/domain-lease.spec.js +0 -530
- package/dist/test/domain-lease.spec.js.map +0 -1
- package/dist/test/domain.integration.spec.d.ts +0 -2
- package/dist/test/domain.integration.spec.d.ts.map +0 -1
- package/dist/test/domain.integration.spec.js +0 -386
- package/dist/test/domain.integration.spec.js.map +0 -1
- package/dist/test/remotes.spec.d.ts +0 -2
- package/dist/test/remotes.spec.d.ts.map +0 -1
- package/dist/test/remotes.spec.js +0 -79
- package/dist/test/remotes.spec.js.map +0 -1
- package/dist/test/signed-request.spec.d.ts +0 -2
- package/dist/test/signed-request.spec.d.ts.map +0 -1
- package/dist/test/signed-request.spec.js +0 -216
- package/dist/test/signed-request.spec.js.map +0 -1
- package/dist/test/trust.spec.d.ts +0 -2
- package/dist/test/trust.spec.d.ts.map +0 -1
- package/dist/test/trust.spec.js +0 -29
- package/dist/test/trust.spec.js.map +0 -1
- package/dist/test/utils.d.ts +0 -3
- package/dist/test/utils.d.ts.map +0 -1
- package/dist/test/utils.js +0 -6
- package/dist/test/utils.js.map +0 -1
package/dist/test/api.spec.js
DELETED
|
@@ -1,667 +0,0 @@
|
|
|
1
|
-
import { getSchema, serialize } from "@dao-xyz/borsh";
|
|
2
|
-
import { tcp } from "@libp2p/tcp";
|
|
3
|
-
import { webSockets } from "@libp2p/websockets";
|
|
4
|
-
import { Ed25519Keypair, toBase64, } from "@peerbit/crypto";
|
|
5
|
-
import { PermissionedString } from "@peerbit/test-lib";
|
|
6
|
-
import { TestSession } from "@peerbit/test-utils";
|
|
7
|
-
import { waitForResolved } from "@peerbit/time";
|
|
8
|
-
import { expect } from "chai";
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import http from "http";
|
|
11
|
-
import { Level } from "level";
|
|
12
|
-
import { MemoryLevel } from "memory-level";
|
|
13
|
-
import path, { dirname } from "path";
|
|
14
|
-
import sinon from "sinon";
|
|
15
|
-
import { fileURLToPath } from "url";
|
|
16
|
-
import { v4 as uuid } from "uuid";
|
|
17
|
-
import { createClient } from "../src/client.js";
|
|
18
|
-
import { getTrustPath } from "../src/config.js";
|
|
19
|
-
import { AUTH_PATH, PEER_ID_PATH, PROGRAMS_PATH, PROGRAM_PATH, } from "../src/routes.js";
|
|
20
|
-
import { startApiServer, startServerWithNode } from "../src/server.js";
|
|
21
|
-
import { Session } from "../src/session.js";
|
|
22
|
-
import { RequestAuthenticator, createAuthDescriptor, getBody, signRequest, verifyAuthDescriptor, verifyRequestBody, } from "../src/signed-request.js";
|
|
23
|
-
import { Trust } from "../src/trust.js";
|
|
24
|
-
const client = (keypair, address) => {
|
|
25
|
-
return createClient(keypair, address
|
|
26
|
-
? { address, peerId: keypair.publicKey.toPeerId().toString() }
|
|
27
|
-
: undefined);
|
|
28
|
-
};
|
|
29
|
-
describe("libp2p only", () => {
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
31
|
-
let session, server;
|
|
32
|
-
let configDirectory;
|
|
33
|
-
before(async () => {
|
|
34
|
-
session = await TestSession.connected(1);
|
|
35
|
-
});
|
|
36
|
-
beforeEach(async () => {
|
|
37
|
-
session.peers[0].services.pubsub.subscribe("1");
|
|
38
|
-
session.peers[0].services.pubsub.subscribe("2");
|
|
39
|
-
session.peers[0].services.pubsub.subscribe("3");
|
|
40
|
-
const dirnameResolved = dirname(fileURLToPath(import.meta.url));
|
|
41
|
-
configDirectory = path.join(dirnameResolved, "tmp", "api-test", "libp2ponly", uuid());
|
|
42
|
-
fs.mkdirSync(configDirectory, { recursive: true });
|
|
43
|
-
server = await startApiServer(session.peers[0], {
|
|
44
|
-
trust: new Trust(getTrustPath(configDirectory)),
|
|
45
|
-
port: 7676,
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
server.close();
|
|
50
|
-
});
|
|
51
|
-
after(async () => {
|
|
52
|
-
await session.stop();
|
|
53
|
-
});
|
|
54
|
-
it("use cli as libp2p cli", async () => {
|
|
55
|
-
const c = await createClient(await Ed25519Keypair.create(), {
|
|
56
|
-
address: "http://localhost:" + 7676,
|
|
57
|
-
});
|
|
58
|
-
expect(await c.peer.id.get()).to.exist;
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
describe("server", () => {
|
|
62
|
-
describe("with node", () => {
|
|
63
|
-
let server;
|
|
64
|
-
let node;
|
|
65
|
-
afterEach(async () => {
|
|
66
|
-
// @ts-ignore
|
|
67
|
-
await node?.stop();
|
|
68
|
-
// @ts-ignore
|
|
69
|
-
server?.close();
|
|
70
|
-
});
|
|
71
|
-
it("bootstrap on start", async () => {
|
|
72
|
-
// TMP disable until bootstrap nodes have migrated
|
|
73
|
-
/* let result = await startServerWithNode({
|
|
74
|
-
bootstrap: true,
|
|
75
|
-
directory: path.join(__dirname, "tmp", "api-test", "server", uuid())
|
|
76
|
-
});
|
|
77
|
-
node = result.node;
|
|
78
|
-
server = result.server;
|
|
79
|
-
expect(node.libp2p.services.pubsub.peers.size).greaterThan(0); */
|
|
80
|
-
});
|
|
81
|
-
it("continues startup when a saved dependency is missing", async () => {
|
|
82
|
-
const dirnameResolved = dirname(fileURLToPath(import.meta.url));
|
|
83
|
-
const directory = path.join(dirnameResolved, "tmp", "api-test", "server", "missing-import", uuid());
|
|
84
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
85
|
-
// Seed a persisted session import that can't be resolved.
|
|
86
|
-
const sessionDirectory = path.join(directory, "session");
|
|
87
|
-
const level = new Level(sessionDirectory, {
|
|
88
|
-
valueEncoding: "view",
|
|
89
|
-
keyEncoding: "utf-8",
|
|
90
|
-
});
|
|
91
|
-
const imports = level.sublevel("imports", {
|
|
92
|
-
keyEncoding: "utf8",
|
|
93
|
-
valueEncoding: "view",
|
|
94
|
-
});
|
|
95
|
-
await imports.put("definitely-not-a-real-peerbit-package-123", new Uint8Array());
|
|
96
|
-
await level.close();
|
|
97
|
-
const errorStub = sinon.stub(console, "error");
|
|
98
|
-
try {
|
|
99
|
-
const result = await startServerWithNode({
|
|
100
|
-
directory,
|
|
101
|
-
ports: { api: 0, node: 0 },
|
|
102
|
-
});
|
|
103
|
-
node = result.node;
|
|
104
|
-
server = result.server;
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
errorStub.restore();
|
|
108
|
-
}
|
|
109
|
-
expect(errorStub.calledWithMatch(/Failed to import dependency/)).to.equal(true);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
describe("api", () => {
|
|
113
|
-
let session, serverPeer, server;
|
|
114
|
-
let db;
|
|
115
|
-
let apiAddress;
|
|
116
|
-
let directory;
|
|
117
|
-
before(async () => { });
|
|
118
|
-
beforeEach(async () => {
|
|
119
|
-
const dirnameResolved = dirname(fileURLToPath(import.meta.url));
|
|
120
|
-
directory = path.join(dirnameResolved, "tmp", "api-test", "api", uuid());
|
|
121
|
-
session = await TestSession.disconnected(2, {
|
|
122
|
-
libp2p: { transports: [tcp(), webSockets()] },
|
|
123
|
-
});
|
|
124
|
-
serverPeer = session.peers[0];
|
|
125
|
-
db = await serverPeer.open(new PermissionedString({ trusted: [] }));
|
|
126
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
127
|
-
server = await startApiServer(serverPeer, {
|
|
128
|
-
trust: new Trust(getTrustPath(directory)),
|
|
129
|
-
session: new Session(new MemoryLevel({
|
|
130
|
-
valueEncoding: "view",
|
|
131
|
-
keyEncoding: "utf-8",
|
|
132
|
-
})),
|
|
133
|
-
port: 0,
|
|
134
|
-
});
|
|
135
|
-
const addr = server.address();
|
|
136
|
-
if (!addr || typeof addr === "string") {
|
|
137
|
-
throw new Error("Failed to resolve API server address");
|
|
138
|
-
}
|
|
139
|
-
apiAddress = `http://localhost:${addr.port}`;
|
|
140
|
-
});
|
|
141
|
-
afterEach(async () => {
|
|
142
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
143
|
-
await db.close();
|
|
144
|
-
await session.stop();
|
|
145
|
-
});
|
|
146
|
-
describe("client", () => {
|
|
147
|
-
it("id", async () => {
|
|
148
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
149
|
-
expect(await c.peer.id.get()).equal(serverPeer.peerId.toString());
|
|
150
|
-
});
|
|
151
|
-
it("addresses", async () => {
|
|
152
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
153
|
-
expect((await c.peer.addresses.get()).map((x) => x.toString())).to.deep.equal((await serverPeer.getMultiaddrs()).map((x) => x.toString()));
|
|
154
|
-
});
|
|
155
|
-
it("requires an exact public route", async () => {
|
|
156
|
-
expect((await fetch(apiAddress + PEER_ID_PATH)).status).to.equal(200);
|
|
157
|
-
expect((await fetch(apiAddress + PEER_ID_PATH + "?extra=1")).status).to.equal(401);
|
|
158
|
-
expect((await fetch(apiAddress + PEER_ID_PATH + "-extra")).status).to.equal(401);
|
|
159
|
-
});
|
|
160
|
-
it("closes unauthorized connections and rejects bodies on public GETs", async () => {
|
|
161
|
-
const unauthorized = await fetch(apiAddress + PROGRAMS_PATH);
|
|
162
|
-
expect(unauthorized.status).to.equal(401);
|
|
163
|
-
expect(unauthorized.headers.get("connection")).to.equal("close");
|
|
164
|
-
const publicWithBody = await new Promise((resolve, reject) => {
|
|
165
|
-
const request = http.request(apiAddress + PEER_ID_PATH, {
|
|
166
|
-
method: "GET",
|
|
167
|
-
headers: { "Content-Length": "1" },
|
|
168
|
-
}, (response) => {
|
|
169
|
-
let body = "";
|
|
170
|
-
response.setEncoding("utf8");
|
|
171
|
-
response.on("data", (chunk) => (body += chunk));
|
|
172
|
-
response.on("end", () => resolve({
|
|
173
|
-
status: response.statusCode,
|
|
174
|
-
connection: response.headers.connection,
|
|
175
|
-
body,
|
|
176
|
-
}));
|
|
177
|
-
});
|
|
178
|
-
request.once("error", reject);
|
|
179
|
-
request.end("x");
|
|
180
|
-
});
|
|
181
|
-
expect(publicWithBody).to.deep.equal({
|
|
182
|
-
status: 400,
|
|
183
|
-
connection: "close",
|
|
184
|
-
body: "Public GET endpoints do not accept a request body",
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
it("requires the client to pin the server identity", async () => {
|
|
188
|
-
const unpinned = await createClient(session.peers[0].identity, {
|
|
189
|
-
address: apiAddress,
|
|
190
|
-
});
|
|
191
|
-
await expect(unpinned.program.list()).rejectedWith("pinned server peer ID");
|
|
192
|
-
const other = await Ed25519Keypair.create();
|
|
193
|
-
const wronglyPinned = await createClient(session.peers[0].identity, {
|
|
194
|
-
address: apiAddress,
|
|
195
|
-
peerId: other.publicKey.toPeerId().toString(),
|
|
196
|
-
});
|
|
197
|
-
await expect(wronglyPinned.program.list()).rejectedWith("identity does not match");
|
|
198
|
-
});
|
|
199
|
-
it("accepts exactly one concurrent copy of a signed request", async () => {
|
|
200
|
-
const descriptorResponse = await fetch(apiAddress + AUTH_PATH);
|
|
201
|
-
expect(descriptorResponse.headers.get("cache-control")).to.equal("no-store");
|
|
202
|
-
const descriptor = (await descriptorResponse.json());
|
|
203
|
-
const headers = {};
|
|
204
|
-
await signRequest(headers, "GET", PROGRAMS_PATH, undefined, session.peers[0].identity, descriptor);
|
|
205
|
-
const responses = await Promise.all([
|
|
206
|
-
fetch(apiAddress + PROGRAMS_PATH, { headers }),
|
|
207
|
-
fetch(apiAddress + PROGRAMS_PATH, { headers }),
|
|
208
|
-
]);
|
|
209
|
-
expect(responses.map((response) => response.status).sort()).to.deep.equal([200, 401]);
|
|
210
|
-
});
|
|
211
|
-
it("does not let a mismatched body burn a valid nonce", async () => {
|
|
212
|
-
const descriptor = (await (await fetch(apiAddress + AUTH_PATH)).json());
|
|
213
|
-
const target = PROGRAM_PATH + "/missing";
|
|
214
|
-
const headers = {};
|
|
215
|
-
await signRequest(headers, "DELETE", target, "right", session.peers[0].identity, descriptor);
|
|
216
|
-
const raced = await fetch(apiAddress + target, {
|
|
217
|
-
method: "DELETE",
|
|
218
|
-
headers,
|
|
219
|
-
body: "wrong",
|
|
220
|
-
});
|
|
221
|
-
expect(raced.status).to.equal(401);
|
|
222
|
-
const legitimate = await fetch(apiAddress + target, {
|
|
223
|
-
method: "DELETE",
|
|
224
|
-
headers,
|
|
225
|
-
body: "right",
|
|
226
|
-
});
|
|
227
|
-
expect(legitimate.status).to.equal(404);
|
|
228
|
-
});
|
|
229
|
-
it("single-flights descriptor discovery for concurrent requests", async () => {
|
|
230
|
-
let descriptorRequests = 0;
|
|
231
|
-
const observeDescriptor = (request) => {
|
|
232
|
-
if (request.method === "GET" && request.url === AUTH_PATH) {
|
|
233
|
-
descriptorRequests += 1;
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
server.prependListener("request", observeDescriptor);
|
|
237
|
-
try {
|
|
238
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
239
|
-
await Promise.all([c.program.list(), c.peer.stats.get()]);
|
|
240
|
-
expect(descriptorRequests).to.equal(1);
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
server.removeListener("request", observeDescriptor);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
it("briefly caches and then refreshes the signed descriptor", async () => {
|
|
247
|
-
let wallClockMs = 1_750_000_000_000;
|
|
248
|
-
const timedDirectory = path.join(process.cwd(), "tmp", "api-test", "timed-auth", uuid());
|
|
249
|
-
fs.mkdirSync(timedDirectory, { recursive: true });
|
|
250
|
-
const timed = await startApiServer(serverPeer, {
|
|
251
|
-
trust: new Trust(getTrustPath(timedDirectory)),
|
|
252
|
-
port: 0,
|
|
253
|
-
signedRequests: { wallClockMs: () => wallClockMs },
|
|
254
|
-
});
|
|
255
|
-
try {
|
|
256
|
-
const address = timed.address();
|
|
257
|
-
if (!address || typeof address === "string") {
|
|
258
|
-
throw new Error("Failed to resolve timed API server address");
|
|
259
|
-
}
|
|
260
|
-
const endpoint = `http://127.0.0.1:${address.port}${AUTH_PATH}`;
|
|
261
|
-
const first = (await (await fetch(endpoint)).json());
|
|
262
|
-
wallClockMs += 1_000;
|
|
263
|
-
const second = (await (await fetch(endpoint)).json());
|
|
264
|
-
wallClockMs += 30_000;
|
|
265
|
-
const third = (await (await fetch(endpoint)).json());
|
|
266
|
-
expect(first.serverTime).to.equal("1750000000");
|
|
267
|
-
expect(second).to.deep.equal(first);
|
|
268
|
-
expect(third.serverTime).to.equal("1750000031");
|
|
269
|
-
expect(third.bootId).to.equal(first.bootId);
|
|
270
|
-
await verifyAuthDescriptor(third, serverPeer.peerId.toString(), {
|
|
271
|
-
nowMs: wallClockMs,
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
finally {
|
|
275
|
-
await new Promise((resolve) => timed.close(() => resolve()));
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
it("does not forward signed requests across redirects", async () => {
|
|
279
|
-
const descriptor = await (await fetch(apiAddress + AUTH_PATH)).text();
|
|
280
|
-
let sinkRequests = 0;
|
|
281
|
-
const sink = http.createServer((request, response) => {
|
|
282
|
-
sinkRequests += 1;
|
|
283
|
-
request.resume();
|
|
284
|
-
response.writeHead(200);
|
|
285
|
-
response.end("captured");
|
|
286
|
-
});
|
|
287
|
-
const listen = (target) => new Promise((resolve, reject) => {
|
|
288
|
-
target.once("error", reject);
|
|
289
|
-
target.listen(0, "127.0.0.1", () => {
|
|
290
|
-
const address = target.address();
|
|
291
|
-
if (!address || typeof address === "string") {
|
|
292
|
-
reject(new Error("Failed to resolve test server address"));
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
resolve(address.port);
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
const close = (target) => new Promise((resolve) => target.close(() => resolve()));
|
|
299
|
-
let proxy;
|
|
300
|
-
try {
|
|
301
|
-
const sinkPort = await listen(sink);
|
|
302
|
-
proxy = http.createServer((request, response) => {
|
|
303
|
-
request.resume();
|
|
304
|
-
if (request.method === "GET" && request.url === AUTH_PATH) {
|
|
305
|
-
response.setHeader("Content-Type", "application/json");
|
|
306
|
-
response.end(descriptor);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
response.writeHead(307, {
|
|
310
|
-
Location: `http://127.0.0.1:${sinkPort}/captured`,
|
|
311
|
-
});
|
|
312
|
-
response.end();
|
|
313
|
-
});
|
|
314
|
-
const proxyPort = await listen(proxy);
|
|
315
|
-
const c = await createClient(session.peers[0].identity, {
|
|
316
|
-
address: `http://127.0.0.1:${proxyPort}`,
|
|
317
|
-
peerId: serverPeer.peerId.toString(),
|
|
318
|
-
});
|
|
319
|
-
await expect(c.program.list()).rejectedWith("status code 307");
|
|
320
|
-
expect(sinkRequests).to.equal(0);
|
|
321
|
-
}
|
|
322
|
-
finally {
|
|
323
|
-
if (proxy?.listening)
|
|
324
|
-
await close(proxy);
|
|
325
|
-
if (sink.listening)
|
|
326
|
-
await close(sink);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
it("sends exactly an offset Uint8Array view over Axios HTTP", async () => {
|
|
330
|
-
const descriptor = await createAuthDescriptor(serverPeer.identity, {
|
|
331
|
-
serverPeerId: serverPeer.peerId.toString(),
|
|
332
|
-
bootId: toBase64(new Uint8Array(32).fill(9)),
|
|
333
|
-
});
|
|
334
|
-
const authenticator = new RequestAuthenticator({
|
|
335
|
-
...descriptor,
|
|
336
|
-
isTrusted: (key) => key.equals(serverPeer.identity.publicKey),
|
|
337
|
-
});
|
|
338
|
-
let received;
|
|
339
|
-
const wireServer = http.createServer(async (request, response) => {
|
|
340
|
-
if (request.method === "GET" && request.url === AUTH_PATH) {
|
|
341
|
-
response.setHeader("Content-Type", "application/json");
|
|
342
|
-
response.end(JSON.stringify(descriptor));
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
try {
|
|
346
|
-
const verified = await authenticator.verify(request.headers, request.method, request.url);
|
|
347
|
-
received = await getBody(request, verified.bodyLength);
|
|
348
|
-
verifyRequestBody(verified, received);
|
|
349
|
-
authenticator.consume(verified);
|
|
350
|
-
response.writeHead(200);
|
|
351
|
-
response.end();
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
response.writeHead(500);
|
|
355
|
-
response.end(error?.toString());
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
await new Promise((resolve, reject) => {
|
|
359
|
-
wireServer.once("error", reject);
|
|
360
|
-
wireServer.listen(0, "127.0.0.1", () => resolve());
|
|
361
|
-
});
|
|
362
|
-
try {
|
|
363
|
-
const address = wireServer.address();
|
|
364
|
-
if (!address || typeof address === "string") {
|
|
365
|
-
throw new Error("Failed to resolve wire server address");
|
|
366
|
-
}
|
|
367
|
-
const { default: axios } = await import("axios");
|
|
368
|
-
const originalCreate = axios.create.bind(axios);
|
|
369
|
-
const instances = [];
|
|
370
|
-
const createStub = sinon.stub(axios, "create");
|
|
371
|
-
createStub.callsFake(((config) => {
|
|
372
|
-
const instance = originalCreate(config);
|
|
373
|
-
instances.push(instance);
|
|
374
|
-
return instance;
|
|
375
|
-
}));
|
|
376
|
-
try {
|
|
377
|
-
await createClient(serverPeer.identity, {
|
|
378
|
-
address: `http://127.0.0.1:${address.port}`,
|
|
379
|
-
peerId: serverPeer.peerId.toString(),
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
finally {
|
|
383
|
-
createStub.restore();
|
|
384
|
-
}
|
|
385
|
-
expect(instances).to.have.length(2);
|
|
386
|
-
const backing = new TextEncoder().encode("xxwire 🌍 bytesyy");
|
|
387
|
-
const view = backing.subarray(2, backing.length - 2);
|
|
388
|
-
await instances[0].put(`http://127.0.0.1:${address.port}/wire`, view);
|
|
389
|
-
expect(received).to.deep.equal(view);
|
|
390
|
-
}
|
|
391
|
-
finally {
|
|
392
|
-
await new Promise((resolve) => wireServer.close(() => resolve()));
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
it("does not verify an endpoint that only echoes the pinned ID", async () => {
|
|
396
|
-
const fake = http.createServer((request, response) => {
|
|
397
|
-
if (request.url === PEER_ID_PATH) {
|
|
398
|
-
response.end(serverPeer.peerId.toString());
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
response.setHeader("Content-Type", "application/json");
|
|
402
|
-
response.end(JSON.stringify({
|
|
403
|
-
version: "2",
|
|
404
|
-
serverPeerId: serverPeer.peerId.toString(),
|
|
405
|
-
bootId: toBase64(new Uint8Array(32).fill(1)),
|
|
406
|
-
serverTime: "1750000000",
|
|
407
|
-
signature: "invalid",
|
|
408
|
-
}));
|
|
409
|
-
});
|
|
410
|
-
await new Promise((resolve, reject) => {
|
|
411
|
-
fake.once("error", reject);
|
|
412
|
-
fake.listen(0, "127.0.0.1", () => resolve());
|
|
413
|
-
});
|
|
414
|
-
try {
|
|
415
|
-
const address = fake.address();
|
|
416
|
-
if (!address || typeof address === "string") {
|
|
417
|
-
throw new Error("Failed to resolve fake server address");
|
|
418
|
-
}
|
|
419
|
-
const c = await createClient(serverPeer.identity, {
|
|
420
|
-
address: `http://127.0.0.1:${address.port}`,
|
|
421
|
-
peerId: serverPeer.peerId.toString(),
|
|
422
|
-
});
|
|
423
|
-
expect(await c.peer.id.get()).to.equal(serverPeer.peerId.toString());
|
|
424
|
-
await expect(c.peer.id.verify()).rejectedWith("identity does not match");
|
|
425
|
-
}
|
|
426
|
-
finally {
|
|
427
|
-
await new Promise((resolve) => fake.close(() => resolve()));
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
it("refreshes a restarted server on the next call without retrying", async () => {
|
|
431
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
432
|
-
await c.program.list();
|
|
433
|
-
const firstAddress = server.address();
|
|
434
|
-
if (!firstAddress || typeof firstAddress === "string") {
|
|
435
|
-
throw new Error("Failed to resolve API server address");
|
|
436
|
-
}
|
|
437
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
438
|
-
const restartDirectory = path.join(process.cwd(), "tmp", "api-test", "restart", uuid());
|
|
439
|
-
fs.mkdirSync(restartDirectory, { recursive: true });
|
|
440
|
-
server = await startApiServer(serverPeer, {
|
|
441
|
-
trust: new Trust(getTrustPath(restartDirectory)),
|
|
442
|
-
port: firstAddress.port,
|
|
443
|
-
});
|
|
444
|
-
let descriptorRequests = 0;
|
|
445
|
-
let protectedRequests = 0;
|
|
446
|
-
const observeRequests = (request) => {
|
|
447
|
-
if (request.url === AUTH_PATH)
|
|
448
|
-
descriptorRequests += 1;
|
|
449
|
-
if (request.url === PROGRAMS_PATH)
|
|
450
|
-
protectedRequests += 1;
|
|
451
|
-
};
|
|
452
|
-
server.prependListener("request", observeRequests);
|
|
453
|
-
try {
|
|
454
|
-
await expect(c.program.list()).to.be.rejected;
|
|
455
|
-
const failedProtectedRequests = protectedRequests;
|
|
456
|
-
expect(failedProtectedRequests).to.be.at.most(1);
|
|
457
|
-
expect(descriptorRequests).to.equal(0);
|
|
458
|
-
await c.program.list();
|
|
459
|
-
expect(protectedRequests).to.equal(failedProtectedRequests + 1);
|
|
460
|
-
expect(descriptorRequests).to.equal(1);
|
|
461
|
-
}
|
|
462
|
-
finally {
|
|
463
|
-
server.removeListener("request", observeRequests);
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
it("uses a distinct boot audience for each API server", async () => {
|
|
467
|
-
const secondDirectory = path.join(process.cwd(), "tmp", "api-test", "second-api", uuid());
|
|
468
|
-
fs.mkdirSync(secondDirectory, { recursive: true });
|
|
469
|
-
const second = await startApiServer(serverPeer, {
|
|
470
|
-
trust: new Trust(getTrustPath(secondDirectory)),
|
|
471
|
-
port: 0,
|
|
472
|
-
});
|
|
473
|
-
try {
|
|
474
|
-
const secondAddress = second.address();
|
|
475
|
-
if (!secondAddress || typeof secondAddress === "string") {
|
|
476
|
-
throw new Error("Failed to resolve second API address");
|
|
477
|
-
}
|
|
478
|
-
const firstDescriptor = (await (await fetch(apiAddress + AUTH_PATH)).json());
|
|
479
|
-
const secondBase = `http://localhost:${secondAddress.port}`;
|
|
480
|
-
const secondDescriptor = (await (await fetch(secondBase + AUTH_PATH)).json());
|
|
481
|
-
expect(secondDescriptor.serverPeerId).to.equal(firstDescriptor.serverPeerId);
|
|
482
|
-
expect(secondDescriptor.bootId).not.to.equal(firstDescriptor.bootId);
|
|
483
|
-
const headers = {};
|
|
484
|
-
await signRequest(headers, "GET", PROGRAMS_PATH, undefined, session.peers[0].identity, firstDescriptor);
|
|
485
|
-
expect((await fetch(secondBase + PROGRAMS_PATH, { headers })).status).to.equal(401);
|
|
486
|
-
}
|
|
487
|
-
finally {
|
|
488
|
-
await new Promise((resolve) => second.close(() => resolve()));
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
describe("program", () => {
|
|
493
|
-
describe("open", () => {
|
|
494
|
-
it("variant", async () => {
|
|
495
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
496
|
-
const address = await c.program.open({
|
|
497
|
-
variant: getSchema(PermissionedString).variant,
|
|
498
|
-
});
|
|
499
|
-
expect(await c.program.has(address)).to.be.true;
|
|
500
|
-
});
|
|
501
|
-
it("base64", async () => {
|
|
502
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
503
|
-
const program = new PermissionedString({
|
|
504
|
-
trusted: [],
|
|
505
|
-
});
|
|
506
|
-
const address = await c.program.open({
|
|
507
|
-
base64: toBase64(serialize(program)),
|
|
508
|
-
});
|
|
509
|
-
expect(await c.program.has(address)).to.be.true;
|
|
510
|
-
});
|
|
511
|
-
it("with args", async () => {
|
|
512
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
513
|
-
const serverOpen = sinon.spy(serverPeer, "open");
|
|
514
|
-
serverPeer.open = serverOpen;
|
|
515
|
-
const address = await c.program.open({
|
|
516
|
-
variant: getSchema(PermissionedString).variant,
|
|
517
|
-
log: true,
|
|
518
|
-
});
|
|
519
|
-
expect(await c.program.has(address)).to.be.true;
|
|
520
|
-
expect(serverOpen.args[0][1]).to.deep.equal({ args: { log: true } });
|
|
521
|
-
});
|
|
522
|
-
});
|
|
523
|
-
describe("trust", () => {
|
|
524
|
-
it("add", async () => {
|
|
525
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
526
|
-
const kp2 = await Ed25519Keypair.create();
|
|
527
|
-
const kp3 = await Ed25519Keypair.create();
|
|
528
|
-
const c2 = await createClient(kp2, {
|
|
529
|
-
address: apiAddress,
|
|
530
|
-
peerId: serverPeer.peerId.toString(),
|
|
531
|
-
});
|
|
532
|
-
await expect(c2.access.allow(kp3.publicKey)).rejectedWith("Request failed with status code 401");
|
|
533
|
-
await c.access.allow(kp2.publicKey);
|
|
534
|
-
await c2.access.allow(kp3.publicKey); // now c2 can add since it is trusted by c
|
|
535
|
-
});
|
|
536
|
-
it("reports and persists access revocation", async () => {
|
|
537
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
538
|
-
const revoked = (await Ed25519Keypair.create()).publicKey;
|
|
539
|
-
const revokedHash = revoked.hashcode();
|
|
540
|
-
expect(await c.access.allow(revoked)).to.equal(true);
|
|
541
|
-
expect(new Trust(getTrustPath(directory)).isTrusted(revokedHash)).to.equal(true);
|
|
542
|
-
expect(await c.access.deny(revoked)).to.equal(true);
|
|
543
|
-
expect(await c.access.deny(revoked)).to.equal(false);
|
|
544
|
-
const reloaded = new Trust(getTrustPath(directory));
|
|
545
|
-
expect(reloaded.isTrusted(revokedHash)).to.equal(false);
|
|
546
|
-
});
|
|
547
|
-
});
|
|
548
|
-
describe("close/drop", () => {
|
|
549
|
-
let program;
|
|
550
|
-
let address;
|
|
551
|
-
let dropped = false;
|
|
552
|
-
let closed = false;
|
|
553
|
-
beforeEach(async () => {
|
|
554
|
-
dropped = false;
|
|
555
|
-
closed = false;
|
|
556
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
557
|
-
address = await c.program.open({
|
|
558
|
-
variant: getSchema(PermissionedString).variant,
|
|
559
|
-
});
|
|
560
|
-
program = session.peers[0].handler.items.get(address);
|
|
561
|
-
const dropFn = program.drop.bind(program);
|
|
562
|
-
program.drop = (from) => {
|
|
563
|
-
dropped = true;
|
|
564
|
-
return dropFn(from);
|
|
565
|
-
};
|
|
566
|
-
const closeFn = program.close.bind(program);
|
|
567
|
-
program.close = (from) => {
|
|
568
|
-
closed = true;
|
|
569
|
-
return closeFn(from);
|
|
570
|
-
};
|
|
571
|
-
});
|
|
572
|
-
it("close", async () => {
|
|
573
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
574
|
-
await c.program.close(address);
|
|
575
|
-
expect(dropped).to.be.false;
|
|
576
|
-
expect(closed).to.be.true;
|
|
577
|
-
});
|
|
578
|
-
it("drop", async () => {
|
|
579
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
580
|
-
await c.program.drop(address);
|
|
581
|
-
expect(dropped).to.be.true;
|
|
582
|
-
expect(closed).to.be.false;
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
it("list arg value", async () => {
|
|
586
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
587
|
-
const address = await c.program.open({
|
|
588
|
-
variant: getSchema(PermissionedString).variant,
|
|
589
|
-
log: true,
|
|
590
|
-
});
|
|
591
|
-
const map = await c.program.list();
|
|
592
|
-
expect(map[address]).to.deep.eq({ log: true });
|
|
593
|
-
});
|
|
594
|
-
it("list arg undefined", async () => {
|
|
595
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
596
|
-
const address = await c.program.open({
|
|
597
|
-
variant: getSchema(PermissionedString).variant,
|
|
598
|
-
});
|
|
599
|
-
const map = await c.program.list();
|
|
600
|
-
expect(map[address]).to.deep.eq(null);
|
|
601
|
-
});
|
|
602
|
-
/* TODO tet correctly
|
|
603
|
-
it("list args after restart", async () => {
|
|
604
|
-
const c = await client(session.peers[0].identity);
|
|
605
|
-
const address = await c.program.open({
|
|
606
|
-
variant: getSchema(PermissionedString).variant! as string,
|
|
607
|
-
log: true,
|
|
608
|
-
});
|
|
609
|
-
await c.restart();
|
|
610
|
-
await waitForResolved(async () => {
|
|
611
|
-
const map = await c.program.list();
|
|
612
|
-
expect(map[address]).to.deep.eq({ log: true });
|
|
613
|
-
}, {
|
|
614
|
-
delayInterval: 5e2,
|
|
615
|
-
})
|
|
616
|
-
}) */
|
|
617
|
-
});
|
|
618
|
-
it("bootstrap", async () => {
|
|
619
|
-
expect(session.peers[0].services.pubsub.peers.size).equal(0);
|
|
620
|
-
const c = await client(session.peers[0].identity, apiAddress);
|
|
621
|
-
const bootstrapAddresses = session.peers[1]
|
|
622
|
-
.getMultiaddrs()
|
|
623
|
-
.map((x) => x.toString());
|
|
624
|
-
await c.network.bootstrap({ addresses: bootstrapAddresses });
|
|
625
|
-
await waitForResolved(() => expect(session.peers[0].services.pubsub.peers.size).greaterThan(0));
|
|
626
|
-
});
|
|
627
|
-
/* TODO how to test this properly? Seems to hang once we added 'sudo --prefix __dirname' to the npm install in the child_process
|
|
628
|
-
it("dependency", async () => {
|
|
629
|
-
const c = await client();
|
|
630
|
-
const result = await c.dependency.install("@peerbit/test-lib");
|
|
631
|
-
expect(result).to.be.empty; // will already be imported in this test env. TODO make test better here, so that new programs are discvovered on import
|
|
632
|
-
}); */
|
|
633
|
-
});
|
|
634
|
-
/* TODO feat
|
|
635
|
-
|
|
636
|
-
it("topics", async () => {
|
|
637
|
-
const c = await client();
|
|
638
|
-
expect(await c.topics.get(true)).to.deep.equal([address.toString()]);
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
*/
|
|
643
|
-
/* TODO add network functionality
|
|
644
|
-
|
|
645
|
-
it("network", async () => {
|
|
646
|
-
const c = await client();
|
|
647
|
-
const program = new PermissionedString({
|
|
648
|
-
store: new DString({}),
|
|
649
|
-
trusted: [peer.identity.publicKey],
|
|
650
|
-
});
|
|
651
|
-
program.setupIndices();
|
|
652
|
-
const address = await c.program.put(program, "topic");
|
|
653
|
-
expect(await c.program.get(address)).to.be.instanceOf(PermissionedString);
|
|
654
|
-
expect(await c.network.peers.get(address)).to.be.empty;
|
|
655
|
-
const pk = (await Ed25519Keypair.create()).publicKey;
|
|
656
|
-
await c.network.peer.put(address, pk);
|
|
657
|
-
const peers = await c.network.peers.get(address);
|
|
658
|
-
expect(peers).to.have.length(1);
|
|
659
|
-
expect(
|
|
660
|
-
(peers?.[0] as IdentityRelation).from.equals(
|
|
661
|
-
peer.identity.publicKey
|
|
662
|
-
)
|
|
663
|
-
);
|
|
664
|
-
expect((peers?.[0] as IdentityRelation).to.equals(pk));
|
|
665
|
-
}); */
|
|
666
|
-
});
|
|
667
|
-
//# sourceMappingURL=api.spec.js.map
|