@peerbit/server 2.0.0 → 3.0.0
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/lib/esm/cli.js +67 -20
- package/lib/esm/cli.js.map +1 -1
- package/lib/esm/client.d.ts +7 -1
- package/lib/esm/client.js +48 -31
- package/lib/esm/client.js.map +1 -1
- package/lib/esm/config.d.ts +3 -3
- package/lib/esm/config.js +18 -16
- package/lib/esm/config.js.map +1 -1
- package/lib/esm/peerbit.d.ts +2 -0
- package/lib/esm/peerbit.js +1 -0
- package/lib/esm/peerbit.js.map +1 -1
- package/lib/esm/remotes.d.ts +1 -2
- package/lib/esm/remotes.js +1 -2
- package/lib/esm/remotes.js.map +1 -1
- package/lib/esm/routes.d.ts +1 -0
- package/lib/esm/routes.js +1 -0
- package/lib/esm/routes.js.map +1 -1
- package/lib/esm/server.d.ts +2 -6
- package/lib/esm/server.js +161 -176
- package/lib/esm/server.js.map +1 -1
- package/lib/esm/signes-request.d.ts +5 -0
- package/lib/esm/signes-request.js +54 -0
- package/lib/esm/signes-request.js.map +1 -0
- package/lib/esm/trust.browser.d.ts +0 -0
- package/lib/esm/trust.browser.js +3 -0
- package/lib/esm/trust.browser.js.map +1 -0
- package/lib/esm/trust.d.ts +9 -0
- package/lib/esm/trust.js +36 -0
- package/lib/esm/trust.js.map +1 -0
- package/lib/ui/assets/index-cac7195d.js +77 -0
- package/lib/ui/index.html +1 -1
- package/package.json +4 -4
- package/src/cli.ts +81 -25
- package/src/client.ts +77 -35
- package/src/config.ts +21 -23
- package/src/peerbit.ts +3 -0
- package/src/remotes.ts +1 -3
- package/src/routes.ts +1 -1
- package/src/server.ts +205 -241
- package/src/signes-request.ts +84 -0
- package/src/trust.browser.ts +1 -0
- package/src/trust.ts +39 -0
- package/lib/ui/assets/index-73eaa3bc.js +0 -53
package/src/server.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http from "http";
|
|
2
|
-
import { fromBase64, sha256Base64Sync } from "@peerbit/crypto";
|
|
2
|
+
import { Ed25519Keypair, fromBase64, sha256Base64Sync } from "@peerbit/crypto";
|
|
3
3
|
import { deserialize } from "@dao-xyz/borsh";
|
|
4
4
|
import {
|
|
5
5
|
Program,
|
|
@@ -10,13 +10,10 @@ import {
|
|
|
10
10
|
import { waitFor } from "@peerbit/time";
|
|
11
11
|
import { v4 as uuid } from "uuid";
|
|
12
12
|
import {
|
|
13
|
-
checkExistPath,
|
|
14
13
|
getHomeConfigDir,
|
|
15
|
-
getCredentialsPath,
|
|
16
|
-
getPackageName,
|
|
17
|
-
loadPassword,
|
|
18
|
-
NotFoundError,
|
|
19
14
|
getNodePath,
|
|
15
|
+
getKeypair,
|
|
16
|
+
getTrustPath,
|
|
20
17
|
} from "./config.js";
|
|
21
18
|
import { setMaxListeners } from "events";
|
|
22
19
|
import { create } from "./peerbit.js";
|
|
@@ -38,6 +35,7 @@ import {
|
|
|
38
35
|
PROGRAMS_PATH,
|
|
39
36
|
PROGRAM_PATH,
|
|
40
37
|
RESTART_PATH,
|
|
38
|
+
TRUST_PATH,
|
|
41
39
|
} from "./routes.js";
|
|
42
40
|
import { Session } from "./session.js";
|
|
43
41
|
import fs from "fs";
|
|
@@ -50,6 +48,9 @@ import { dirname } from "path";
|
|
|
50
48
|
import { fileURLToPath } from "url";
|
|
51
49
|
import { Level } from "level";
|
|
52
50
|
import { MemoryLevel } from "memory-level";
|
|
51
|
+
import { Trust } from "./trust.js";
|
|
52
|
+
import { getBody, verifyRequest } from "./signes-request.js";
|
|
53
|
+
import { cli } from "./cli.js";
|
|
53
54
|
|
|
54
55
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
55
56
|
|
|
@@ -62,62 +63,21 @@ export const stopAndWait = (server: http.Server) => {
|
|
|
62
63
|
return waitFor(() => closed);
|
|
63
64
|
};
|
|
64
65
|
|
|
65
|
-
export const createPassword = async (
|
|
66
|
-
configDirectory: string,
|
|
67
|
-
password?: string
|
|
68
|
-
): Promise<string> => {
|
|
69
|
-
const configDir = configDirectory ?? (await getHomeConfigDir());
|
|
70
|
-
const credentialsPath = await getCredentialsPath(configDir);
|
|
71
|
-
if (!password && (await checkExistPath(credentialsPath))) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
"Config path for credentials: " + credentialsPath + ", already exist"
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
console.log(`Creating config folder ${configDir}`);
|
|
77
|
-
|
|
78
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
79
|
-
await waitFor(() => fs.existsSync(configDir));
|
|
80
|
-
|
|
81
|
-
console.log(`Created config folder ${configDir}`);
|
|
82
|
-
|
|
83
|
-
password = password || uuid();
|
|
84
|
-
fs.writeFileSync(
|
|
85
|
-
credentialsPath,
|
|
86
|
-
JSON.stringify({ username: "admin", password })
|
|
87
|
-
);
|
|
88
|
-
console.log(`Created credentials at ${credentialsPath}`);
|
|
89
|
-
return password!;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export const loadOrCreatePassword = async (
|
|
93
|
-
configDirectory: string,
|
|
94
|
-
password?: string
|
|
95
|
-
): Promise<string> => {
|
|
96
|
-
if (!password) {
|
|
97
|
-
try {
|
|
98
|
-
return await loadPassword(configDirectory);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
if (error instanceof NotFoundError) {
|
|
101
|
-
return createPassword(configDirectory, password);
|
|
102
|
-
}
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
return createPassword(configDirectory, password);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
66
|
export const startServerWithNode = async (properties: {
|
|
110
|
-
directory
|
|
67
|
+
directory: string;
|
|
111
68
|
domain?: string;
|
|
112
69
|
bootstrap?: boolean;
|
|
113
70
|
newSession?: boolean;
|
|
114
|
-
password?: string;
|
|
115
71
|
ports?: {
|
|
116
72
|
node: number;
|
|
117
73
|
api: number;
|
|
118
74
|
};
|
|
119
75
|
restart?: () => void;
|
|
120
76
|
}) => {
|
|
77
|
+
if (!fs.existsSync(properties.directory)) {
|
|
78
|
+
fs.mkdirSync(properties.directory, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
const keypair = await getKeypair(properties.directory);
|
|
121
81
|
const peer = await create({
|
|
122
82
|
directory:
|
|
123
83
|
properties.directory != null
|
|
@@ -125,6 +85,7 @@ export const startServerWithNode = async (properties: {
|
|
|
125
85
|
: undefined,
|
|
126
86
|
domain: properties.domain,
|
|
127
87
|
listenPort: properties.ports?.node,
|
|
88
|
+
peerId: await keypair.toPeerId(),
|
|
128
89
|
});
|
|
129
90
|
|
|
130
91
|
if (properties.bootstrap) {
|
|
@@ -134,6 +95,7 @@ export const startServerWithNode = async (properties: {
|
|
|
134
95
|
properties.directory != null
|
|
135
96
|
? path.join(properties.directory, "session")
|
|
136
97
|
: undefined;
|
|
98
|
+
|
|
137
99
|
const session = new Session(
|
|
138
100
|
sessionDirectory
|
|
139
101
|
? new Level<string, Uint8Array>(sessionDirectory, {
|
|
@@ -165,7 +127,6 @@ export const startServerWithNode = async (properties: {
|
|
|
165
127
|
? path.join(properties.directory, "server")
|
|
166
128
|
: undefined || getHomeConfigDir(),
|
|
167
129
|
session,
|
|
168
|
-
password: properties.password,
|
|
169
130
|
});
|
|
170
131
|
const printNodeInfo = async () => {
|
|
171
132
|
console.log("Starting node with address(es): ");
|
|
@@ -206,11 +167,7 @@ export const startServerWithNode = async (properties: {
|
|
|
206
167
|
return { server, node: peer };
|
|
207
168
|
};
|
|
208
169
|
|
|
209
|
-
const
|
|
210
|
-
client: Peerbit,
|
|
211
|
-
req: http.IncomingMessage,
|
|
212
|
-
pathIndex: number
|
|
213
|
-
): Program | undefined => {
|
|
170
|
+
const getPathValue = (req: http.IncomingMessage, pathIndex: number): string => {
|
|
214
171
|
if (!req.url) {
|
|
215
172
|
throw new Error("Missing url");
|
|
216
173
|
}
|
|
@@ -222,7 +179,7 @@ const getProgramFromPath = (
|
|
|
222
179
|
throw new Error("Invalid path");
|
|
223
180
|
}
|
|
224
181
|
const address = decodeURIComponent(path[pathIndex]);
|
|
225
|
-
return
|
|
182
|
+
return address;
|
|
226
183
|
};
|
|
227
184
|
function findPeerbitProgramFolder(inputDirectory: string): string | null {
|
|
228
185
|
let currentDir = path.resolve(inputDirectory);
|
|
@@ -254,18 +211,18 @@ function findPeerbitProgramFolder(inputDirectory: string): string | null {
|
|
|
254
211
|
|
|
255
212
|
export const startApiServer = async (
|
|
256
213
|
client: ProgramClient,
|
|
257
|
-
|
|
214
|
+
properties: {
|
|
258
215
|
configDirectory: string;
|
|
259
216
|
session?: Session;
|
|
260
217
|
port?: number;
|
|
261
|
-
password?: string;
|
|
262
218
|
}
|
|
263
219
|
): Promise<http.Server> => {
|
|
264
|
-
const port =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
220
|
+
const port = properties?.port ?? LOCAL_PORT;
|
|
221
|
+
if (!fs.existsSync(properties.configDirectory)) {
|
|
222
|
+
fs.mkdirSync(properties.configDirectory, { recursive: true });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const trust = new Trust(getTrustPath(properties.configDirectory));
|
|
269
226
|
|
|
270
227
|
const restart = async () => {
|
|
271
228
|
await client.stop();
|
|
@@ -285,43 +242,27 @@ export const startApiServer = async (
|
|
|
285
242
|
gid: process.getgid!(),
|
|
286
243
|
}
|
|
287
244
|
);
|
|
288
|
-
|
|
289
|
-
/* process.on("exit", async () => {
|
|
290
|
-
child.kill("SIGINT")
|
|
291
|
-
});
|
|
292
|
-
process.on("SIGINT", async () => {
|
|
293
|
-
child.kill("SIGINT")
|
|
294
|
-
}); */
|
|
295
245
|
process.exit(0);
|
|
296
246
|
};
|
|
247
|
+
if (!client.peerId.equals(await client.identity.publicKey.toPeerId())) {
|
|
248
|
+
throw new Error("Expecting node identity to equal peerId");
|
|
249
|
+
}
|
|
297
250
|
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
251
|
+
const getVerifiedBody = async (req: http.IncomingMessage) => {
|
|
252
|
+
const body = await getBody(req);
|
|
253
|
+
const result = await verifyRequest(
|
|
254
|
+
req.headers,
|
|
255
|
+
req.method!,
|
|
256
|
+
req.url!,
|
|
257
|
+
body
|
|
258
|
+
);
|
|
259
|
+
if (result.equals(client.identity.publicKey)) {
|
|
260
|
+
return body;
|
|
307
261
|
}
|
|
308
|
-
if (
|
|
309
|
-
return
|
|
262
|
+
if (trust.isTrusted(result.hashcode())) {
|
|
263
|
+
return body;
|
|
310
264
|
}
|
|
311
|
-
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const getBody = (
|
|
315
|
-
req: http.IncomingMessage,
|
|
316
|
-
callback: (body: string) => Promise<void> | void
|
|
317
|
-
) => {
|
|
318
|
-
let body = "";
|
|
319
|
-
req.on("data", function (d) {
|
|
320
|
-
body += d;
|
|
321
|
-
});
|
|
322
|
-
req.on("end", function () {
|
|
323
|
-
callback(body);
|
|
324
|
-
});
|
|
265
|
+
throw new Error("Not trusted");
|
|
325
266
|
};
|
|
326
267
|
|
|
327
268
|
const e404 = "404";
|
|
@@ -339,15 +280,20 @@ export const startApiServer = async (
|
|
|
339
280
|
|
|
340
281
|
try {
|
|
341
282
|
if (req.url) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
283
|
+
let body: string;
|
|
284
|
+
try {
|
|
285
|
+
body =
|
|
286
|
+
req.url.startsWith(PEER_ID_PATH) ||
|
|
287
|
+
req.url.startsWith(ADDRESS_PATH)
|
|
288
|
+
? await getBody(req)
|
|
289
|
+
: await getVerifiedBody(req);
|
|
290
|
+
} catch (error: any) {
|
|
347
291
|
res.writeHead(401);
|
|
348
|
-
res.end("Not authorized");
|
|
292
|
+
res.end("Not authorized: " + error.toString());
|
|
349
293
|
return;
|
|
350
|
-
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (req.url.startsWith(PROGRAMS_PATH)) {
|
|
351
297
|
if (client instanceof Peerbit === false) {
|
|
352
298
|
res.writeHead(400);
|
|
353
299
|
res.end("Server node is not running a native client");
|
|
@@ -380,7 +326,9 @@ export const startApiServer = async (
|
|
|
380
326
|
switch (req.method) {
|
|
381
327
|
case "HEAD":
|
|
382
328
|
try {
|
|
383
|
-
const program =
|
|
329
|
+
const program = (client as Peerbit).handler?.items.get(
|
|
330
|
+
getPathValue(req, 1)
|
|
331
|
+
);
|
|
384
332
|
if (program) {
|
|
385
333
|
res.writeHead(200);
|
|
386
334
|
res.end();
|
|
@@ -399,7 +347,9 @@ export const startApiServer = async (
|
|
|
399
347
|
const url = new URL(req.url, "http://localhost:" + 1234);
|
|
400
348
|
const queryData = url.searchParams.get("delete");
|
|
401
349
|
|
|
402
|
-
const program =
|
|
350
|
+
const program = (client as Peerbit).handler?.items.get(
|
|
351
|
+
getPathValue(req, 1)
|
|
352
|
+
);
|
|
403
353
|
if (program) {
|
|
404
354
|
let closed = false;
|
|
405
355
|
if (queryData === "true") {
|
|
@@ -408,7 +358,9 @@ export const startApiServer = async (
|
|
|
408
358
|
closed = await program.close();
|
|
409
359
|
}
|
|
410
360
|
if (closed) {
|
|
411
|
-
await
|
|
361
|
+
await properties?.session?.programs.remove(
|
|
362
|
+
program.address
|
|
363
|
+
);
|
|
412
364
|
}
|
|
413
365
|
|
|
414
366
|
res.writeHead(200);
|
|
@@ -423,56 +375,54 @@ export const startApiServer = async (
|
|
|
423
375
|
}
|
|
424
376
|
break;
|
|
425
377
|
case "PUT":
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
(startArguments as StartByVariant).variant
|
|
440
|
-
);
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
program = new P();
|
|
444
|
-
} else {
|
|
445
|
-
program = deserialize(
|
|
446
|
-
fromBase64((startArguments as StartByBase64).base64),
|
|
447
|
-
Program
|
|
378
|
+
try {
|
|
379
|
+
const startArguments: StartProgram = JSON.parse(body);
|
|
380
|
+
|
|
381
|
+
let program: Program;
|
|
382
|
+
if ((startArguments as StartByVariant).variant) {
|
|
383
|
+
const P = getProgramFromVariant(
|
|
384
|
+
(startArguments as StartByVariant).variant
|
|
385
|
+
);
|
|
386
|
+
if (!P) {
|
|
387
|
+
res.writeHead(400);
|
|
388
|
+
res.end(
|
|
389
|
+
"Missing program with variant: " +
|
|
390
|
+
(startArguments as StartByVariant).variant
|
|
448
391
|
);
|
|
392
|
+
return;
|
|
449
393
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
program.address,
|
|
457
|
-
(client as Peerbit).directory,
|
|
458
|
-
await client.services.blocks.has(program.address)
|
|
459
|
-
);
|
|
460
|
-
await options?.session?.programs.add(
|
|
461
|
-
program.address,
|
|
462
|
-
new Uint8Array()
|
|
463
|
-
);
|
|
464
|
-
res.writeHead(200);
|
|
465
|
-
res.end(program.address.toString());
|
|
466
|
-
})
|
|
467
|
-
.catch((error) => {
|
|
468
|
-
res.writeHead(400);
|
|
469
|
-
res.end("Failed to open program: " + error.toString());
|
|
470
|
-
});
|
|
471
|
-
} catch (error: any) {
|
|
472
|
-
res.writeHead(400);
|
|
473
|
-
res.end(error.toString());
|
|
394
|
+
program = new P();
|
|
395
|
+
} else {
|
|
396
|
+
program = deserialize(
|
|
397
|
+
fromBase64((startArguments as StartByBase64).base64),
|
|
398
|
+
Program
|
|
399
|
+
);
|
|
474
400
|
}
|
|
475
|
-
|
|
401
|
+
client
|
|
402
|
+
.open(program) // TODO all users to pass args
|
|
403
|
+
.then(async (program) => {
|
|
404
|
+
// TODO what if this is a reopen?
|
|
405
|
+
console.log(
|
|
406
|
+
"OPEN ADDRESS",
|
|
407
|
+
program.address,
|
|
408
|
+
(client as Peerbit).directory,
|
|
409
|
+
await client.services.blocks.has(program.address)
|
|
410
|
+
);
|
|
411
|
+
await properties?.session?.programs.add(
|
|
412
|
+
program.address,
|
|
413
|
+
new Uint8Array()
|
|
414
|
+
);
|
|
415
|
+
res.writeHead(200);
|
|
416
|
+
res.end(program.address.toString());
|
|
417
|
+
})
|
|
418
|
+
.catch((error) => {
|
|
419
|
+
res.writeHead(400);
|
|
420
|
+
res.end("Failed to open program: " + error.toString());
|
|
421
|
+
});
|
|
422
|
+
} catch (error: any) {
|
|
423
|
+
res.writeHead(400);
|
|
424
|
+
res.end(error.toString());
|
|
425
|
+
}
|
|
476
426
|
break;
|
|
477
427
|
|
|
478
428
|
default:
|
|
@@ -481,105 +431,101 @@ export const startApiServer = async (
|
|
|
481
431
|
}
|
|
482
432
|
} else if (req.url.startsWith(INSTALL_PATH)) {
|
|
483
433
|
switch (req.method) {
|
|
484
|
-
case "PUT":
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (!installName || installName.length === 0) {
|
|
507
|
-
res.writeHead(400);
|
|
508
|
-
res.end("Invalid package: " + packageName);
|
|
509
|
-
} else {
|
|
510
|
-
try {
|
|
511
|
-
// TODO do this without sudo. i.e. for servers provide arguments so that this app folder is writeable by default by the user
|
|
512
|
-
const installDir =
|
|
513
|
-
process.env.PEERBIT_MODULES_PATH ||
|
|
514
|
-
findPeerbitProgramFolder(__dirname);
|
|
515
|
-
let permission = "";
|
|
516
|
-
if (!installDir) {
|
|
517
|
-
res.writeHead(400);
|
|
518
|
-
res.end("Missing installation directory");
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
try {
|
|
522
|
-
fs.accessSync(installDir, fs.constants.W_OK);
|
|
523
|
-
} catch (error) {
|
|
524
|
-
permission = "sudo";
|
|
525
|
-
}
|
|
434
|
+
case "PUT": {
|
|
435
|
+
const installArgs: InstallDependency = JSON.parse(body);
|
|
436
|
+
|
|
437
|
+
const packageName = installArgs.name; // @abc/123
|
|
438
|
+
let installName = installArgs.name; // abc123.tgz or @abc/123 (npm package name)
|
|
439
|
+
let clear: (() => void) | undefined;
|
|
440
|
+
if (installArgs.type === "tgz") {
|
|
441
|
+
const binary = fromBase64(installArgs.base64);
|
|
442
|
+
const tempFile = tmp.fileSync({
|
|
443
|
+
name:
|
|
444
|
+
base58btc.encode(Buffer.from(installName)) +
|
|
445
|
+
uuid() +
|
|
446
|
+
".tgz",
|
|
447
|
+
});
|
|
448
|
+
fs.writeFileSync(tempFile.fd, binary);
|
|
449
|
+
clear = () => tempFile.removeCallback();
|
|
450
|
+
installName = tempFile.name;
|
|
451
|
+
} else {
|
|
452
|
+
clear = undefined;
|
|
453
|
+
}
|
|
526
454
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
455
|
+
if (!installName || installName.length === 0) {
|
|
456
|
+
res.writeHead(400);
|
|
457
|
+
res.end("Invalid package: " + packageName);
|
|
458
|
+
} else {
|
|
459
|
+
try {
|
|
460
|
+
// TODO do this without sudo. i.e. for servers provide arguments so that this app folder is writeable by default by the user
|
|
461
|
+
const installDir =
|
|
462
|
+
process.env.PEERBIT_MODULES_PATH ||
|
|
463
|
+
findPeerbitProgramFolder(__dirname);
|
|
464
|
+
let permission = "";
|
|
465
|
+
if (!installDir) {
|
|
532
466
|
res.writeHead(400);
|
|
533
|
-
res.end(
|
|
534
|
-
"Failed to install library: " +
|
|
535
|
-
packageName +
|
|
536
|
-
". " +
|
|
537
|
-
error.toString()
|
|
538
|
-
);
|
|
539
|
-
clear?.();
|
|
467
|
+
res.end("Missing installation directory");
|
|
540
468
|
return;
|
|
541
469
|
}
|
|
542
|
-
|
|
543
470
|
try {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
);
|
|
471
|
+
fs.accessSync(installDir, fs.constants.W_OK);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
permission = "sudo";
|
|
474
|
+
}
|
|
549
475
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
476
|
+
console.log("Installing package: " + installName);
|
|
477
|
+
execSync(
|
|
478
|
+
`${permission} npm install ${installName} --prefix ${installDir} --no-save --no-package-lock`
|
|
479
|
+
); // TODO omit=dev ? but this makes breaks the tests after running once?
|
|
480
|
+
} catch (error: any) {
|
|
481
|
+
res.writeHead(400);
|
|
482
|
+
res.end(
|
|
483
|
+
"Failed to install library: " +
|
|
484
|
+
packageName +
|
|
485
|
+
". " +
|
|
486
|
+
error.toString()
|
|
487
|
+
);
|
|
488
|
+
clear?.();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
566
491
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
492
|
+
try {
|
|
493
|
+
const programsPre = new Set(
|
|
494
|
+
getProgramFromVariants().map((x) => getSchema(x).variant)
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
await import(
|
|
498
|
+
/* webpackIgnore: true */ /* @vite-ignore */ packageName
|
|
499
|
+
);
|
|
500
|
+
await properties?.session?.imports.add(
|
|
501
|
+
packageName,
|
|
502
|
+
new Uint8Array()
|
|
503
|
+
);
|
|
504
|
+
const programsPost = getProgramFromVariants()?.map((x) =>
|
|
505
|
+
getSchema(x)
|
|
506
|
+
);
|
|
507
|
+
const newPrograms: { variant: string }[] = [];
|
|
508
|
+
for (const p of programsPost) {
|
|
509
|
+
if (!programsPre.has(p.variant)) {
|
|
510
|
+
newPrograms.push(p as { variant: string });
|
|
511
|
+
}
|
|
575
512
|
}
|
|
513
|
+
|
|
514
|
+
res.writeHead(200);
|
|
515
|
+
res.end(JSON.stringify(newPrograms.map((x) => x.variant)));
|
|
516
|
+
} catch (e: any) {
|
|
517
|
+
res.writeHead(400);
|
|
518
|
+
res.end(e.message.toString?.());
|
|
519
|
+
clear?.();
|
|
576
520
|
}
|
|
577
|
-
}
|
|
521
|
+
}
|
|
578
522
|
break;
|
|
523
|
+
}
|
|
579
524
|
|
|
580
|
-
default:
|
|
525
|
+
default: {
|
|
581
526
|
r404();
|
|
582
527
|
break;
|
|
528
|
+
}
|
|
583
529
|
}
|
|
584
530
|
} else if (req.url.startsWith(BOOTSTRAP_PATH)) {
|
|
585
531
|
switch (req.method) {
|
|
@@ -598,6 +544,24 @@ export const startApiServer = async (
|
|
|
598
544
|
r404();
|
|
599
545
|
break;
|
|
600
546
|
}
|
|
547
|
+
} else if (req.url.startsWith(TRUST_PATH)) {
|
|
548
|
+
switch (req.method) {
|
|
549
|
+
case "PUT": {
|
|
550
|
+
trust.add(getPathValue(req, 1));
|
|
551
|
+
res.writeHead(200);
|
|
552
|
+
res.end();
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
case "DELETE": {
|
|
556
|
+
const removed = trust.remove(getPathValue(req, 1));
|
|
557
|
+
res.writeHead(200);
|
|
558
|
+
res.end(removed);
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
default:
|
|
562
|
+
r404();
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
601
565
|
} else if (req.url.startsWith(RESTART_PATH)) {
|
|
602
566
|
switch (req.method) {
|
|
603
567
|
case "POST":
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Identity,
|
|
3
|
+
PublicSignKey,
|
|
4
|
+
SignatureWithKey,
|
|
5
|
+
fromBase64,
|
|
6
|
+
toBase64,
|
|
7
|
+
verify,
|
|
8
|
+
Ed25519PublicKey,
|
|
9
|
+
} from "@peerbit/crypto";
|
|
10
|
+
import { deserialize, serialize, BinaryWriter } from "@dao-xyz/borsh";
|
|
11
|
+
import http from "http";
|
|
12
|
+
|
|
13
|
+
const SIGNATURE_KEY = "X-Peerbit-Signature";
|
|
14
|
+
const SIGNATURE_TIME_KEY = "X-Peerbit-Signature-Time";
|
|
15
|
+
|
|
16
|
+
export const signRequest = async (
|
|
17
|
+
headers: Record<string, string>,
|
|
18
|
+
method: string,
|
|
19
|
+
path: string,
|
|
20
|
+
data: string | undefined,
|
|
21
|
+
keypair: Identity<Ed25519PublicKey>
|
|
22
|
+
) => {
|
|
23
|
+
const sigTimestamp = Math.round(new Date().getTime() / 1000).toString();
|
|
24
|
+
const write = new BinaryWriter();
|
|
25
|
+
if (!method) {
|
|
26
|
+
throw new Error("Expecting method");
|
|
27
|
+
}
|
|
28
|
+
if (!path) {
|
|
29
|
+
throw new Error("Expecting path");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
write.string(
|
|
33
|
+
method.toLowerCase() + path.toLowerCase() + sigTimestamp + (data || "")
|
|
34
|
+
);
|
|
35
|
+
const signature = await keypair.sign(write.finalize());
|
|
36
|
+
headers[SIGNATURE_TIME_KEY] = sigTimestamp;
|
|
37
|
+
headers[SIGNATURE_KEY] = toBase64(serialize(signature));
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const getBody = (req: http.IncomingMessage): Promise<string> => {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
let body = "";
|
|
43
|
+
req.on("data", (d) => {
|
|
44
|
+
body += d;
|
|
45
|
+
});
|
|
46
|
+
req.on("end", () => {
|
|
47
|
+
resolve(body);
|
|
48
|
+
});
|
|
49
|
+
req.on("error", (e) => reject(e));
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const verifyRequest = async (
|
|
54
|
+
headers: Record<string, string | string[] | undefined>,
|
|
55
|
+
method: string,
|
|
56
|
+
path: string,
|
|
57
|
+
body = ""
|
|
58
|
+
): Promise<PublicSignKey> => {
|
|
59
|
+
const timestamp =
|
|
60
|
+
headers[SIGNATURE_TIME_KEY] || headers[SIGNATURE_TIME_KEY.toLowerCase()];
|
|
61
|
+
if (typeof timestamp !== "string") {
|
|
62
|
+
throw new Error("Unexpected timestamp type: " + typeof timestamp);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const write = new BinaryWriter();
|
|
66
|
+
if (!method) {
|
|
67
|
+
throw new Error("Expecting method");
|
|
68
|
+
}
|
|
69
|
+
if (!path) {
|
|
70
|
+
throw new Error("Expecting path");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
write.string(method.toLowerCase() + path.toLowerCase() + timestamp + body);
|
|
74
|
+
const signature =
|
|
75
|
+
headers[SIGNATURE_KEY] || headers[SIGNATURE_KEY.toLowerCase()];
|
|
76
|
+
if (typeof signature !== "string") {
|
|
77
|
+
throw new Error("Unexpected signature type: " + typeof signature);
|
|
78
|
+
}
|
|
79
|
+
const signatureWithKey = deserialize(fromBase64(signature), SignatureWithKey);
|
|
80
|
+
if (await verify(signatureWithKey, write.finalize())) {
|
|
81
|
+
return signatureWithKey.publicKey;
|
|
82
|
+
}
|
|
83
|
+
throw new Error("Invalid signature");
|
|
84
|
+
};
|