@interopio/gateway-server 0.6.2-beta → 0.7.0-beta
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/changelog.md +15 -0
- package/dist/gateway-ent.cjs +170 -6
- package/dist/gateway-ent.cjs.map +4 -4
- package/dist/gateway-ent.js +176 -6
- package/dist/gateway-ent.js.map +4 -4
- package/dist/index.cjs +1551 -1983
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +1551 -1983
- package/dist/index.js.map +4 -4
- package/gateway-server.d.ts +8 -46
- package/package.json +10 -4
- package/readme.md +26 -3
- package/types/auth.d.ts +5 -0
- package/types/web/http.d.ts +64 -0
- package/types/web/server.d.ts +108 -0
- package/types/web/ws.d.ts +19 -0
package/dist/index.js
CHANGED
|
@@ -9,12 +9,12 @@ var server_exports = {};
|
|
|
9
9
|
__export(server_exports, {
|
|
10
10
|
Factory: () => Factory
|
|
11
11
|
});
|
|
12
|
-
import
|
|
13
|
-
import
|
|
12
|
+
import * as ws from "ws";
|
|
13
|
+
import http2 from "node:http";
|
|
14
14
|
import https from "node:https";
|
|
15
15
|
import { readFileSync } from "node:fs";
|
|
16
16
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
17
|
-
import { IOGateway as
|
|
17
|
+
import { IOGateway as IOGateway6 } from "@interopio/gateway";
|
|
18
18
|
|
|
19
19
|
// src/logger.ts
|
|
20
20
|
import { IOGateway } from "@interopio/gateway";
|
|
@@ -22,26 +22,124 @@ function getLogger(name) {
|
|
|
22
22
|
return IOGateway.Logging.getLogger(`gateway.server.${name}`);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// src/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// src/gateway/ws/core.ts
|
|
26
|
+
import { IOGateway as IOGateway2 } from "@interopio/gateway";
|
|
27
|
+
var GatewayEncoders = IOGateway2.Encoding;
|
|
28
|
+
var log = getLogger("ws");
|
|
29
|
+
var codec = GatewayEncoders.json();
|
|
30
|
+
function initClient(socket, authenticationPromise, key, host) {
|
|
31
|
+
const opts = {
|
|
32
|
+
key,
|
|
33
|
+
host,
|
|
34
|
+
codec,
|
|
35
|
+
onAuthenticate: async () => {
|
|
36
|
+
const authentication = await authenticationPromise();
|
|
37
|
+
if (authentication?.authenticated) {
|
|
38
|
+
return { type: "success", user: authentication.name };
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`no valid client authentication ${key}`);
|
|
41
|
+
},
|
|
42
|
+
onPing: () => {
|
|
43
|
+
socket.ping((err) => {
|
|
44
|
+
if (err) {
|
|
45
|
+
log.warn(`failed to ping ${key}`, err);
|
|
46
|
+
} else {
|
|
47
|
+
log.info(`ping sent to ${key}`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
onDisconnect: (reason) => {
|
|
52
|
+
switch (reason) {
|
|
53
|
+
case "inactive": {
|
|
54
|
+
log.warn(`no heartbeat (ping) received from ${key}, closing socket`);
|
|
55
|
+
socket.close(4001, "ping expected");
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "shutdown": {
|
|
59
|
+
socket.close(1001, "shutdown");
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
return this.client((data) => socket.send(data), opts);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
log.warn(`${key} failed to create client`, err);
|
|
30
69
|
}
|
|
31
|
-
return `${remoteIp}:${socket.remotePort}`;
|
|
32
70
|
}
|
|
71
|
+
async function create(server) {
|
|
72
|
+
log.info(`starting gateway on ${server.endpoint}`);
|
|
73
|
+
await this.start({ endpoint: server.endpoint });
|
|
74
|
+
return async (socket, handshake) => {
|
|
75
|
+
const key = handshake.logPrefix;
|
|
76
|
+
const host = handshake.remoteAddress;
|
|
77
|
+
log.info(`${key} connected on gw from ${host}`);
|
|
78
|
+
const client = initClient.call(this, socket, handshake.principal, key, host);
|
|
79
|
+
if (!client) {
|
|
80
|
+
log.error(`${key} gw client init failed`);
|
|
81
|
+
socket.terminate();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
socket.on("error", (err) => {
|
|
85
|
+
log.error(`${key} websocket error: ${err}`, err);
|
|
86
|
+
});
|
|
87
|
+
socket.on("message", (data, _isBinary) => {
|
|
88
|
+
if (Array.isArray(data)) {
|
|
89
|
+
data = Buffer.concat(data);
|
|
90
|
+
}
|
|
91
|
+
client.send(data);
|
|
92
|
+
});
|
|
93
|
+
socket.on("close", (code) => {
|
|
94
|
+
log.info(`${key} disconnected from gw. code: ${code}`);
|
|
95
|
+
client.close();
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
var core_default = create;
|
|
33
100
|
|
|
34
|
-
// src/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
101
|
+
// src/common/compose.ts
|
|
102
|
+
function compose(...middleware) {
|
|
103
|
+
if (!Array.isArray(middleware)) {
|
|
104
|
+
throw new Error("middleware must be array!");
|
|
38
105
|
}
|
|
39
|
-
|
|
40
|
-
|
|
106
|
+
const fns = middleware.flat();
|
|
107
|
+
for (const fn of fns) {
|
|
108
|
+
if (typeof fn !== "function") {
|
|
109
|
+
throw new Error("middleware must be compose of functions!");
|
|
110
|
+
}
|
|
41
111
|
}
|
|
42
|
-
|
|
112
|
+
return async function(ctx, next) {
|
|
113
|
+
const dispatch = async (i) => {
|
|
114
|
+
const fn = i === fns.length ? next : fns[i];
|
|
115
|
+
if (fn === void 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
let nextCalled = false;
|
|
119
|
+
let nextResolved = false;
|
|
120
|
+
const nextFn = async () => {
|
|
121
|
+
if (nextCalled) {
|
|
122
|
+
throw new Error("next() called multiple times");
|
|
123
|
+
}
|
|
124
|
+
nextCalled = true;
|
|
125
|
+
try {
|
|
126
|
+
return await dispatch(i + 1);
|
|
127
|
+
} finally {
|
|
128
|
+
nextResolved = true;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const result = await fn(ctx, nextFn);
|
|
132
|
+
if (nextCalled && !nextResolved) {
|
|
133
|
+
throw new Error("middleware resolved before downstream.\n You are probably missing an await or return statement in your middleware function.");
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
};
|
|
137
|
+
return dispatch(0);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
43
140
|
|
|
44
141
|
// src/server/exchange.ts
|
|
142
|
+
import http from "node:http";
|
|
45
143
|
import { Cookie } from "tough-cookie";
|
|
46
144
|
function requestToProtocol(request, defaultProtocol) {
|
|
47
145
|
let proto = request.headers.get("x-forwarded-proto");
|
|
@@ -82,53 +180,81 @@ function parseCookies(request) {
|
|
|
82
180
|
return result;
|
|
83
181
|
});
|
|
84
182
|
}
|
|
183
|
+
var ExtendedHttpIncomingMessage = class extends http.IncomingMessage {
|
|
184
|
+
// circular reference to the exchange
|
|
185
|
+
exchange;
|
|
186
|
+
get urlBang() {
|
|
187
|
+
return this.url;
|
|
188
|
+
}
|
|
189
|
+
get socketEncrypted() {
|
|
190
|
+
return this.socket["encrypted"] === true;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var ExtendedHttpServerResponse = class _ExtendedHttpServerResponse extends http.ServerResponse {
|
|
194
|
+
static forUpgrade(req, socket, head) {
|
|
195
|
+
const res = new _ExtendedHttpServerResponse(req);
|
|
196
|
+
res.upgradeHead = head;
|
|
197
|
+
res.assignSocket(socket);
|
|
198
|
+
return res;
|
|
199
|
+
}
|
|
200
|
+
upgradeHead;
|
|
201
|
+
markHeadersSent() {
|
|
202
|
+
this["_header"] = true;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
85
205
|
var HttpServerRequest = class {
|
|
86
206
|
_body;
|
|
87
207
|
_url;
|
|
88
208
|
_cookies;
|
|
89
|
-
|
|
90
|
-
|
|
209
|
+
#headers;
|
|
210
|
+
#req;
|
|
91
211
|
constructor(req) {
|
|
92
|
-
this
|
|
93
|
-
this
|
|
212
|
+
this.#req = req;
|
|
213
|
+
this.#headers = new IncomingMessageHeaders(this.#req);
|
|
214
|
+
}
|
|
215
|
+
get unsafeIncomingMessage() {
|
|
216
|
+
return this.#req;
|
|
217
|
+
}
|
|
218
|
+
get upgrade() {
|
|
219
|
+
return this.#req["upgrade"];
|
|
94
220
|
}
|
|
95
221
|
get http2() {
|
|
96
|
-
return this.
|
|
222
|
+
return this.#req.httpVersionMajor >= 2;
|
|
97
223
|
}
|
|
98
224
|
get headers() {
|
|
99
|
-
return this
|
|
225
|
+
return this.#headers;
|
|
100
226
|
}
|
|
101
227
|
get path() {
|
|
102
228
|
return this.URL?.pathname;
|
|
103
229
|
}
|
|
104
230
|
get URL() {
|
|
105
|
-
this._url ??= new URL(this.
|
|
231
|
+
this._url ??= new URL(this.#req.urlBang, `${this.protocol}://${this.host}`);
|
|
106
232
|
return this._url;
|
|
107
233
|
}
|
|
108
234
|
get query() {
|
|
109
235
|
return this.URL?.search;
|
|
110
236
|
}
|
|
111
237
|
get method() {
|
|
112
|
-
return this.
|
|
238
|
+
return this.#req.method;
|
|
113
239
|
}
|
|
114
240
|
get host() {
|
|
115
241
|
let dh = void 0;
|
|
116
|
-
if (this.
|
|
117
|
-
dh =
|
|
242
|
+
if (this.#req.httpVersionMajor >= 2) {
|
|
243
|
+
dh = this.#req.headers[":authority"];
|
|
118
244
|
}
|
|
119
|
-
dh ??= this.
|
|
245
|
+
dh ??= this.#req.socket.remoteAddress;
|
|
120
246
|
return requestToHost(this, dh);
|
|
121
247
|
}
|
|
122
248
|
get protocol() {
|
|
123
249
|
let dp = void 0;
|
|
124
|
-
if (this.
|
|
125
|
-
dp = this.
|
|
250
|
+
if (this.#req.httpVersionMajor > 2) {
|
|
251
|
+
dp = this.#req.headers[":scheme"];
|
|
126
252
|
}
|
|
127
|
-
dp ??= this.
|
|
253
|
+
dp ??= this.#req.socketEncrypted ? "https" : "http";
|
|
128
254
|
return requestToProtocol(this, dp);
|
|
129
255
|
}
|
|
130
256
|
get socket() {
|
|
131
|
-
return this.
|
|
257
|
+
return this.#req.socket;
|
|
132
258
|
}
|
|
133
259
|
get cookies() {
|
|
134
260
|
this._cookies ??= parseCookies(this);
|
|
@@ -137,7 +263,7 @@ var HttpServerRequest = class {
|
|
|
137
263
|
get body() {
|
|
138
264
|
this._body ??= new Promise((resolve, reject) => {
|
|
139
265
|
const chunks = [];
|
|
140
|
-
this.
|
|
266
|
+
this.#req.on("error", (err) => reject(err)).on("data", (chunk) => chunks.push(chunk)).on("end", () => {
|
|
141
267
|
resolve(new Blob(chunks, { type: this.headers.one("content-type") }));
|
|
142
268
|
});
|
|
143
269
|
});
|
|
@@ -236,26 +362,29 @@ var OutgoingMessageHeaders = class {
|
|
|
236
362
|
}
|
|
237
363
|
};
|
|
238
364
|
var HttpServerResponse = class {
|
|
239
|
-
|
|
240
|
-
|
|
365
|
+
#headers;
|
|
366
|
+
#res;
|
|
241
367
|
constructor(res) {
|
|
242
|
-
this
|
|
243
|
-
this
|
|
368
|
+
this.#res = res;
|
|
369
|
+
this.#headers = new OutgoingMessageHeaders(res);
|
|
370
|
+
}
|
|
371
|
+
get unsafeServerResponse() {
|
|
372
|
+
return this.#res;
|
|
244
373
|
}
|
|
245
374
|
get statusCode() {
|
|
246
|
-
return this.
|
|
375
|
+
return this.#res.statusCode;
|
|
247
376
|
}
|
|
248
377
|
set statusCode(value) {
|
|
249
|
-
if (this.
|
|
378
|
+
if (this.#res.headersSent) {
|
|
250
379
|
return;
|
|
251
380
|
}
|
|
252
|
-
this.
|
|
381
|
+
this.#res.statusCode = value;
|
|
253
382
|
}
|
|
254
383
|
set statusMessage(value) {
|
|
255
|
-
this.
|
|
384
|
+
this.#res.statusMessage = value;
|
|
256
385
|
}
|
|
257
386
|
get headers() {
|
|
258
|
-
return this
|
|
387
|
+
return this.#headers;
|
|
259
388
|
}
|
|
260
389
|
get cookies() {
|
|
261
390
|
return this.headers.list("set-cookie").map((cookie) => {
|
|
@@ -273,15 +402,15 @@ var HttpServerResponse = class {
|
|
|
273
402
|
}).filter((cookie) => cookie !== void 0);
|
|
274
403
|
}
|
|
275
404
|
end(chunk) {
|
|
276
|
-
if (!this.
|
|
405
|
+
if (!this.#res.headersSent) {
|
|
277
406
|
return new Promise((resolve, reject) => {
|
|
278
407
|
try {
|
|
279
408
|
if (chunk === void 0) {
|
|
280
|
-
this.
|
|
409
|
+
this.#res.end(() => {
|
|
281
410
|
resolve(true);
|
|
282
411
|
});
|
|
283
412
|
} else {
|
|
284
|
-
this.
|
|
413
|
+
this.#res.end(chunk, () => {
|
|
285
414
|
resolve(true);
|
|
286
415
|
});
|
|
287
416
|
}
|
|
@@ -307,15 +436,20 @@ var HttpServerResponse = class {
|
|
|
307
436
|
return this;
|
|
308
437
|
}
|
|
309
438
|
};
|
|
310
|
-
var DefaultWebExchange = class
|
|
439
|
+
var DefaultWebExchange = class {
|
|
311
440
|
request;
|
|
312
441
|
response;
|
|
313
442
|
constructor(request, response) {
|
|
314
|
-
super();
|
|
315
443
|
this.request = request;
|
|
316
444
|
this.response = response;
|
|
317
445
|
}
|
|
318
|
-
get
|
|
446
|
+
get method() {
|
|
447
|
+
return this.request.method;
|
|
448
|
+
}
|
|
449
|
+
get path() {
|
|
450
|
+
return this.request.path;
|
|
451
|
+
}
|
|
452
|
+
principal() {
|
|
319
453
|
return Promise.resolve(void 0);
|
|
320
454
|
}
|
|
321
455
|
};
|
|
@@ -402,6 +536,7 @@ var MockHttpRequest = class {
|
|
|
402
536
|
#url;
|
|
403
537
|
#body;
|
|
404
538
|
headers = new MapHttpHeaders();
|
|
539
|
+
upgrade = false;
|
|
405
540
|
constructor(url, method) {
|
|
406
541
|
if (typeof url === "string") {
|
|
407
542
|
if (URL.canParse(url)) {
|
|
@@ -485,185 +620,290 @@ var MockHttpResponse = class {
|
|
|
485
620
|
}
|
|
486
621
|
};
|
|
487
622
|
|
|
488
|
-
// src/
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
function initClient(key, socket, host, securityContextPromise) {
|
|
494
|
-
const opts = {
|
|
495
|
-
key,
|
|
496
|
-
host,
|
|
497
|
-
codec,
|
|
498
|
-
onAuthenticate: async () => {
|
|
499
|
-
const authentication = (await securityContextPromise)?.authentication;
|
|
500
|
-
if (authentication?.authenticated) {
|
|
501
|
-
return { type: "success", user: authentication.name };
|
|
502
|
-
}
|
|
503
|
-
throw new Error(`no valid client authentication ${key}`);
|
|
504
|
-
},
|
|
505
|
-
onPing: () => {
|
|
506
|
-
socket.ping((err) => {
|
|
507
|
-
if (err) {
|
|
508
|
-
log.warn(`failed to ping ${key}`, err);
|
|
509
|
-
} else {
|
|
510
|
-
log.info(`ping sent to ${key}`);
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
},
|
|
514
|
-
onDisconnect: (reason) => {
|
|
515
|
-
switch (reason) {
|
|
516
|
-
case "inactive": {
|
|
517
|
-
log.warn(`no heartbeat (ping) received from ${key}, closing socket`);
|
|
518
|
-
socket.close(4001, "ping expected");
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
case "shutdown": {
|
|
522
|
-
socket.close(1001, "shutdown");
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
try {
|
|
529
|
-
return this.client((data) => socket.send(data), opts);
|
|
530
|
-
} catch (err) {
|
|
531
|
-
log.warn(`${key} failed to create client`, err);
|
|
623
|
+
// src/utils.ts
|
|
624
|
+
function socketKey(socket) {
|
|
625
|
+
const remoteIp = socket.remoteAddress;
|
|
626
|
+
if (!remoteIp) {
|
|
627
|
+
throw new Error("Socket has no remote address");
|
|
532
628
|
}
|
|
629
|
+
return `${remoteIp}:${socket.remotePort}`;
|
|
533
630
|
}
|
|
534
|
-
async function create(server) {
|
|
535
|
-
log.info("start gateway");
|
|
536
|
-
await this.start({ endpoint: server.endpoint });
|
|
537
|
-
server.wss.on("error", (err) => {
|
|
538
|
-
log.error("error starting the gateway websocket server", err);
|
|
539
|
-
}).on("connection", (socket, req) => {
|
|
540
|
-
const request = new HttpServerRequest(req);
|
|
541
|
-
const securityContextPromise = server.storage?.getStore()?.securityContext;
|
|
542
|
-
const key = socketKey(request.socket);
|
|
543
|
-
const host = request.host;
|
|
544
|
-
log.info(`${key} connected on gw from ${host}`);
|
|
545
|
-
const client = initClient.call(this, key, socket, host, securityContextPromise);
|
|
546
|
-
if (!client) {
|
|
547
|
-
log.error(`${key} gw client init failed`);
|
|
548
|
-
socket.terminate();
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
socket.on("error", (err) => {
|
|
552
|
-
log.error(`${key} websocket error: ${err}`, err);
|
|
553
|
-
});
|
|
554
|
-
socket.on("message", (data, _isBinary) => {
|
|
555
|
-
if (Array.isArray(data)) {
|
|
556
|
-
data = Buffer.concat(data);
|
|
557
|
-
}
|
|
558
|
-
client.send(data);
|
|
559
|
-
});
|
|
560
|
-
socket.on("close", (code) => {
|
|
561
|
-
log.info(`${key} disconnected from gw. code: ${code}`);
|
|
562
|
-
client.close();
|
|
563
|
-
});
|
|
564
|
-
});
|
|
565
|
-
return {
|
|
566
|
-
close: async () => {
|
|
567
|
-
server.wss.close();
|
|
568
|
-
await this.stop();
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
var core_default = create;
|
|
573
631
|
|
|
574
|
-
// src/
|
|
575
|
-
|
|
576
|
-
var
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
this.nodes.delete(foundId);
|
|
632
|
+
// src/server/address.ts
|
|
633
|
+
import { networkInterfaces } from "node:os";
|
|
634
|
+
var PORT_RANGE_MATCHER = /^(\d+|(0x[\da-f]+))(-(\d+|(0x[\da-f]+)))?$/i;
|
|
635
|
+
function validPort(port) {
|
|
636
|
+
if (port > 65535) throw new Error(`bad port ${port}`);
|
|
637
|
+
return port;
|
|
638
|
+
}
|
|
639
|
+
function* portRange(port) {
|
|
640
|
+
if (typeof port === "string") {
|
|
641
|
+
for (const portRange2 of port.split(",")) {
|
|
642
|
+
const trimmed = portRange2.trim();
|
|
643
|
+
const matchResult = PORT_RANGE_MATCHER.exec(trimmed);
|
|
644
|
+
if (matchResult) {
|
|
645
|
+
const start2 = parseInt(matchResult[1]);
|
|
646
|
+
const end = parseInt(matchResult[4] ?? matchResult[1]);
|
|
647
|
+
for (let i = validPort(start2); i < validPort(end) + 1; i++) {
|
|
648
|
+
yield i;
|
|
592
649
|
}
|
|
593
650
|
} else {
|
|
594
|
-
|
|
595
|
-
this.nodesByEndpoint.set(endpoint, nodeId);
|
|
596
|
-
}
|
|
597
|
-
this.nodes.set(nodeId, this.updateNode(node, new Set(users ?? []), nodeId, this.nodes.get(nodeId)));
|
|
598
|
-
}
|
|
599
|
-
this.cleanupOldNodes();
|
|
600
|
-
const sortedNodes = Array.from(this.nodes.values()).sort((a, b) => a.memberId - b.memberId);
|
|
601
|
-
return nodes.map((e) => {
|
|
602
|
-
const node = e.node;
|
|
603
|
-
const connect = this.findConnections(sortedNodes, this.nodes.get(node));
|
|
604
|
-
return { node, connect };
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
remove(nodeId) {
|
|
608
|
-
const removed = this.nodes.get(nodeId);
|
|
609
|
-
if (removed) {
|
|
610
|
-
this.nodes.delete(nodeId);
|
|
611
|
-
const endpoint = removed.endpoint;
|
|
612
|
-
this.nodesByEndpoint.delete(endpoint);
|
|
613
|
-
logger.info(`endpoint ${endpoint} removed for ${nodeId}`);
|
|
614
|
-
return true;
|
|
615
|
-
}
|
|
616
|
-
return false;
|
|
617
|
-
}
|
|
618
|
-
updateNode(newNode, users, _key, oldNode) {
|
|
619
|
-
const node = !oldNode ? { ...newNode, memberId: this.memberIds++ } : oldNode;
|
|
620
|
-
return { ...node, users, lastAccess: Date.now() };
|
|
621
|
-
}
|
|
622
|
-
cleanupOldNodes() {
|
|
623
|
-
const threshold = Date.now() - this.timeout;
|
|
624
|
-
for (const [nodeId, v] of this.nodes) {
|
|
625
|
-
if (v.lastAccess < threshold) {
|
|
626
|
-
if (logger.enabledFor("debug")) {
|
|
627
|
-
logger.debug(`${nodeId} expired - no announcement since ${new Date(v.lastAccess).toISOString()}, timeout is ${this.timeout} ms.`);
|
|
628
|
-
}
|
|
629
|
-
this.nodes.delete(nodeId);
|
|
651
|
+
throw new Error(`'${portRange2}' is not a valid port or range.`);
|
|
630
652
|
}
|
|
631
653
|
}
|
|
654
|
+
} else {
|
|
655
|
+
yield validPort(port);
|
|
632
656
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
node.users.forEach((user) => {
|
|
638
|
-
if (!c.users.has(user)) {
|
|
639
|
-
intersection.delete(user);
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
c.users.forEach((user) => {
|
|
643
|
-
if (!node.users.has(user)) {
|
|
644
|
-
intersection.delete(user);
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
if (intersection.size > 0) {
|
|
648
|
-
const e = { node: c.node, endpoint: c.endpoint };
|
|
649
|
-
return l.concat(e);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return l;
|
|
653
|
-
}, new Array());
|
|
657
|
+
}
|
|
658
|
+
var localIp = (() => {
|
|
659
|
+
function first(a) {
|
|
660
|
+
return a.length > 0 ? a[0] : void 0;
|
|
654
661
|
}
|
|
655
|
-
|
|
662
|
+
const addresses = Object.values(networkInterfaces()).flatMap((details) => {
|
|
663
|
+
return (details ?? []).filter((info2) => info2.family === "IPv4");
|
|
664
|
+
}).reduce((acc, info2) => {
|
|
665
|
+
acc[info2.internal ? "internal" : "external"].push(info2);
|
|
666
|
+
return acc;
|
|
667
|
+
}, { internal: [], external: [] });
|
|
668
|
+
return (first(addresses.internal) ?? first(addresses.external))?.address;
|
|
669
|
+
})();
|
|
656
670
|
|
|
657
|
-
// src/server/
|
|
671
|
+
// src/server/monitoring.ts
|
|
672
|
+
import { getHeapStatistics, writeHeapSnapshot } from "node:v8";
|
|
673
|
+
import { access, mkdir, rename, unlink } from "node:fs/promises";
|
|
674
|
+
var log2 = getLogger("monitoring");
|
|
675
|
+
var DEFAULT_OPTIONS = {
|
|
676
|
+
memoryLimit: 1024 * 1024 * 1024,
|
|
677
|
+
// 1GB
|
|
678
|
+
reportInterval: 10 * 60 * 1e3,
|
|
679
|
+
// 10 min
|
|
680
|
+
dumpLocation: ".",
|
|
681
|
+
// current folder
|
|
682
|
+
maxBackups: 10,
|
|
683
|
+
dumpPrefix: "Heap"
|
|
684
|
+
};
|
|
685
|
+
function fetchStats() {
|
|
686
|
+
return getHeapStatistics();
|
|
687
|
+
}
|
|
688
|
+
async function dumpHeap(opts) {
|
|
689
|
+
const prefix = opts.dumpPrefix ?? "Heap";
|
|
690
|
+
const target = `${opts.dumpLocation}/${prefix}.heapsnapshot`;
|
|
691
|
+
if (log2.enabledFor("debug")) {
|
|
692
|
+
log2.debug(`starting heap dump in ${target}`);
|
|
693
|
+
}
|
|
694
|
+
await fileExists(opts.dumpLocation).catch(async (_) => {
|
|
695
|
+
if (log2.enabledFor("debug")) {
|
|
696
|
+
log2.debug(`dump location ${opts.dumpLocation} does not exists. Will try to create it`);
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
await mkdir(opts.dumpLocation, { recursive: true });
|
|
700
|
+
log2.info(`dump location dir ${opts.dumpLocation} successfully created`);
|
|
701
|
+
} catch (e) {
|
|
702
|
+
log2.error(`failed to create dump location ${opts.dumpLocation}`);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
const dumpFileName = writeHeapSnapshot(target);
|
|
706
|
+
log2.info(`heap dumped`);
|
|
707
|
+
try {
|
|
708
|
+
log2.debug(`rolling snapshot backups`);
|
|
709
|
+
const lastFileName = `${opts.dumpLocation}/${prefix}.${opts.maxBackups}.heapsnapshot`;
|
|
710
|
+
await fileExists(lastFileName).then(async () => {
|
|
711
|
+
if (log2.enabledFor("debug")) {
|
|
712
|
+
log2.debug(`deleting ${lastFileName}`);
|
|
713
|
+
}
|
|
714
|
+
try {
|
|
715
|
+
await unlink(lastFileName);
|
|
716
|
+
} catch (e) {
|
|
717
|
+
log2.warn(`failed to delete ${lastFileName}`, e);
|
|
718
|
+
}
|
|
719
|
+
}).catch(() => {
|
|
720
|
+
});
|
|
721
|
+
for (let i = opts.maxBackups - 1; i > 0; i--) {
|
|
722
|
+
const currentFileName = `${opts.dumpLocation}/${prefix}.${i}.heapsnapshot`;
|
|
723
|
+
const nextFileName = `${opts.dumpLocation}/${prefix}.${i + 1}.heapsnapshot`;
|
|
724
|
+
await fileExists(currentFileName).then(async () => {
|
|
725
|
+
try {
|
|
726
|
+
await rename(currentFileName, nextFileName);
|
|
727
|
+
} catch (e) {
|
|
728
|
+
log2.warn(`failed to rename ${currentFileName} to ${nextFileName}`, e);
|
|
729
|
+
}
|
|
730
|
+
}).catch(() => {
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
const firstFileName = `${opts.dumpLocation}/${prefix}.${1}.heapsnapshot`;
|
|
734
|
+
try {
|
|
735
|
+
await rename(dumpFileName, firstFileName);
|
|
736
|
+
} catch (e) {
|
|
737
|
+
log2.warn(`failed to rename ${dumpFileName} to ${firstFileName}`, e);
|
|
738
|
+
}
|
|
739
|
+
log2.debug("snapshots rolled");
|
|
740
|
+
} catch (e) {
|
|
741
|
+
log2.error("error rolling backups", e);
|
|
742
|
+
throw e;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
async function fileExists(path) {
|
|
746
|
+
log2.trace(`checking file ${path}`);
|
|
747
|
+
await access(path);
|
|
748
|
+
}
|
|
749
|
+
async function processStats(stats, state, opts) {
|
|
750
|
+
if (log2.enabledFor("debug")) {
|
|
751
|
+
log2.debug(`processing heap stats ${JSON.stringify(stats)}`);
|
|
752
|
+
}
|
|
753
|
+
const limit = Math.min(opts.memoryLimit, 0.95 * stats.heap_size_limit);
|
|
754
|
+
const used = stats.used_heap_size;
|
|
755
|
+
log2.info(`heap stats ${JSON.stringify(stats)}`);
|
|
756
|
+
if (used >= limit) {
|
|
757
|
+
log2.warn(`used heap ${used} bytes exceeds memory limit ${limit} bytes`);
|
|
758
|
+
if (state.memoryLimitExceeded) {
|
|
759
|
+
delete state.snapshot;
|
|
760
|
+
} else {
|
|
761
|
+
state.memoryLimitExceeded = true;
|
|
762
|
+
state.snapshot = true;
|
|
763
|
+
}
|
|
764
|
+
await dumpHeap(opts);
|
|
765
|
+
} else {
|
|
766
|
+
state.memoryLimitExceeded = false;
|
|
767
|
+
delete state.snapshot;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function start(opts) {
|
|
771
|
+
const merged = { ...DEFAULT_OPTIONS, ...opts };
|
|
772
|
+
let stopped = false;
|
|
773
|
+
const state = { memoryLimitExceeded: false };
|
|
774
|
+
const report = async () => {
|
|
775
|
+
const stats = fetchStats();
|
|
776
|
+
await processStats(stats, state, merged);
|
|
777
|
+
};
|
|
778
|
+
const interval = setInterval(report, merged.reportInterval);
|
|
779
|
+
const channel = async (command) => {
|
|
780
|
+
if (!stopped) {
|
|
781
|
+
command ??= "run";
|
|
782
|
+
switch (command) {
|
|
783
|
+
case "run": {
|
|
784
|
+
await report();
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
case "dump": {
|
|
788
|
+
await dumpHeap(merged);
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
case "stop": {
|
|
792
|
+
stopped = true;
|
|
793
|
+
clearInterval(interval);
|
|
794
|
+
log2.info("exit memory diagnostic");
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return stopped;
|
|
800
|
+
};
|
|
801
|
+
return { ...merged, channel };
|
|
802
|
+
}
|
|
803
|
+
async function run({ channel }, command) {
|
|
804
|
+
if (!await channel(command)) {
|
|
805
|
+
log2.warn(`cannot execute command "${command}" already closed`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
async function stop(m) {
|
|
809
|
+
return await run(m, "stop");
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/app/ws-client-verify.ts
|
|
813
|
+
import { IOGateway as IOGateway3 } from "@interopio/gateway";
|
|
814
|
+
var log3 = getLogger("gateway.ws.client-verify");
|
|
815
|
+
function acceptsMissing(originFilters) {
|
|
816
|
+
switch (originFilters.missing) {
|
|
817
|
+
case "allow":
|
|
818
|
+
// fall-through
|
|
819
|
+
case "whitelist":
|
|
820
|
+
return true;
|
|
821
|
+
case "block":
|
|
822
|
+
// fall-through
|
|
823
|
+
case "blacklist":
|
|
824
|
+
return false;
|
|
825
|
+
default:
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function tryMatch(originFilters, origin) {
|
|
830
|
+
const block = originFilters.block ?? originFilters["blacklist"];
|
|
831
|
+
const allow = originFilters.allow ?? originFilters["whitelist"];
|
|
832
|
+
if (block.length > 0 && IOGateway3.Filtering.valuesMatch(block, origin)) {
|
|
833
|
+
log3.warn(`origin ${origin} matches block filter`);
|
|
834
|
+
return false;
|
|
835
|
+
} else if (allow.length > 0 && IOGateway3.Filtering.valuesMatch(allow, origin)) {
|
|
836
|
+
if (log3.enabledFor("debug")) {
|
|
837
|
+
log3.debug(`origin ${origin} matches allow filter`);
|
|
838
|
+
}
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function acceptsNonMatched(originFilters) {
|
|
843
|
+
switch (originFilters.non_matched) {
|
|
844
|
+
case "allow":
|
|
845
|
+
// fall-through
|
|
846
|
+
case "whitelist":
|
|
847
|
+
return true;
|
|
848
|
+
case "block":
|
|
849
|
+
// fall-through
|
|
850
|
+
case "blacklist":
|
|
851
|
+
return false;
|
|
852
|
+
default:
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function acceptsOrigin(origin, originFilters) {
|
|
857
|
+
if (!originFilters) {
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
if (!origin) {
|
|
861
|
+
return acceptsMissing(originFilters);
|
|
862
|
+
} else {
|
|
863
|
+
const matchResult = tryMatch(originFilters, origin);
|
|
864
|
+
if (matchResult) {
|
|
865
|
+
return matchResult;
|
|
866
|
+
} else {
|
|
867
|
+
return acceptsNonMatched(originFilters);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
function regexifyOriginFilters(originFilters) {
|
|
872
|
+
if (originFilters) {
|
|
873
|
+
const block = (originFilters.block ?? originFilters.blacklist ?? []).map(IOGateway3.Filtering.regexify);
|
|
874
|
+
const allow = (originFilters.allow ?? originFilters.whitelist ?? []).map(IOGateway3.Filtering.regexify);
|
|
875
|
+
return {
|
|
876
|
+
non_matched: originFilters.non_matched ?? "allow",
|
|
877
|
+
missing: originFilters.missing ?? "allow",
|
|
878
|
+
allow,
|
|
879
|
+
block
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/server/server-header.ts
|
|
885
|
+
import info from "@interopio/gateway-server/package.json" with { type: "json" };
|
|
886
|
+
var serverHeader = (server) => {
|
|
887
|
+
server ??= `${info.name} - v${info.version}`;
|
|
888
|
+
return async ({ response }, next) => {
|
|
889
|
+
if (server !== false && !response.headers.has("server")) {
|
|
890
|
+
response.headers.set("Server", server);
|
|
891
|
+
}
|
|
892
|
+
await next();
|
|
893
|
+
};
|
|
894
|
+
};
|
|
895
|
+
var server_header_default = (server) => serverHeader(server);
|
|
896
|
+
|
|
897
|
+
// src/server/util/matchers.ts
|
|
658
898
|
var or = (matchers) => {
|
|
659
899
|
return async (exchange) => {
|
|
660
900
|
for (const matcher of matchers) {
|
|
661
901
|
const result = await matcher(exchange);
|
|
662
902
|
if (result.match) {
|
|
663
|
-
return
|
|
903
|
+
return match();
|
|
664
904
|
}
|
|
665
905
|
}
|
|
666
|
-
return
|
|
906
|
+
return NO_MATCH;
|
|
667
907
|
};
|
|
668
908
|
};
|
|
669
909
|
var and = (matchers) => {
|
|
@@ -671,10 +911,10 @@ var and = (matchers) => {
|
|
|
671
911
|
for (const matcher2 of matchers) {
|
|
672
912
|
const result = await matcher2(exchange);
|
|
673
913
|
if (!result.match) {
|
|
674
|
-
return
|
|
914
|
+
return NO_MATCH;
|
|
675
915
|
}
|
|
676
916
|
}
|
|
677
|
-
return
|
|
917
|
+
return match();
|
|
678
918
|
};
|
|
679
919
|
matcher.toString = () => `and(${matchers.map((m) => m.toString()).join(", ")})`;
|
|
680
920
|
return matcher;
|
|
@@ -682,38 +922,41 @@ var and = (matchers) => {
|
|
|
682
922
|
var not = (matcher) => {
|
|
683
923
|
return async (exchange) => {
|
|
684
924
|
const result = await matcher(exchange);
|
|
685
|
-
return
|
|
925
|
+
return result.match ? NO_MATCH : match();
|
|
686
926
|
};
|
|
687
927
|
};
|
|
688
928
|
var anyExchange = async (_exchange) => {
|
|
689
|
-
return
|
|
929
|
+
return match();
|
|
690
930
|
};
|
|
691
931
|
anyExchange.toString = () => "any-exchange";
|
|
932
|
+
var EMPTY_OBJECT = Object.freeze({});
|
|
933
|
+
var NO_MATCH = Object.freeze({ match: false, variables: EMPTY_OBJECT });
|
|
934
|
+
var match = (variables = EMPTY_OBJECT) => {
|
|
935
|
+
return { match: true, variables };
|
|
936
|
+
};
|
|
692
937
|
var pattern = (pattern2, opts) => {
|
|
693
938
|
const method = opts?.method;
|
|
694
939
|
const matcher = async (exchange) => {
|
|
695
940
|
const request = exchange.request;
|
|
696
941
|
const path = request.path;
|
|
697
942
|
if (method !== void 0 && request.method !== method) {
|
|
698
|
-
return
|
|
943
|
+
return NO_MATCH;
|
|
699
944
|
}
|
|
700
945
|
if (typeof pattern2 === "string") {
|
|
701
946
|
if (path === pattern2) {
|
|
702
|
-
return
|
|
703
|
-
} else {
|
|
704
|
-
return { match: false };
|
|
947
|
+
return match();
|
|
705
948
|
}
|
|
949
|
+
return NO_MATCH;
|
|
706
950
|
} else {
|
|
707
|
-
const
|
|
708
|
-
if (
|
|
709
|
-
return
|
|
710
|
-
} else {
|
|
711
|
-
return { match: false };
|
|
951
|
+
const match2 = pattern2.exec(path);
|
|
952
|
+
if (match2 === null) {
|
|
953
|
+
return NO_MATCH;
|
|
712
954
|
}
|
|
955
|
+
return { match: true, variables: { ...match2.groups } };
|
|
713
956
|
}
|
|
714
957
|
};
|
|
715
958
|
matcher.toString = () => {
|
|
716
|
-
return `pattern(${pattern2.toString()}
|
|
959
|
+
return `pattern(${pattern2.toString()}, method=${method ?? "<any>"})`;
|
|
717
960
|
};
|
|
718
961
|
return matcher;
|
|
719
962
|
};
|
|
@@ -734,7 +977,7 @@ var mediaType = (opts) => {
|
|
|
734
977
|
try {
|
|
735
978
|
requestMediaTypes = request.headers.list("accept");
|
|
736
979
|
} catch (e) {
|
|
737
|
-
return
|
|
980
|
+
return NO_MATCH;
|
|
738
981
|
}
|
|
739
982
|
for (const requestedMediaType of requestMediaTypes) {
|
|
740
983
|
if (shouldIgnore(requestedMediaType)) {
|
|
@@ -742,1814 +985,1160 @@ var mediaType = (opts) => {
|
|
|
742
985
|
}
|
|
743
986
|
for (const mediaType2 of opts.mediaTypes) {
|
|
744
987
|
if (requestedMediaType.startsWith(mediaType2)) {
|
|
745
|
-
return
|
|
988
|
+
return match();
|
|
746
989
|
}
|
|
747
990
|
}
|
|
748
991
|
}
|
|
749
|
-
return
|
|
992
|
+
return NO_MATCH;
|
|
750
993
|
};
|
|
751
994
|
};
|
|
752
995
|
var upgradeMatcher = async ({ request }) => {
|
|
753
|
-
const
|
|
754
|
-
return
|
|
996
|
+
const upgrade = request.upgrade && request.headers.one("upgrade")?.toLowerCase() === "websocket";
|
|
997
|
+
return upgrade ? match() : NO_MATCH;
|
|
755
998
|
};
|
|
756
999
|
upgradeMatcher.toString = () => "websocket upgrade";
|
|
757
1000
|
|
|
758
|
-
// src/
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
config.authorize.push([pattern(/\/api\/nodes(\/.*)?/), authorize]);
|
|
766
|
-
}
|
|
767
|
-
return [
|
|
768
|
-
async (ctx, next) => {
|
|
769
|
-
if (ctx.method === "POST" && ctx.path === "/api/nodes") {
|
|
770
|
-
const json = await ctx.request.json;
|
|
771
|
-
if (!Array.isArray(json)) {
|
|
772
|
-
ctx.response.statusCode = 400;
|
|
773
|
-
await ctx.response.end();
|
|
774
|
-
} else {
|
|
775
|
-
const nodes = json;
|
|
776
|
-
const result = connections.announce(nodes);
|
|
777
|
-
const buffer = Buffer.from(JSON.stringify(result), "utf-8");
|
|
778
|
-
ctx.response.statusCode = 200;
|
|
779
|
-
ctx.response.headers.set("Content-Type", "application/json").set("Content-Length", buffer.length);
|
|
780
|
-
await ctx.response.end(buffer);
|
|
781
|
-
}
|
|
782
|
-
} else {
|
|
783
|
-
await next();
|
|
784
|
-
}
|
|
785
|
-
},
|
|
786
|
-
async ({ method, path, response }, next) => {
|
|
787
|
-
if (method === "DELETE" && path?.startsWith("/api/nodes/")) {
|
|
788
|
-
const nodeId = path?.substring("/api/nodes/".length);
|
|
789
|
-
connections.remove(nodeId);
|
|
790
|
-
response.statusCode = 200;
|
|
791
|
-
await response.end();
|
|
792
|
-
} else {
|
|
793
|
-
await next();
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
];
|
|
797
|
-
}
|
|
798
|
-
var routes_default = routes;
|
|
799
|
-
|
|
800
|
-
// src/mesh/ws/broker/core.ts
|
|
801
|
-
import { IOGateway as IOGateway3 } from "@interopio/gateway";
|
|
802
|
-
var GatewayEncoders2 = IOGateway3.Encoding;
|
|
803
|
-
var logger2 = getLogger("mesh.ws.broker");
|
|
804
|
-
function broadcastNodeAdded(nodes, newSocket, newNodeId) {
|
|
805
|
-
Object.entries(nodes.nodes).forEach(([nodeId, socket]) => {
|
|
806
|
-
if (nodeId !== newNodeId) {
|
|
807
|
-
newSocket.send(codec2.encode({ type: "node-added", "node-id": newNodeId, "new-node": nodeId }));
|
|
808
|
-
socket.send(codec2.encode({ type: "node-added", "node-id": nodeId, "new-node": newNodeId }));
|
|
1001
|
+
// src/app/route.ts
|
|
1002
|
+
import { IOGateway as IOGateway4 } from "@interopio/gateway";
|
|
1003
|
+
function findSocketRoute({ request }, { sockets: routes }) {
|
|
1004
|
+
const path = request.path ?? "/";
|
|
1005
|
+
const route = routes.get(path) ?? Array.from(routes.values()).find((route2) => {
|
|
1006
|
+
if (path === "/" && route2.default === true) {
|
|
1007
|
+
return true;
|
|
809
1008
|
}
|
|
810
1009
|
});
|
|
1010
|
+
return [route, path];
|
|
811
1011
|
}
|
|
812
|
-
function
|
|
813
|
-
|
|
814
|
-
if (
|
|
815
|
-
|
|
1012
|
+
async function configure(app, config, routes) {
|
|
1013
|
+
const applyCors = (matcher, requestMethod, options) => {
|
|
1014
|
+
if (options?.cors) {
|
|
1015
|
+
const cors = options.cors === true ? {
|
|
1016
|
+
allowOrigins: options.origins?.allow?.map(IOGateway4.Filtering.regexify),
|
|
1017
|
+
allowMethods: requestMethod == void 0 ? void 0 : [requestMethod],
|
|
1018
|
+
allowCredentials: options.authorize?.access !== "permitted"
|
|
1019
|
+
} : options.cors;
|
|
1020
|
+
routes.cors.push([matcher, cors]);
|
|
816
1021
|
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const nodeIds = connectedNodes.sockets[key];
|
|
825
|
-
if (nodeIds) {
|
|
826
|
-
delete connectedNodes.sockets[key];
|
|
827
|
-
for (const nodeId of nodeIds) {
|
|
828
|
-
delete connectedNodes.nodes[nodeId];
|
|
829
|
-
}
|
|
830
|
-
for (const nodeId of nodeIds) {
|
|
831
|
-
broadcastNodeRemoved(connectedNodes, nodeId);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
function processMessage(connectedNodes, socket, key, msg) {
|
|
836
|
-
switch (msg.type) {
|
|
837
|
-
case "hello": {
|
|
838
|
-
const nodeId = msg["node-id"];
|
|
839
|
-
connectedNodes.nodes[nodeId] = socket;
|
|
840
|
-
connectedNodes.sockets[key] = connectedNodes.sockets[key] ?? [];
|
|
841
|
-
connectedNodes.sockets[key].push(nodeId);
|
|
842
|
-
logger2.info(`[${key}] node ${nodeId} added.`);
|
|
843
|
-
broadcastNodeAdded(connectedNodes, socket, nodeId);
|
|
844
|
-
break;
|
|
845
|
-
}
|
|
846
|
-
case "bye": {
|
|
847
|
-
const nodeId = msg["node-id"];
|
|
848
|
-
delete connectedNodes[nodeId];
|
|
849
|
-
logger2.info(`[${key}] node ${nodeId} removed.`);
|
|
850
|
-
broadcastNodeRemoved(connectedNodes, nodeId);
|
|
851
|
-
break;
|
|
852
|
-
}
|
|
853
|
-
case "data": {
|
|
854
|
-
const sourceNodeId = msg.from;
|
|
855
|
-
const targetNodeId = msg.to;
|
|
856
|
-
if ("all" === targetNodeId) {
|
|
857
|
-
Object.entries(connectedNodes.nodes).forEach(([nodeId, socket2]) => {
|
|
858
|
-
if (nodeId !== sourceNodeId) {
|
|
859
|
-
socket2.send(codec2.encode(msg));
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
} else {
|
|
863
|
-
const socket2 = connectedNodes.nodes[targetNodeId];
|
|
864
|
-
if (socket2) {
|
|
865
|
-
socket2.send(codec2.encode(msg));
|
|
866
|
-
} else {
|
|
867
|
-
logger2.warn(`unable to send to node ${targetNodeId} message ${JSON.stringify(msg)}`);
|
|
1022
|
+
};
|
|
1023
|
+
const configurer = new class {
|
|
1024
|
+
handle(...handlers) {
|
|
1025
|
+
handlers.forEach(({ request, options, handler }) => {
|
|
1026
|
+
const matcher = pattern(IOGateway4.Filtering.regexify(request.path), { method: request.method });
|
|
1027
|
+
if (options?.authorize) {
|
|
1028
|
+
routes.authorize.push([matcher, options.authorize]);
|
|
868
1029
|
}
|
|
869
|
-
|
|
870
|
-
|
|
1030
|
+
applyCors(matcher, request.method, options);
|
|
1031
|
+
const middleware = async (exchange, next) => {
|
|
1032
|
+
const { match: match2, variables } = await matcher(exchange);
|
|
1033
|
+
if (match2) {
|
|
1034
|
+
await handler(exchange, variables);
|
|
1035
|
+
} else {
|
|
1036
|
+
await next();
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
routes.middleware.push(middleware);
|
|
1040
|
+
});
|
|
871
1041
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1042
|
+
socket(...sockets) {
|
|
1043
|
+
for (const { path, factory, options } of sockets) {
|
|
1044
|
+
const route = path ?? "/";
|
|
1045
|
+
routes.sockets.set(route, {
|
|
1046
|
+
default: path === void 0,
|
|
1047
|
+
ping: options?.ping,
|
|
1048
|
+
factory,
|
|
1049
|
+
maxConnections: options?.maxConnections,
|
|
1050
|
+
authorize: options?.authorize,
|
|
1051
|
+
originFilters: regexifyOriginFilters(options?.origins)
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
875
1054
|
}
|
|
876
|
-
}
|
|
1055
|
+
}();
|
|
1056
|
+
await app(configurer, config);
|
|
877
1057
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
["/message/body/type", "*"]
|
|
886
|
-
])
|
|
887
|
-
});
|
|
888
|
-
function onMessage(connectedNodes, socket, key, msg) {
|
|
889
|
-
try {
|
|
890
|
-
const decoded = codec2.decode(msg);
|
|
891
|
-
if (logger2.enabledFor("debug")) {
|
|
892
|
-
logger2.debug(`[${key}] processing msg ${JSON.stringify(decoded)}`);
|
|
893
|
-
}
|
|
894
|
-
processMessage(connectedNodes, socket, key, decoded);
|
|
895
|
-
} catch (ex) {
|
|
896
|
-
logger2.error(`[${key}] unable to process message`, ex);
|
|
1058
|
+
|
|
1059
|
+
// src/server/cors.ts
|
|
1060
|
+
import { IOGateway as IOGateway5 } from "@interopio/gateway";
|
|
1061
|
+
function isSameOrigin(request) {
|
|
1062
|
+
const origin = request.headers.one("origin");
|
|
1063
|
+
if (origin === void 0) {
|
|
1064
|
+
return true;
|
|
897
1065
|
}
|
|
1066
|
+
const url = request.URL;
|
|
1067
|
+
const actualProtocol = url.protocol;
|
|
1068
|
+
const actualHost = url.host;
|
|
1069
|
+
const originUrl = URL.parse(origin);
|
|
1070
|
+
const originHost = originUrl?.host;
|
|
1071
|
+
const originProtocol = originUrl?.protocol;
|
|
1072
|
+
return actualProtocol === originProtocol && actualHost === originHost;
|
|
898
1073
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
this.server = server;
|
|
902
|
-
}
|
|
903
|
-
async close() {
|
|
904
|
-
this.server.close();
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
|
-
async function create2(server) {
|
|
908
|
-
const connectedNodes = { nodes: {}, sockets: {} };
|
|
909
|
-
logger2.info(`mesh server is listening`);
|
|
910
|
-
server.wss.on("error", () => {
|
|
911
|
-
logger2.error(`error starting mesh server`);
|
|
912
|
-
}).on("connection", (socket, request) => {
|
|
913
|
-
const key = socketKey(request.socket);
|
|
914
|
-
onOpen(connectedNodes, key);
|
|
915
|
-
socket.on("error", (err) => {
|
|
916
|
-
logger2.error(`[${key}] websocket error: ${err}`, err);
|
|
917
|
-
});
|
|
918
|
-
socket.on("message", (data, isBinary) => {
|
|
919
|
-
if (Array.isArray(data)) {
|
|
920
|
-
data = Buffer.concat(data);
|
|
921
|
-
}
|
|
922
|
-
onMessage(connectedNodes, socket, key, data);
|
|
923
|
-
});
|
|
924
|
-
socket.on("close", (code, reason) => {
|
|
925
|
-
onClose(connectedNodes, key, code, reason);
|
|
926
|
-
});
|
|
927
|
-
});
|
|
928
|
-
return new WebsocketBroker(server.wss);
|
|
1074
|
+
function isCorsRequest(request) {
|
|
1075
|
+
return request.headers.has("origin") && !isSameOrigin(request);
|
|
929
1076
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
var
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
])
|
|
945
|
-
});
|
|
946
|
-
var InternalRelays = class {
|
|
947
|
-
// key -> socket
|
|
948
|
-
clients = /* @__PURE__ */ new Map();
|
|
949
|
-
// node -> key
|
|
950
|
-
links = /* @__PURE__ */ new Map();
|
|
951
|
-
onMsg;
|
|
952
|
-
onErr;
|
|
953
|
-
add(key, soc) {
|
|
954
|
-
this.clients.set(key, soc);
|
|
955
|
-
}
|
|
956
|
-
remove(key) {
|
|
957
|
-
this.clients.delete(key);
|
|
958
|
-
for (const [node, k] of this.links) {
|
|
959
|
-
if (k === key) {
|
|
960
|
-
this.links.delete(node);
|
|
1077
|
+
function isPreFlightRequest(request) {
|
|
1078
|
+
return request.method === "OPTIONS" && request.headers.has("origin") && request.headers.has("access-control-request-method");
|
|
1079
|
+
}
|
|
1080
|
+
var VARY_HEADERS = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"];
|
|
1081
|
+
var processRequest = (exchange, config) => {
|
|
1082
|
+
const { request, response } = exchange;
|
|
1083
|
+
const responseHeaders = response.headers;
|
|
1084
|
+
if (!responseHeaders.has("Vary")) {
|
|
1085
|
+
responseHeaders.set("Vary", VARY_HEADERS.join(", "));
|
|
1086
|
+
} else {
|
|
1087
|
+
const varyHeaders = responseHeaders.list("Vary");
|
|
1088
|
+
for (const header of VARY_HEADERS) {
|
|
1089
|
+
if (!varyHeaders.find((h) => h === header)) {
|
|
1090
|
+
varyHeaders.push(header);
|
|
961
1091
|
}
|
|
962
1092
|
}
|
|
1093
|
+
responseHeaders.set("Vary", varyHeaders.join(", "));
|
|
963
1094
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
this.onMsg(key, node, msg);
|
|
1095
|
+
try {
|
|
1096
|
+
if (!isCorsRequest(request)) {
|
|
1097
|
+
return true;
|
|
968
1098
|
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const decoded = codec3.decode(msg);
|
|
973
|
-
const { type, from, to } = decoded;
|
|
974
|
-
if (to === "all") {
|
|
975
|
-
switch (type) {
|
|
976
|
-
case "hello": {
|
|
977
|
-
if (logger3.enabledFor("debug")) {
|
|
978
|
-
logger3.debug(`${key} registers node ${from}`);
|
|
979
|
-
}
|
|
980
|
-
this.links.set(from, key);
|
|
981
|
-
break;
|
|
982
|
-
}
|
|
983
|
-
case "bye": {
|
|
984
|
-
if (logger3.enabledFor("debug")) {
|
|
985
|
-
logger3.debug(`${key} unregisters node ${from}`);
|
|
986
|
-
}
|
|
987
|
-
this.links.delete(from);
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
return from;
|
|
994
|
-
} catch (e) {
|
|
995
|
-
if (this.onErr) {
|
|
996
|
-
this.onErr(key, e instanceof Error ? e : new Error(`link failed :${e}`));
|
|
997
|
-
} else {
|
|
998
|
-
logger3.warn(`${key} unable to process ${msg}`, e);
|
|
999
|
-
}
|
|
1099
|
+
} catch (e) {
|
|
1100
|
+
if (logger.enabledFor("debug")) {
|
|
1101
|
+
logger.debug(`reject: origin is malformed`);
|
|
1000
1102
|
}
|
|
1103
|
+
rejectRequest(response);
|
|
1104
|
+
return false;
|
|
1001
1105
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
});
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
throw new Error(`${key} no active link for ${decoded.to}`);
|
|
1106
|
+
if (responseHeaders.has("access-control-allow-origin")) {
|
|
1107
|
+
logger.trace(`skip: already contains "Access-Control-Allow-Origin"`);
|
|
1108
|
+
return true;
|
|
1109
|
+
}
|
|
1110
|
+
const preFlightRequest = isPreFlightRequest(request);
|
|
1111
|
+
if (config) {
|
|
1112
|
+
return handleInternal(exchange, config, preFlightRequest);
|
|
1113
|
+
}
|
|
1114
|
+
if (preFlightRequest) {
|
|
1115
|
+
rejectRequest(response);
|
|
1116
|
+
return false;
|
|
1018
1117
|
}
|
|
1118
|
+
return true;
|
|
1019
1119
|
};
|
|
1020
|
-
var
|
|
1021
|
-
var
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
}
|
|
1037
|
-
try {
|
|
1038
|
-
internal.receive(key, data);
|
|
1039
|
-
} catch (e) {
|
|
1040
|
-
logger3.warn(`[${key}] error processing received data '${data}'`, e);
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
socket.on("close", (code, reason) => {
|
|
1044
|
-
internal.remove(key);
|
|
1045
|
-
logger3.info(`${key} disconnected from relays`);
|
|
1046
|
-
});
|
|
1047
|
-
});
|
|
1048
|
-
return {
|
|
1049
|
-
close: async () => {
|
|
1050
|
-
server.wss.close();
|
|
1120
|
+
var DEFAULT_PERMIT_ALL = ["*"];
|
|
1121
|
+
var DEFAULT_PERMIT_METHODS = ["GET", "HEAD", "POST"];
|
|
1122
|
+
var PERMIT_DEFAULT_CONFIG = {
|
|
1123
|
+
allowOrigins: DEFAULT_PERMIT_ALL,
|
|
1124
|
+
allowMethods: DEFAULT_PERMIT_METHODS,
|
|
1125
|
+
allowHeaders: DEFAULT_PERMIT_ALL,
|
|
1126
|
+
maxAge: 1800
|
|
1127
|
+
// 30 minutes
|
|
1128
|
+
};
|
|
1129
|
+
function validateCorsConfig(config) {
|
|
1130
|
+
if (config) {
|
|
1131
|
+
const allowHeaders = config.allowHeaders;
|
|
1132
|
+
if (allowHeaders && allowHeaders !== ALL) {
|
|
1133
|
+
config = {
|
|
1134
|
+
...config,
|
|
1135
|
+
allowHeaders: allowHeaders.map((header) => header.toLowerCase())
|
|
1136
|
+
};
|
|
1051
1137
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1138
|
+
const allowOrigins = config.allowOrigins;
|
|
1139
|
+
if (allowOrigins) {
|
|
1140
|
+
if (allowOrigins === "*") {
|
|
1141
|
+
validateAllowCredentials(config);
|
|
1142
|
+
validateAllowPrivateNetwork(config);
|
|
1143
|
+
} else {
|
|
1144
|
+
config = {
|
|
1145
|
+
...config,
|
|
1146
|
+
allowOrigins: allowOrigins.map((origin) => {
|
|
1147
|
+
if (typeof origin === "string" && origin !== ALL) {
|
|
1148
|
+
origin = IOGateway5.Filtering.regexify(origin);
|
|
1149
|
+
if (typeof origin === "string") {
|
|
1150
|
+
return trimTrailingSlash(origin).toLowerCase();
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return origin;
|
|
1154
|
+
})
|
|
1155
|
+
};
|
|
1067
1156
|
}
|
|
1068
|
-
});
|
|
1069
|
-
} catch (ex) {
|
|
1070
|
-
logger4.error(`${key} unable to process message`, ex);
|
|
1071
|
-
if (node) {
|
|
1072
|
-
const socket = socketsByNodeId.get(node)?.get(key);
|
|
1073
|
-
socket?.terminate();
|
|
1074
1157
|
}
|
|
1158
|
+
return config;
|
|
1075
1159
|
}
|
|
1076
1160
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1161
|
+
function combine(source, other) {
|
|
1162
|
+
if (other === void 0) {
|
|
1163
|
+
return source !== void 0 ? source === ALL ? [ALL] : source : [];
|
|
1164
|
+
}
|
|
1165
|
+
if (source === void 0) {
|
|
1166
|
+
return other === ALL ? [ALL] : other;
|
|
1167
|
+
}
|
|
1168
|
+
if (source == DEFAULT_PERMIT_ALL || source === DEFAULT_PERMIT_METHODS) {
|
|
1169
|
+
return other === ALL ? [ALL] : other;
|
|
1170
|
+
}
|
|
1171
|
+
if (other == DEFAULT_PERMIT_ALL || other === DEFAULT_PERMIT_METHODS) {
|
|
1172
|
+
return source === ALL ? [ALL] : source;
|
|
1173
|
+
}
|
|
1174
|
+
if (source === ALL || source.includes(ALL) || other === ALL || other.includes(ALL)) {
|
|
1175
|
+
return [ALL];
|
|
1176
|
+
}
|
|
1177
|
+
const combined = /* @__PURE__ */ new Set();
|
|
1178
|
+
source.forEach((v) => combined.add(v));
|
|
1179
|
+
other.forEach((v) => combined.add(v));
|
|
1180
|
+
return Array.from(combined);
|
|
1181
|
+
}
|
|
1182
|
+
var combineCorsConfig = (source, other) => {
|
|
1183
|
+
if (other === void 0) {
|
|
1184
|
+
return source;
|
|
1185
|
+
}
|
|
1186
|
+
const config = {
|
|
1187
|
+
allowOrigins: combine(source.allowOrigins, other?.allowOrigins),
|
|
1188
|
+
allowMethods: combine(source.allowMethods, other?.allowMethods),
|
|
1189
|
+
allowHeaders: combine(source.allowHeaders, other?.allowHeaders),
|
|
1190
|
+
exposeHeaders: combine(source.exposeHeaders, other?.exposeHeaders),
|
|
1191
|
+
allowCredentials: other?.allowCredentials ?? source.allowCredentials,
|
|
1192
|
+
allowPrivateNetwork: other?.allowPrivateNetwork ?? source.allowPrivateNetwork,
|
|
1193
|
+
maxAge: other?.maxAge ?? source.maxAge
|
|
1100
1194
|
};
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
const node = query.get("node");
|
|
1111
|
-
if (node) {
|
|
1112
|
-
let sockets = socketsByNodeId.get(node);
|
|
1113
|
-
if (!sockets) {
|
|
1114
|
-
sockets = /* @__PURE__ */ new Map();
|
|
1115
|
-
socketsByNodeId.set(node, sockets);
|
|
1116
|
-
}
|
|
1117
|
-
sockets.set(key, socket);
|
|
1118
|
-
} else {
|
|
1119
|
-
socket.terminate();
|
|
1195
|
+
return config;
|
|
1196
|
+
};
|
|
1197
|
+
var corsFilter = (opts) => {
|
|
1198
|
+
const source = opts.corsConfigSource;
|
|
1199
|
+
const processor = opts.corsProcessor ?? processRequest;
|
|
1200
|
+
return async (ctx, next) => {
|
|
1201
|
+
const config = await source(ctx);
|
|
1202
|
+
const isValid = processor(ctx, config);
|
|
1203
|
+
if (!isValid || isPreFlightRequest(ctx.request)) {
|
|
1120
1204
|
return;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
logger4.error(`${key} websocket error: ${err}`, err);
|
|
1124
|
-
});
|
|
1125
|
-
socket.on("message", (data, _isBinary) => {
|
|
1126
|
-
if (Array.isArray(data)) {
|
|
1127
|
-
data = Buffer.concat(data);
|
|
1128
|
-
}
|
|
1129
|
-
onMessage2(key, node, socketsByNodeId, data);
|
|
1130
|
-
});
|
|
1131
|
-
socket.on("close", (_code, _reason) => {
|
|
1132
|
-
logger4.info(`${key} disconnected from cluster`);
|
|
1133
|
-
const sockets = socketsByNodeId.get(node);
|
|
1134
|
-
if (sockets) {
|
|
1135
|
-
sockets.delete(key);
|
|
1136
|
-
if (sockets.size === 0) {
|
|
1137
|
-
socketsByNodeId.delete(node);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
});
|
|
1141
|
-
});
|
|
1142
|
-
return {
|
|
1143
|
-
close: async () => {
|
|
1144
|
-
server.wss.close();
|
|
1205
|
+
} else {
|
|
1206
|
+
await next();
|
|
1145
1207
|
}
|
|
1146
1208
|
};
|
|
1209
|
+
};
|
|
1210
|
+
var cors_default = corsFilter;
|
|
1211
|
+
var logger = getLogger("cors");
|
|
1212
|
+
function rejectRequest(response) {
|
|
1213
|
+
response.statusCode = 403;
|
|
1147
1214
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
cors.push([pattern("/api/metrics"), { allowMethods: ["GET", "POST"] }]);
|
|
1157
|
-
if (config.authorize) {
|
|
1158
|
-
authorize.push([pattern("/api/metrics"), config.authorize]);
|
|
1159
|
-
}
|
|
1160
|
-
return [
|
|
1161
|
-
async (ctx, next) => {
|
|
1162
|
-
if (ctx.method === "GET" && ctx.path === "/api/metrics") {
|
|
1163
|
-
ctx.response.statusCode = 200;
|
|
1164
|
-
await ctx.response.end();
|
|
1165
|
-
} else {
|
|
1166
|
-
await next();
|
|
1167
|
-
}
|
|
1168
|
-
},
|
|
1169
|
-
async ({ request, response }, next) => {
|
|
1170
|
-
if (request.method === "POST" && request.path === "/api/metrics") {
|
|
1171
|
-
response.statusCode = 202;
|
|
1172
|
-
await response.end();
|
|
1173
|
-
try {
|
|
1174
|
-
const json = await request.json;
|
|
1175
|
-
const update = json;
|
|
1176
|
-
if (logger5.enabledFor("debug")) {
|
|
1177
|
-
logger5.debug(`${JSON.stringify(update)}`);
|
|
1178
|
-
}
|
|
1179
|
-
if ((config.file?.status ?? false) || update.status === void 0) {
|
|
1180
|
-
await appender.write(update);
|
|
1181
|
-
}
|
|
1182
|
-
} catch (e) {
|
|
1183
|
-
logger5.error(`error processing metrics`, e);
|
|
1184
|
-
}
|
|
1185
|
-
} else {
|
|
1186
|
-
await next();
|
|
1187
|
-
}
|
|
1215
|
+
function handleInternal(exchange, config, preFlightRequest) {
|
|
1216
|
+
const { request, response } = exchange;
|
|
1217
|
+
const responseHeaders = response.headers;
|
|
1218
|
+
const requestOrigin = request.headers.one("origin");
|
|
1219
|
+
const allowOrigin = checkOrigin(config, requestOrigin);
|
|
1220
|
+
if (allowOrigin === void 0) {
|
|
1221
|
+
if (logger.enabledFor("debug")) {
|
|
1222
|
+
logger.debug(`reject: '${requestOrigin}' origin is not allowed`);
|
|
1188
1223
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
var routes_default2 = routes2;
|
|
1192
|
-
|
|
1193
|
-
// src/common/compose.ts
|
|
1194
|
-
function compose(...middleware) {
|
|
1195
|
-
if (!Array.isArray(middleware)) {
|
|
1196
|
-
throw new Error("middleware must be array!");
|
|
1224
|
+
rejectRequest(response);
|
|
1225
|
+
return false;
|
|
1197
1226
|
}
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1204
|
-
return async function(ctx, next) {
|
|
1205
|
-
const dispatch = async (i) => {
|
|
1206
|
-
const fn = i === fns.length ? next : fns[i];
|
|
1207
|
-
if (fn === void 0) {
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
let nextCalled = false;
|
|
1211
|
-
let nextResolved = false;
|
|
1212
|
-
const nextFn = async () => {
|
|
1213
|
-
if (nextCalled) {
|
|
1214
|
-
throw new Error("next() called multiple times");
|
|
1215
|
-
}
|
|
1216
|
-
nextCalled = true;
|
|
1217
|
-
try {
|
|
1218
|
-
return await dispatch(i + 1);
|
|
1219
|
-
} finally {
|
|
1220
|
-
nextResolved = true;
|
|
1221
|
-
}
|
|
1222
|
-
};
|
|
1223
|
-
const result = await fn(ctx, nextFn);
|
|
1224
|
-
if (nextCalled && !nextResolved) {
|
|
1225
|
-
throw new Error("middleware resolved before downstream.\n You are probably missing an await or return statement in your middleware function.");
|
|
1226
|
-
}
|
|
1227
|
-
return result;
|
|
1228
|
-
};
|
|
1229
|
-
return dispatch(0);
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// src/server/address.ts
|
|
1234
|
-
import { networkInterfaces } from "node:os";
|
|
1235
|
-
var PORT_RANGE_MATCHER = /^(\d+|(0x[\da-f]+))(-(\d+|(0x[\da-f]+)))?$/i;
|
|
1236
|
-
function validPort(port) {
|
|
1237
|
-
if (port > 65535) throw new Error(`bad port ${port}`);
|
|
1238
|
-
return port;
|
|
1239
|
-
}
|
|
1240
|
-
function* portRange(port) {
|
|
1241
|
-
if (typeof port === "string") {
|
|
1242
|
-
for (const portRange2 of port.split(",")) {
|
|
1243
|
-
const trimmed = portRange2.trim();
|
|
1244
|
-
const matchResult = PORT_RANGE_MATCHER.exec(trimmed);
|
|
1245
|
-
if (matchResult) {
|
|
1246
|
-
const start2 = parseInt(matchResult[1]);
|
|
1247
|
-
const end = parseInt(matchResult[4] ?? matchResult[1]);
|
|
1248
|
-
for (let i = validPort(start2); i < validPort(end) + 1; i++) {
|
|
1249
|
-
yield i;
|
|
1250
|
-
}
|
|
1251
|
-
} else {
|
|
1252
|
-
throw new Error(`'${portRange2}' is not a valid port or range.`);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
} else {
|
|
1256
|
-
yield validPort(port);
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
var localIp = (() => {
|
|
1260
|
-
function first(a) {
|
|
1261
|
-
return a.length > 0 ? a[0] : void 0;
|
|
1262
|
-
}
|
|
1263
|
-
const addresses = Object.values(networkInterfaces()).flatMap((details) => {
|
|
1264
|
-
return (details ?? []).filter((info2) => info2.family === "IPv4");
|
|
1265
|
-
}).reduce((acc, info2) => {
|
|
1266
|
-
acc[info2.internal ? "internal" : "external"].push(info2);
|
|
1267
|
-
return acc;
|
|
1268
|
-
}, { internal: [], external: [] });
|
|
1269
|
-
return (first(addresses.internal) ?? first(addresses.external))?.address;
|
|
1270
|
-
})();
|
|
1271
|
-
|
|
1272
|
-
// src/server/monitoring.ts
|
|
1273
|
-
import { getHeapStatistics, writeHeapSnapshot } from "node:v8";
|
|
1274
|
-
import { access, mkdir, rename, unlink } from "node:fs/promises";
|
|
1275
|
-
var log2 = getLogger("monitoring");
|
|
1276
|
-
var DEFAULT_OPTIONS = {
|
|
1277
|
-
memoryLimit: 1024 * 1024 * 1024,
|
|
1278
|
-
// 1GB
|
|
1279
|
-
reportInterval: 10 * 60 * 1e3,
|
|
1280
|
-
// 10 min
|
|
1281
|
-
dumpLocation: ".",
|
|
1282
|
-
// current folder
|
|
1283
|
-
maxBackups: 10,
|
|
1284
|
-
dumpPrefix: "Heap"
|
|
1285
|
-
};
|
|
1286
|
-
function fetchStats() {
|
|
1287
|
-
return getHeapStatistics();
|
|
1288
|
-
}
|
|
1289
|
-
async function dumpHeap(opts) {
|
|
1290
|
-
const prefix = opts.dumpPrefix ?? "Heap";
|
|
1291
|
-
const target = `${opts.dumpLocation}/${prefix}.heapsnapshot`;
|
|
1292
|
-
if (log2.enabledFor("debug")) {
|
|
1293
|
-
log2.debug(`starting heap dump in ${target}`);
|
|
1294
|
-
}
|
|
1295
|
-
await fileExists(opts.dumpLocation).catch(async (_) => {
|
|
1296
|
-
if (log2.enabledFor("debug")) {
|
|
1297
|
-
log2.debug(`dump location ${opts.dumpLocation} does not exists. Will try to create it`);
|
|
1298
|
-
}
|
|
1299
|
-
try {
|
|
1300
|
-
await mkdir(opts.dumpLocation, { recursive: true });
|
|
1301
|
-
log2.info(`dump location dir ${opts.dumpLocation} successfully created`);
|
|
1302
|
-
} catch (e) {
|
|
1303
|
-
log2.error(`failed to create dump location ${opts.dumpLocation}`);
|
|
1304
|
-
}
|
|
1305
|
-
});
|
|
1306
|
-
const dumpFileName = writeHeapSnapshot(target);
|
|
1307
|
-
log2.info(`heap dumped`);
|
|
1308
|
-
try {
|
|
1309
|
-
log2.debug(`rolling snapshot backups`);
|
|
1310
|
-
const lastFileName = `${opts.dumpLocation}/${prefix}.${opts.maxBackups}.heapsnapshot`;
|
|
1311
|
-
await fileExists(lastFileName).then(async () => {
|
|
1312
|
-
if (log2.enabledFor("debug")) {
|
|
1313
|
-
log2.debug(`deleting ${lastFileName}`);
|
|
1314
|
-
}
|
|
1315
|
-
try {
|
|
1316
|
-
await unlink(lastFileName);
|
|
1317
|
-
} catch (e) {
|
|
1318
|
-
log2.warn(`failed to delete ${lastFileName}`, e);
|
|
1319
|
-
}
|
|
1320
|
-
}).catch(() => {
|
|
1321
|
-
});
|
|
1322
|
-
for (let i = opts.maxBackups - 1; i > 0; i--) {
|
|
1323
|
-
const currentFileName = `${opts.dumpLocation}/${prefix}.${i}.heapsnapshot`;
|
|
1324
|
-
const nextFileName = `${opts.dumpLocation}/${prefix}.${i + 1}.heapsnapshot`;
|
|
1325
|
-
await fileExists(currentFileName).then(async () => {
|
|
1326
|
-
try {
|
|
1327
|
-
await rename(currentFileName, nextFileName);
|
|
1328
|
-
} catch (e) {
|
|
1329
|
-
log2.warn(`failed to rename ${currentFileName} to ${nextFileName}`, e);
|
|
1330
|
-
}
|
|
1331
|
-
}).catch(() => {
|
|
1332
|
-
});
|
|
1333
|
-
}
|
|
1334
|
-
const firstFileName = `${opts.dumpLocation}/${prefix}.${1}.heapsnapshot`;
|
|
1335
|
-
try {
|
|
1336
|
-
await rename(dumpFileName, firstFileName);
|
|
1337
|
-
} catch (e) {
|
|
1338
|
-
log2.warn(`failed to rename ${dumpFileName} to ${firstFileName}`, e);
|
|
1339
|
-
}
|
|
1340
|
-
log2.debug("snapshots rolled");
|
|
1341
|
-
} catch (e) {
|
|
1342
|
-
log2.error("error rolling backups", e);
|
|
1343
|
-
throw e;
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
async function fileExists(path) {
|
|
1347
|
-
log2.trace(`checking file ${path}`);
|
|
1348
|
-
await access(path);
|
|
1349
|
-
}
|
|
1350
|
-
async function processStats(stats, state, opts) {
|
|
1351
|
-
if (log2.enabledFor("debug")) {
|
|
1352
|
-
log2.debug(`processing heap stats ${JSON.stringify(stats)}`);
|
|
1353
|
-
}
|
|
1354
|
-
const limit = Math.min(opts.memoryLimit, 0.95 * stats.heap_size_limit);
|
|
1355
|
-
const used = stats.used_heap_size;
|
|
1356
|
-
log2.info(`heap stats ${JSON.stringify(stats)}`);
|
|
1357
|
-
if (used >= limit) {
|
|
1358
|
-
log2.warn(`used heap ${used} bytes exceeds memory limit ${limit} bytes`);
|
|
1359
|
-
if (state.memoryLimitExceeded) {
|
|
1360
|
-
delete state.snapshot;
|
|
1361
|
-
} else {
|
|
1362
|
-
state.memoryLimitExceeded = true;
|
|
1363
|
-
state.snapshot = true;
|
|
1364
|
-
}
|
|
1365
|
-
await dumpHeap(opts);
|
|
1366
|
-
} else {
|
|
1367
|
-
state.memoryLimitExceeded = false;
|
|
1368
|
-
delete state.snapshot;
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
function start(opts) {
|
|
1372
|
-
const merged = { ...DEFAULT_OPTIONS, ...opts };
|
|
1373
|
-
let stopped = false;
|
|
1374
|
-
const state = { memoryLimitExceeded: false };
|
|
1375
|
-
const report = async () => {
|
|
1376
|
-
const stats = fetchStats();
|
|
1377
|
-
await processStats(stats, state, merged);
|
|
1378
|
-
};
|
|
1379
|
-
const interval = setInterval(report, merged.reportInterval);
|
|
1380
|
-
const channel = async (command) => {
|
|
1381
|
-
if (!stopped) {
|
|
1382
|
-
command ??= "run";
|
|
1383
|
-
switch (command) {
|
|
1384
|
-
case "run": {
|
|
1385
|
-
await report();
|
|
1386
|
-
break;
|
|
1387
|
-
}
|
|
1388
|
-
case "dump": {
|
|
1389
|
-
await dumpHeap(merged);
|
|
1390
|
-
break;
|
|
1391
|
-
}
|
|
1392
|
-
case "stop": {
|
|
1393
|
-
stopped = true;
|
|
1394
|
-
clearInterval(interval);
|
|
1395
|
-
log2.info("exit memory diagnostic");
|
|
1396
|
-
break;
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
return stopped;
|
|
1401
|
-
};
|
|
1402
|
-
return { ...merged, channel };
|
|
1403
|
-
}
|
|
1404
|
-
async function run({ channel }, command) {
|
|
1405
|
-
if (!await channel(command)) {
|
|
1406
|
-
log2.warn(`cannot execute command "${command}" already closed`);
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
async function stop(m) {
|
|
1410
|
-
return await run(m, "stop");
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
// src/app/ws-client-verify.ts
|
|
1414
|
-
import { IOGateway as IOGateway5 } from "@interopio/gateway";
|
|
1415
|
-
var log3 = getLogger("gateway.ws.client-verify");
|
|
1416
|
-
function acceptsMissing(originFilters) {
|
|
1417
|
-
switch (originFilters.missing) {
|
|
1418
|
-
case "allow":
|
|
1419
|
-
// fall-through
|
|
1420
|
-
case "whitelist":
|
|
1421
|
-
return true;
|
|
1422
|
-
case "block":
|
|
1423
|
-
// fall-through
|
|
1424
|
-
case "blacklist":
|
|
1425
|
-
return false;
|
|
1426
|
-
default:
|
|
1427
|
-
return false;
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
function tryMatch(originFilters, origin) {
|
|
1431
|
-
const block = originFilters.block ?? originFilters["blacklist"];
|
|
1432
|
-
const allow = originFilters.allow ?? originFilters["whitelist"];
|
|
1433
|
-
if (block.length > 0 && IOGateway5.Filtering.valuesMatch(block, origin)) {
|
|
1434
|
-
log3.warn(`origin ${origin} matches block filter`);
|
|
1435
|
-
return false;
|
|
1436
|
-
} else if (allow.length > 0 && IOGateway5.Filtering.valuesMatch(allow, origin)) {
|
|
1437
|
-
if (log3.enabledFor("debug")) {
|
|
1438
|
-
log3.debug(`origin ${origin} matches allow filter`);
|
|
1439
|
-
}
|
|
1440
|
-
return true;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
function acceptsNonMatched(originFilters) {
|
|
1444
|
-
switch (originFilters.non_matched) {
|
|
1445
|
-
case "allow":
|
|
1446
|
-
// fall-through
|
|
1447
|
-
case "whitelist":
|
|
1448
|
-
return true;
|
|
1449
|
-
case "block":
|
|
1450
|
-
// fall-through
|
|
1451
|
-
case "blacklist":
|
|
1452
|
-
return false;
|
|
1453
|
-
default:
|
|
1454
|
-
return false;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
function acceptsOrigin(origin, originFilters) {
|
|
1458
|
-
if (!originFilters) {
|
|
1459
|
-
return true;
|
|
1460
|
-
}
|
|
1461
|
-
if (!origin) {
|
|
1462
|
-
return acceptsMissing(originFilters);
|
|
1463
|
-
} else {
|
|
1464
|
-
const matchResult = tryMatch(originFilters, origin);
|
|
1465
|
-
if (matchResult) {
|
|
1466
|
-
return matchResult;
|
|
1467
|
-
} else {
|
|
1468
|
-
return acceptsNonMatched(originFilters);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
function regexifyOriginFilters(originFilters) {
|
|
1473
|
-
if (originFilters) {
|
|
1474
|
-
const block = (originFilters.block ?? originFilters.blacklist ?? []).map(IOGateway5.Filtering.regexify);
|
|
1475
|
-
const allow = (originFilters.allow ?? originFilters.whitelist ?? []).map(IOGateway5.Filtering.regexify);
|
|
1476
|
-
return {
|
|
1477
|
-
non_matched: originFilters.non_matched ?? "allow",
|
|
1478
|
-
missing: originFilters.missing ?? "allow",
|
|
1479
|
-
allow,
|
|
1480
|
-
block
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
// src/server/server-header.ts
|
|
1486
|
-
import info from "@interopio/gateway-server/package.json" with { type: "json" };
|
|
1487
|
-
var serverHeader = (server) => {
|
|
1488
|
-
server ??= `${info.name} - v${info.version}`;
|
|
1489
|
-
return async ({ response }, next) => {
|
|
1490
|
-
if (server !== false && !response.headers.has("server")) {
|
|
1491
|
-
response.headers.set("Server", server);
|
|
1492
|
-
}
|
|
1493
|
-
await next();
|
|
1494
|
-
};
|
|
1495
|
-
};
|
|
1496
|
-
var server_header_default = (server) => serverHeader(server);
|
|
1497
|
-
|
|
1498
|
-
// src/app/route.ts
|
|
1499
|
-
function findSocketRoute({ request }, { sockets: routes3 }) {
|
|
1500
|
-
const path = request.path ?? "/";
|
|
1501
|
-
const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
|
|
1502
|
-
if (path === "/" && route2.default === true) {
|
|
1503
|
-
return true;
|
|
1504
|
-
}
|
|
1505
|
-
});
|
|
1506
|
-
return [route, path];
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// src/server/security/http-headers.ts
|
|
1510
|
-
var staticServerHttpHeadersWriter = (headers2) => {
|
|
1511
|
-
return async (exchange) => {
|
|
1512
|
-
let containsNoHeaders = true;
|
|
1513
|
-
const { response } = exchange;
|
|
1514
|
-
for (const name of headers2.keys()) {
|
|
1515
|
-
if (response.headers.has(name)) {
|
|
1516
|
-
containsNoHeaders = false;
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
if (containsNoHeaders) {
|
|
1520
|
-
for (const [name, value] of headers2) {
|
|
1521
|
-
response.headers.set(name, value);
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
};
|
|
1525
|
-
};
|
|
1526
|
-
var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1527
|
-
new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
|
|
1528
|
-
);
|
|
1529
|
-
var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1530
|
-
new MapHttpHeaders().add("x-content-type-options", "nosniff")
|
|
1531
|
-
);
|
|
1532
|
-
var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
|
|
1533
|
-
let headerValue = `max-age=${maxAgeInSeconds}`;
|
|
1534
|
-
if (includeSubDomains) {
|
|
1535
|
-
headerValue += " ; includeSubDomains";
|
|
1536
|
-
}
|
|
1537
|
-
if (preload) {
|
|
1538
|
-
headerValue += " ; preload";
|
|
1539
|
-
}
|
|
1540
|
-
const delegate = staticServerHttpHeadersWriter(
|
|
1541
|
-
new MapHttpHeaders().add("strict-transport-security", headerValue)
|
|
1542
|
-
);
|
|
1543
|
-
const isSecure = (exchange) => {
|
|
1544
|
-
const protocol = exchange.request.URL.protocol;
|
|
1545
|
-
return protocol === "https:";
|
|
1546
|
-
};
|
|
1547
|
-
return async (exchange) => {
|
|
1548
|
-
if (isSecure(exchange)) {
|
|
1549
|
-
await delegate(exchange);
|
|
1550
|
-
}
|
|
1551
|
-
};
|
|
1552
|
-
};
|
|
1553
|
-
var frameOptionsServerHttpHeadersWriter = (mode) => {
|
|
1554
|
-
return staticServerHttpHeadersWriter(
|
|
1555
|
-
new MapHttpHeaders().add("x-frame-options", mode)
|
|
1556
|
-
);
|
|
1557
|
-
};
|
|
1558
|
-
var xssProtectionServerHttpHeadersWriter = (headerValue) => staticServerHttpHeadersWriter(
|
|
1559
|
-
new MapHttpHeaders().add("x-xss-protection", headerValue)
|
|
1560
|
-
);
|
|
1561
|
-
var permissionsPolicyServerHttpHeadersWriter = (policyDirectives) => {
|
|
1562
|
-
const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1563
|
-
new MapHttpHeaders().add("permissions-policy", policyDirectives)
|
|
1564
|
-
);
|
|
1565
|
-
return async (exchange) => {
|
|
1566
|
-
if (delegate !== void 0) {
|
|
1567
|
-
await delegate(exchange);
|
|
1568
|
-
}
|
|
1569
|
-
};
|
|
1570
|
-
};
|
|
1571
|
-
var contentSecurityPolicyServerHttpHeadersWriter = (policyDirectives, reportOnly) => {
|
|
1572
|
-
const headerName = reportOnly ? "content-security-policy-report-only" : "content-security-policy";
|
|
1573
|
-
const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1574
|
-
new MapHttpHeaders().add(headerName, policyDirectives)
|
|
1575
|
-
);
|
|
1576
|
-
return async (exchange) => {
|
|
1577
|
-
if (delegate !== void 0) {
|
|
1578
|
-
await delegate(exchange);
|
|
1579
|
-
}
|
|
1580
|
-
};
|
|
1581
|
-
};
|
|
1582
|
-
var refererPolicyServerHttpHeadersWriter = (policy = "no-referrer") => {
|
|
1583
|
-
return staticServerHttpHeadersWriter(
|
|
1584
|
-
new MapHttpHeaders().add("referer-policy", policy)
|
|
1585
|
-
);
|
|
1586
|
-
};
|
|
1587
|
-
var crossOriginOpenerPolicyServerHttpHeadersWriter = (policy) => {
|
|
1588
|
-
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1589
|
-
new MapHttpHeaders().add("cross-origin-opener-policy", policy)
|
|
1590
|
-
);
|
|
1591
|
-
return async (exchange) => {
|
|
1592
|
-
if (delegate !== void 0) {
|
|
1593
|
-
await delegate(exchange);
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
};
|
|
1597
|
-
var crossOriginEmbedderPolicyServerHttpHeadersWriter = (policy) => {
|
|
1598
|
-
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1599
|
-
new MapHttpHeaders().add("cross-origin-embedder-policy", policy)
|
|
1600
|
-
);
|
|
1601
|
-
return async (exchange) => {
|
|
1602
|
-
if (delegate !== void 0) {
|
|
1603
|
-
await delegate(exchange);
|
|
1604
|
-
}
|
|
1605
|
-
};
|
|
1606
|
-
};
|
|
1607
|
-
var crossOriginResourcePolicyServerHttpHeadersWriter = (policy) => {
|
|
1608
|
-
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1609
|
-
new MapHttpHeaders().add("cross-origin-resource-policy", policy)
|
|
1610
|
-
);
|
|
1611
|
-
return async (exchange) => {
|
|
1612
|
-
if (delegate !== void 0) {
|
|
1613
|
-
await delegate(exchange);
|
|
1614
|
-
}
|
|
1615
|
-
};
|
|
1616
|
-
};
|
|
1617
|
-
var compositeServerHttpHeadersWriter = (...writers) => {
|
|
1618
|
-
return async (exchange) => {
|
|
1619
|
-
for (const writer of writers) {
|
|
1620
|
-
await writer(exchange);
|
|
1621
|
-
}
|
|
1622
|
-
};
|
|
1623
|
-
};
|
|
1624
|
-
function headers(opts) {
|
|
1625
|
-
const writers = [];
|
|
1626
|
-
if (!opts?.cache?.disabled) {
|
|
1627
|
-
writers.push(cacheControlServerHttpHeadersWriter());
|
|
1628
|
-
}
|
|
1629
|
-
if (!opts?.contentType?.disabled) {
|
|
1630
|
-
writers.push(contentTypeServerHttpHeadersWriter());
|
|
1631
|
-
}
|
|
1632
|
-
if (!opts?.hsts?.disabled) {
|
|
1633
|
-
writers.push(strictTransportSecurityServerHttpHeadersWriter(opts?.hsts?.maxAge ?? 365 * 24 * 60 * 60, opts?.hsts?.includeSubDomains ?? true, opts?.hsts?.preload ?? false));
|
|
1634
|
-
}
|
|
1635
|
-
if (!opts?.frameOptions?.disabled) {
|
|
1636
|
-
writers.push(frameOptionsServerHttpHeadersWriter(opts?.frameOptions?.mode ?? "DENY"));
|
|
1637
|
-
}
|
|
1638
|
-
if (!opts?.xss?.disabled) {
|
|
1639
|
-
writers.push(xssProtectionServerHttpHeadersWriter(opts?.xss?.headerValue ?? "0"));
|
|
1640
|
-
}
|
|
1641
|
-
if (!opts?.permissionsPolicy?.disabled) {
|
|
1642
|
-
writers.push(permissionsPolicyServerHttpHeadersWriter(opts?.permissionsPolicy?.policyDirectives));
|
|
1643
|
-
}
|
|
1644
|
-
if (!opts?.contentSecurityPolicy?.disabled) {
|
|
1645
|
-
writers.push(contentSecurityPolicyServerHttpHeadersWriter(opts?.contentSecurityPolicy?.policyDirectives ?? "default-src 'self'", opts?.contentSecurityPolicy?.reportOnly));
|
|
1646
|
-
}
|
|
1647
|
-
if (!opts?.refererPolicy?.disabled) {
|
|
1648
|
-
writers.push(refererPolicyServerHttpHeadersWriter(opts?.refererPolicy?.policy ?? "no-referrer"));
|
|
1649
|
-
}
|
|
1650
|
-
if (!opts?.crossOriginOpenerPolicy?.disabled) {
|
|
1651
|
-
writers.push(crossOriginOpenerPolicyServerHttpHeadersWriter(opts?.crossOriginOpenerPolicy?.policy));
|
|
1652
|
-
}
|
|
1653
|
-
if (!opts?.crossOriginEmbedderPolicy?.disabled) {
|
|
1654
|
-
writers.push(crossOriginEmbedderPolicyServerHttpHeadersWriter(opts?.crossOriginEmbedderPolicy?.policy));
|
|
1655
|
-
}
|
|
1656
|
-
if (!opts?.crossOriginResourcePolicy?.disabled) {
|
|
1657
|
-
writers.push(crossOriginResourcePolicyServerHttpHeadersWriter(opts?.crossOriginResourcePolicy?.policy));
|
|
1658
|
-
}
|
|
1659
|
-
if (opts?.writers) {
|
|
1660
|
-
writers.push(...opts.writers);
|
|
1661
|
-
}
|
|
1662
|
-
const writer = compositeServerHttpHeadersWriter(...writers);
|
|
1663
|
-
return async (exchange, next) => {
|
|
1664
|
-
await writer(exchange);
|
|
1665
|
-
await next();
|
|
1666
|
-
};
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
// src/server/security/types.ts
|
|
1670
|
-
var AuthenticationError = class extends Error {
|
|
1671
|
-
_authentication;
|
|
1672
|
-
get authentication() {
|
|
1673
|
-
return this._authentication;
|
|
1674
|
-
}
|
|
1675
|
-
set authentication(value) {
|
|
1676
|
-
if (value === void 0) {
|
|
1677
|
-
throw new TypeError("Authentication cannot be undefined");
|
|
1678
|
-
}
|
|
1679
|
-
this._authentication = value;
|
|
1680
|
-
}
|
|
1681
|
-
};
|
|
1682
|
-
var InsufficientAuthenticationError = class extends AuthenticationError {
|
|
1683
|
-
};
|
|
1684
|
-
var BadCredentialsError = class extends AuthenticationError {
|
|
1685
|
-
};
|
|
1686
|
-
var AccessDeniedError = class extends Error {
|
|
1687
|
-
};
|
|
1688
|
-
var AuthorizationDecision = class {
|
|
1689
|
-
constructor(granted) {
|
|
1690
|
-
this.granted = granted;
|
|
1691
|
-
}
|
|
1692
|
-
granted;
|
|
1693
|
-
};
|
|
1694
|
-
var DefaultAuthorizationManager = class {
|
|
1695
|
-
#check;
|
|
1696
|
-
constructor(check) {
|
|
1697
|
-
this.#check = check;
|
|
1698
|
-
}
|
|
1699
|
-
async verify(authentication, object) {
|
|
1700
|
-
const decision = await this.#check(authentication, object);
|
|
1701
|
-
if (!decision?.granted) {
|
|
1702
|
-
throw new AccessDeniedError("Access denied");
|
|
1227
|
+
const requestMethod = getMethodToUse(request, preFlightRequest);
|
|
1228
|
+
const allowMethods = checkMethods(config, requestMethod);
|
|
1229
|
+
if (allowMethods === void 0) {
|
|
1230
|
+
if (logger.enabledFor("debug")) {
|
|
1231
|
+
logger.debug(`reject: HTTP '${requestMethod}' is not allowed`);
|
|
1703
1232
|
}
|
|
1233
|
+
rejectRequest(response);
|
|
1234
|
+
return false;
|
|
1704
1235
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
};
|
|
1711
|
-
|
|
1712
|
-
// src/server/security/entry-point-failure-handler.ts
|
|
1713
|
-
var serverAuthenticationEntryPointFailureHandler = (opts) => {
|
|
1714
|
-
const entryPoint = opts.entryPoint;
|
|
1715
|
-
const rethrowAuthenticationServiceError = opts?.rethrowAuthenticationServiceError ?? true;
|
|
1716
|
-
return async ({ exchange }, error) => {
|
|
1717
|
-
if (!rethrowAuthenticationServiceError) {
|
|
1718
|
-
return entryPoint(exchange, error);
|
|
1719
|
-
}
|
|
1720
|
-
if (!(error instanceof AuthenticationServiceError)) {
|
|
1721
|
-
return entryPoint(exchange, error);
|
|
1722
|
-
}
|
|
1723
|
-
throw error;
|
|
1724
|
-
};
|
|
1725
|
-
};
|
|
1726
|
-
|
|
1727
|
-
// src/server/security/http-basic-entry-point.ts
|
|
1728
|
-
var DEFAULT_REALM = "Realm";
|
|
1729
|
-
var createHeaderValue = (realm) => {
|
|
1730
|
-
return `Basic realm="${realm}"`;
|
|
1731
|
-
};
|
|
1732
|
-
var httpBasicEntryPoint = (opts) => {
|
|
1733
|
-
const headerValue = createHeaderValue(opts?.realm ?? DEFAULT_REALM);
|
|
1734
|
-
return async (exchange, _error) => {
|
|
1735
|
-
const { response } = exchange;
|
|
1736
|
-
response.statusCode = 401;
|
|
1737
|
-
response.headers.set("WWW-Authenticate", headerValue);
|
|
1738
|
-
};
|
|
1739
|
-
};
|
|
1740
|
-
|
|
1741
|
-
// src/server/security/http-basic-converter.ts
|
|
1742
|
-
var BASIC = "Basic ";
|
|
1743
|
-
var httpBasicAuthenticationConverter = (opts) => {
|
|
1744
|
-
return async (exchange) => {
|
|
1745
|
-
const { request } = exchange;
|
|
1746
|
-
const authorization = request.headers.one("authorization");
|
|
1747
|
-
if (!authorization || !/basic/i.test(authorization.substring(0))) {
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
const credentials = authorization.length <= BASIC.length ? "" : authorization.substring(BASIC.length);
|
|
1751
|
-
const decoded = Buffer.from(credentials, "base64").toString(opts?.credentialsEncoding ?? "utf-8");
|
|
1752
|
-
const parts = decoded.split(":", 2);
|
|
1753
|
-
if (parts.length !== 2) {
|
|
1754
|
-
return void 0;
|
|
1236
|
+
const requestHeaders = getHeadersToUse(request, preFlightRequest);
|
|
1237
|
+
const allowHeaders = checkHeaders(config, requestHeaders);
|
|
1238
|
+
if (preFlightRequest && allowHeaders === void 0) {
|
|
1239
|
+
if (logger.enabledFor("debug")) {
|
|
1240
|
+
logger.debug(`reject: headers '${requestHeaders}' are not allowed`);
|
|
1755
1241
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
};
|
|
1759
|
-
|
|
1760
|
-
// src/server/security/security-context.ts
|
|
1761
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1762
|
-
var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
|
|
1763
|
-
static hasSecurityContext(storage) {
|
|
1764
|
-
return storage.getStore()?.securityContext !== void 0;
|
|
1765
|
-
}
|
|
1766
|
-
static async getSecurityContext(storage) {
|
|
1767
|
-
return await storage.getStore()?.securityContext;
|
|
1768
|
-
}
|
|
1769
|
-
static clearSecurityContext(storage) {
|
|
1770
|
-
delete storage.getStore()?.securityContext;
|
|
1771
|
-
}
|
|
1772
|
-
static withSecurityContext(securityContext) {
|
|
1773
|
-
return (storage = new AsyncLocalStorage()) => {
|
|
1774
|
-
storage.getStore().securityContext = securityContext;
|
|
1775
|
-
return storage;
|
|
1776
|
-
};
|
|
1242
|
+
rejectRequest(response);
|
|
1243
|
+
return false;
|
|
1777
1244
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1245
|
+
responseHeaders.set("Access-Control-Allow-Origin", allowOrigin);
|
|
1246
|
+
if (preFlightRequest) {
|
|
1247
|
+
responseHeaders.set("Access-Control-Allow-Methods", allowMethods.join(","));
|
|
1780
1248
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
return _AsyncStorageSecurityContextHolder.getSecurityContext(storage);
|
|
1784
|
-
}
|
|
1249
|
+
if (preFlightRequest && allowHeaders !== void 0 && allowHeaders.length > 0) {
|
|
1250
|
+
responseHeaders.set("Access-Control-Allow-Headers", allowHeaders.join(", "));
|
|
1785
1251
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
async function authenticate(exchange, next, token, managerResolver, successHandler, storage) {
|
|
1790
|
-
const authManager = await managerResolver(exchange);
|
|
1791
|
-
const authentication = await authManager?.(token);
|
|
1792
|
-
if (authentication === void 0) {
|
|
1793
|
-
throw new Error("No authentication manager found for the exchange");
|
|
1252
|
+
const exposeHeaders = config.exposeHeaders;
|
|
1253
|
+
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
1254
|
+
responseHeaders.set("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
1794
1255
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
} catch (e) {
|
|
1798
|
-
if (e instanceof AuthenticationError) {
|
|
1799
|
-
}
|
|
1800
|
-
throw e;
|
|
1256
|
+
if (config.allowCredentials) {
|
|
1257
|
+
responseHeaders.set("Access-Control-Allow-Credentials", "true");
|
|
1801
1258
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
AsyncStorageSecurityContextHolder.withAuthentication(authentication)(storage);
|
|
1805
|
-
await successHandler(filterExchange, authentication);
|
|
1806
|
-
}
|
|
1807
|
-
function authenticationFilter(opts) {
|
|
1808
|
-
const auth = {
|
|
1809
|
-
matcher: anyExchange,
|
|
1810
|
-
successHandler: async ({ next }) => {
|
|
1811
|
-
await next();
|
|
1812
|
-
},
|
|
1813
|
-
converter: httpBasicAuthenticationConverter({}),
|
|
1814
|
-
failureHandler: serverAuthenticationEntryPointFailureHandler({ entryPoint: httpBasicEntryPoint({}) }),
|
|
1815
|
-
...opts
|
|
1816
|
-
};
|
|
1817
|
-
let managerResolver = auth.managerResolver;
|
|
1818
|
-
if (managerResolver === void 0 && auth.manager !== void 0) {
|
|
1819
|
-
const manager = auth.manager;
|
|
1820
|
-
managerResolver = async (_exchange) => {
|
|
1821
|
-
return manager;
|
|
1822
|
-
};
|
|
1259
|
+
if (config.allowPrivateNetwork && request.headers.one("access-control-request-private-network") === "true") {
|
|
1260
|
+
responseHeaders.set("Access-Control-Allow-Private-Network", "true");
|
|
1823
1261
|
}
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
1262
|
+
if (preFlightRequest && config.maxAge !== void 0) {
|
|
1263
|
+
responseHeaders.set("Access-Control-Max-Age", config.maxAge.toString());
|
|
1826
1264
|
}
|
|
1827
|
-
return
|
|
1828
|
-
const matchResult = await auth.matcher(exchange);
|
|
1829
|
-
const token = matchResult.match ? await auth.converter(exchange) : void 0;
|
|
1830
|
-
if (token === void 0) {
|
|
1831
|
-
await next();
|
|
1832
|
-
return;
|
|
1833
|
-
}
|
|
1834
|
-
try {
|
|
1835
|
-
await authenticate(exchange, next, token, managerResolver, auth.successHandler, auth.storage);
|
|
1836
|
-
} catch (error) {
|
|
1837
|
-
if (error instanceof AuthenticationError) {
|
|
1838
|
-
await auth.failureHandler({ exchange, next }, error);
|
|
1839
|
-
return;
|
|
1840
|
-
}
|
|
1841
|
-
throw error;
|
|
1842
|
-
}
|
|
1843
|
-
};
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
// src/server/security/oauth2/token-error.ts
|
|
1847
|
-
var BearerTokenErrorCodes = {
|
|
1848
|
-
invalid_request: "invalid_request",
|
|
1849
|
-
invalid_token: "invalid_token",
|
|
1850
|
-
insufficient_scope: "insufficient_scope"
|
|
1851
|
-
};
|
|
1852
|
-
var DEFAULT_URI = "https://tools.ietf.org/html/rfc6750#section-3.1";
|
|
1853
|
-
function invalidToken(message) {
|
|
1854
|
-
return {
|
|
1855
|
-
errorCode: BearerTokenErrorCodes.invalid_token,
|
|
1856
|
-
httpStatus: 401,
|
|
1857
|
-
description: message,
|
|
1858
|
-
uri: DEFAULT_URI
|
|
1859
|
-
};
|
|
1860
|
-
}
|
|
1861
|
-
function invalidRequest(message) {
|
|
1862
|
-
return {
|
|
1863
|
-
errorCode: BearerTokenErrorCodes.invalid_request,
|
|
1864
|
-
httpStatus: 400,
|
|
1865
|
-
description: message,
|
|
1866
|
-
uri: DEFAULT_URI
|
|
1867
|
-
};
|
|
1265
|
+
return true;
|
|
1868
1266
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
error;
|
|
1875
|
-
constructor(error, message, options) {
|
|
1876
|
-
super(message ?? (typeof error === "string" ? void 0 : error.description), options);
|
|
1877
|
-
this.error = typeof error === "string" ? { errorCode: error } : error;
|
|
1878
|
-
}
|
|
1879
|
-
};
|
|
1880
|
-
var isBearerTokenAuthenticationToken = (authentication) => {
|
|
1881
|
-
return authentication.type === "BearerToken";
|
|
1882
|
-
};
|
|
1883
|
-
var serverBearerTokenAuthenticationConverter = (opts) => {
|
|
1884
|
-
return async (exchange) => {
|
|
1885
|
-
const { request } = exchange;
|
|
1886
|
-
return Promise.all([
|
|
1887
|
-
resolveFromAuthorizationHeader(request.headers, opts?.headerName).then((token) => token !== void 0 ? [token] : void 0),
|
|
1888
|
-
resolveFromQueryString(request, opts?.uriQueryParameter),
|
|
1889
|
-
resolveFromBody(exchange, opts?.formEncodedBodyParameter)
|
|
1890
|
-
]).then((rs) => rs.filter((r) => r !== void 0).flat(1)).then(resolveToken).then((token) => {
|
|
1891
|
-
if (token) return { authenticated: false, type: "BearerToken", token };
|
|
1892
|
-
});
|
|
1893
|
-
};
|
|
1894
|
-
};
|
|
1895
|
-
async function resolveToken(accessTokens) {
|
|
1896
|
-
if (accessTokens.length === 0) {
|
|
1897
|
-
return;
|
|
1898
|
-
}
|
|
1899
|
-
if (accessTokens.length > 1) {
|
|
1900
|
-
const error = invalidRequest("Found multiple access tokens in the request");
|
|
1901
|
-
throw new Oauth2AuthenticationError(error);
|
|
1902
|
-
}
|
|
1903
|
-
const accessToken = accessTokens[0];
|
|
1904
|
-
if (!accessToken || accessToken.length === 0) {
|
|
1905
|
-
const error = invalidRequest("The requested access token parameter is an empty string");
|
|
1906
|
-
throw new Oauth2AuthenticationError(error);
|
|
1267
|
+
var ALL = "*";
|
|
1268
|
+
var DEFAULT_METHODS = ["GET", "HEAD"];
|
|
1269
|
+
function validateAllowCredentials(config) {
|
|
1270
|
+
if (config.allowCredentials === true && config.allowOrigins === ALL) {
|
|
1271
|
+
throw new Error(`when allowCredentials is true allowOrigins cannot be "*"`);
|
|
1907
1272
|
}
|
|
1908
|
-
return accessToken;
|
|
1909
1273
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
return;
|
|
1274
|
+
function validateAllowPrivateNetwork(config) {
|
|
1275
|
+
if (config.allowPrivateNetwork === true && config.allowOrigins === ALL) {
|
|
1276
|
+
throw new Error(`when allowPrivateNetwork is true allowOrigins cannot be "*"`);
|
|
1914
1277
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1278
|
+
}
|
|
1279
|
+
function checkOrigin(config, origin) {
|
|
1280
|
+
if (origin) {
|
|
1281
|
+
const allowedOrigins = config.allowOrigins;
|
|
1282
|
+
if (allowedOrigins) {
|
|
1283
|
+
if (allowedOrigins === ALL) {
|
|
1284
|
+
validateAllowCredentials(config);
|
|
1285
|
+
validateAllowPrivateNetwork(config);
|
|
1286
|
+
return ALL;
|
|
1287
|
+
}
|
|
1288
|
+
const originToCheck = trimTrailingSlash(origin.toLowerCase());
|
|
1289
|
+
for (const allowedOrigin of allowedOrigins) {
|
|
1290
|
+
if (allowedOrigin === ALL || IOGateway5.Filtering.valueMatches(allowedOrigin, originToCheck)) {
|
|
1291
|
+
return origin;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1919
1295
|
}
|
|
1920
|
-
return match.groups?.token;
|
|
1921
1296
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1297
|
+
function checkMethods(config, requestMethod) {
|
|
1298
|
+
if (requestMethod) {
|
|
1299
|
+
const allowedMethods = config.allowMethods ?? DEFAULT_METHODS;
|
|
1300
|
+
if (allowedMethods === ALL) {
|
|
1301
|
+
return [requestMethod];
|
|
1302
|
+
}
|
|
1303
|
+
if (IOGateway5.Filtering.valuesMatch(allowedMethods, requestMethod)) {
|
|
1304
|
+
return allowedMethods;
|
|
1305
|
+
}
|
|
1926
1306
|
}
|
|
1927
|
-
return accessTokens;
|
|
1928
1307
|
}
|
|
1929
|
-
|
|
1930
|
-
if (
|
|
1308
|
+
function checkHeaders(config, requestHeaders) {
|
|
1309
|
+
if (requestHeaders === void 0) {
|
|
1931
1310
|
return;
|
|
1932
1311
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
const
|
|
1937
|
-
if (
|
|
1312
|
+
if (requestHeaders.length == 0) {
|
|
1313
|
+
return [];
|
|
1314
|
+
}
|
|
1315
|
+
const allowedHeaders = config.allowHeaders;
|
|
1316
|
+
if (allowedHeaders === void 0) {
|
|
1938
1317
|
return;
|
|
1939
1318
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1319
|
+
const allowAnyHeader = allowedHeaders === ALL || allowedHeaders.includes(ALL);
|
|
1320
|
+
const result = [];
|
|
1321
|
+
for (const requestHeader of requestHeaders) {
|
|
1322
|
+
const value = requestHeader?.trim();
|
|
1323
|
+
if (value) {
|
|
1324
|
+
if (allowAnyHeader) {
|
|
1325
|
+
result.push(value);
|
|
1326
|
+
} else {
|
|
1327
|
+
for (const allowedHeader of allowedHeaders) {
|
|
1328
|
+
if (value.toLowerCase() === allowedHeader) {
|
|
1329
|
+
result.push(value);
|
|
1330
|
+
break;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1954
1333
|
}
|
|
1955
|
-
i++;
|
|
1956
1334
|
}
|
|
1957
1335
|
}
|
|
1958
|
-
|
|
1336
|
+
if (result.length > 0) {
|
|
1337
|
+
return result;
|
|
1338
|
+
}
|
|
1959
1339
|
}
|
|
1960
|
-
|
|
1961
|
-
return
|
|
1340
|
+
function trimTrailingSlash(origin) {
|
|
1341
|
+
return origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
1342
|
+
}
|
|
1343
|
+
function getMethodToUse(request, isPreFlight) {
|
|
1344
|
+
return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
|
|
1345
|
+
}
|
|
1346
|
+
function getHeadersToUse(request, isPreFlight) {
|
|
1347
|
+
const headers2 = request.headers;
|
|
1348
|
+
return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
|
|
1349
|
+
}
|
|
1350
|
+
var matchingCorsConfigSource = (opts) => {
|
|
1351
|
+
return async (exchange) => {
|
|
1352
|
+
for (const [matcher, config] of opts.mappings) {
|
|
1353
|
+
if ((await matcher(exchange)).match) {
|
|
1354
|
+
logger.debug(`resolved cors config on '${exchange.request.path}' using ${matcher}: ${JSON.stringify(config)}`);
|
|
1355
|
+
return config;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1962
1359
|
};
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1360
|
+
|
|
1361
|
+
// src/app/cors.ts
|
|
1362
|
+
function mockUpgradeExchange(path) {
|
|
1363
|
+
const request = new MockHttpRequest(path, "GET");
|
|
1364
|
+
request.headers.set("Upgrade", "websocket");
|
|
1365
|
+
request.upgrade = true;
|
|
1366
|
+
return new DefaultWebExchange(request, new MockHttpResponse());
|
|
1367
|
+
}
|
|
1368
|
+
async function createCorsConfigSource(context) {
|
|
1369
|
+
const { sockets: routes, cors } = context;
|
|
1370
|
+
const defaultCorsConfig = combineCorsConfig(PERMIT_DEFAULT_CONFIG, context.corsConfig);
|
|
1371
|
+
const validatedConfigs = [];
|
|
1372
|
+
for (const [path, route] of routes) {
|
|
1373
|
+
let routeCorsConfig = defaultCorsConfig;
|
|
1374
|
+
const upgradeExchange = mockUpgradeExchange(path);
|
|
1375
|
+
for (const [matcher, config] of cors) {
|
|
1376
|
+
if ((await matcher(upgradeExchange)).match) {
|
|
1377
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, config);
|
|
1378
|
+
}
|
|
1968
1379
|
}
|
|
1380
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, {
|
|
1381
|
+
allowOrigins: route.originFilters?.allow,
|
|
1382
|
+
allowMethods: ["GET", "CONNECT"],
|
|
1383
|
+
allowHeaders: ["upgrade", "connection", "origin", "sec-websocket-key", "sec-websocket-version", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
1384
|
+
exposeHeaders: ["sec-websocket-accept", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
1385
|
+
allowCredentials: route.authorize?.access !== "permitted"
|
|
1386
|
+
});
|
|
1387
|
+
validatedConfigs.push([and([upgradeMatcher, pattern(path)]), validateCorsConfig(routeCorsConfig)]);
|
|
1969
1388
|
}
|
|
1970
|
-
|
|
1389
|
+
for (const [matcher, config] of cors) {
|
|
1390
|
+
const routeCorsConfig = combineCorsConfig(defaultCorsConfig, config);
|
|
1391
|
+
validatedConfigs.push([matcher, validateCorsConfig(routeCorsConfig)]);
|
|
1392
|
+
}
|
|
1393
|
+
validatedConfigs.push([pattern(/\/api\/.*/), validateCorsConfig(defaultCorsConfig)]);
|
|
1394
|
+
return matchingCorsConfigSource({ mappings: validatedConfigs });
|
|
1971
1395
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1396
|
+
|
|
1397
|
+
// src/server/security/types.ts
|
|
1398
|
+
function isAuthentication(principal) {
|
|
1399
|
+
return principal !== void 0 && typeof principal["type"] === "string" && typeof principal["authenticated"] === "boolean";
|
|
1400
|
+
}
|
|
1401
|
+
var AuthenticationError = class extends Error {
|
|
1402
|
+
_authentication;
|
|
1403
|
+
get authentication() {
|
|
1404
|
+
return this._authentication;
|
|
1976
1405
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
if (error.description) {
|
|
1981
|
-
parameters.set("error_description", error.description);
|
|
1982
|
-
}
|
|
1983
|
-
if (error.uri) {
|
|
1984
|
-
parameters.set("error_uri", error.uri);
|
|
1406
|
+
set authentication(value) {
|
|
1407
|
+
if (value === void 0) {
|
|
1408
|
+
throw new TypeError("Authentication cannot be undefined");
|
|
1985
1409
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1410
|
+
this._authentication = value;
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
var InsufficientAuthenticationError = class extends AuthenticationError {
|
|
1414
|
+
};
|
|
1415
|
+
var BadCredentialsError = class extends AuthenticationError {
|
|
1416
|
+
};
|
|
1417
|
+
var AccessDeniedError = class extends Error {
|
|
1418
|
+
};
|
|
1419
|
+
var AuthorizationDecision = class {
|
|
1420
|
+
constructor(granted) {
|
|
1421
|
+
this.granted = granted;
|
|
1422
|
+
}
|
|
1423
|
+
granted;
|
|
1424
|
+
};
|
|
1425
|
+
var DefaultAuthorizationManager = class {
|
|
1426
|
+
#check;
|
|
1427
|
+
constructor(check) {
|
|
1428
|
+
this.#check = check;
|
|
1429
|
+
}
|
|
1430
|
+
async verify(authentication, object) {
|
|
1431
|
+
const decision = await this.#check(authentication, object);
|
|
1432
|
+
if (!decision?.granted) {
|
|
1433
|
+
throw new AccessDeniedError("Access denied");
|
|
1988
1434
|
}
|
|
1989
1435
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
const parameters = createParameters(error, opts?.realmName);
|
|
1996
|
-
const wwwAuthenticate = computeWWWAuthenticate(parameters);
|
|
1997
|
-
const { response } = exchange;
|
|
1998
|
-
response.headers.set("WWW-Authenticate", wwwAuthenticate);
|
|
1999
|
-
response.statusCode = status;
|
|
2000
|
-
await response.end();
|
|
2001
|
-
};
|
|
1436
|
+
async authorize(authentication, object) {
|
|
1437
|
+
return await this.#check(authentication, object);
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
var AuthenticationServiceError = class extends AuthenticationError {
|
|
2002
1441
|
};
|
|
2003
|
-
var token_entry_point_default = bearerTokenServerAuthenticationEntryPoint;
|
|
2004
1442
|
|
|
2005
|
-
// src/server/security/
|
|
2006
|
-
var
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
const
|
|
2010
|
-
|
|
1443
|
+
// src/server/security/http-headers.ts
|
|
1444
|
+
var staticServerHttpHeadersWriter = (headers2) => {
|
|
1445
|
+
return async (exchange) => {
|
|
1446
|
+
let containsNoHeaders = true;
|
|
1447
|
+
const { response } = exchange;
|
|
1448
|
+
for (const name of headers2.keys()) {
|
|
1449
|
+
if (response.headers.has(name)) {
|
|
1450
|
+
containsNoHeaders = false;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (containsNoHeaders) {
|
|
1454
|
+
for (const [name, value] of headers2) {
|
|
1455
|
+
response.headers.set(name, value);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
2011
1458
|
};
|
|
2012
1459
|
};
|
|
2013
|
-
var
|
|
2014
|
-
|
|
2015
|
-
|
|
1460
|
+
var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1461
|
+
new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
|
|
1462
|
+
);
|
|
1463
|
+
var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1464
|
+
new MapHttpHeaders().add("x-content-type-options", "nosniff")
|
|
1465
|
+
);
|
|
1466
|
+
var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
|
|
1467
|
+
let headerValue = `max-age=${maxAgeInSeconds}`;
|
|
1468
|
+
if (includeSubDomains) {
|
|
1469
|
+
headerValue += " ; includeSubDomains";
|
|
1470
|
+
}
|
|
1471
|
+
if (preload) {
|
|
1472
|
+
headerValue += " ; preload";
|
|
1473
|
+
}
|
|
1474
|
+
const delegate = staticServerHttpHeadersWriter(
|
|
1475
|
+
new MapHttpHeaders().add("strict-transport-security", headerValue)
|
|
1476
|
+
);
|
|
1477
|
+
const isSecure = (exchange) => {
|
|
1478
|
+
const protocol = exchange.request.URL.protocol;
|
|
1479
|
+
return protocol === "https:";
|
|
1480
|
+
};
|
|
1481
|
+
return async (exchange) => {
|
|
1482
|
+
if (isSecure(exchange)) {
|
|
1483
|
+
await delegate(exchange);
|
|
1484
|
+
}
|
|
2016
1485
|
};
|
|
2017
1486
|
};
|
|
2018
|
-
var
|
|
2019
|
-
|
|
2020
|
-
|
|
1487
|
+
var frameOptionsServerHttpHeadersWriter = (mode) => {
|
|
1488
|
+
return staticServerHttpHeadersWriter(
|
|
1489
|
+
new MapHttpHeaders().add("x-frame-options", mode)
|
|
1490
|
+
);
|
|
2021
1491
|
};
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
if (isBearerTokenAuthenticationToken(authentication)) {
|
|
2033
|
-
const token = authentication.token;
|
|
2034
|
-
try {
|
|
2035
|
-
const jwt = await decoder(token);
|
|
2036
|
-
return await authConverter(jwt);
|
|
2037
|
-
} catch (e) {
|
|
2038
|
-
if (e instanceof JwtError) {
|
|
2039
|
-
throw onError(e);
|
|
2040
|
-
}
|
|
2041
|
-
throw e;
|
|
2042
|
-
}
|
|
1492
|
+
var xssProtectionServerHttpHeadersWriter = (headerValue) => staticServerHttpHeadersWriter(
|
|
1493
|
+
new MapHttpHeaders().add("x-xss-protection", headerValue)
|
|
1494
|
+
);
|
|
1495
|
+
var permissionsPolicyServerHttpHeadersWriter = (policyDirectives) => {
|
|
1496
|
+
const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1497
|
+
new MapHttpHeaders().add("permissions-policy", policyDirectives)
|
|
1498
|
+
);
|
|
1499
|
+
return async (exchange) => {
|
|
1500
|
+
if (delegate !== void 0) {
|
|
1501
|
+
await delegate(exchange);
|
|
2043
1502
|
}
|
|
2044
1503
|
};
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
converter,
|
|
2056
|
-
failureHandler,
|
|
2057
|
-
managerResolver: opts.managerResolver
|
|
2058
|
-
});
|
|
2059
|
-
}
|
|
2060
|
-
if (opts.jwt !== void 0) {
|
|
2061
|
-
const manager = opts.jwt.manager ?? jwtAuthManager(opts.jwt);
|
|
2062
|
-
return authenticationFilter({
|
|
2063
|
-
storage: opts.storage,
|
|
2064
|
-
converter,
|
|
2065
|
-
failureHandler,
|
|
2066
|
-
managerResolver: async (_exchange) => {
|
|
2067
|
-
return manager;
|
|
2068
|
-
}
|
|
2069
|
-
});
|
|
2070
|
-
}
|
|
2071
|
-
throw new Error("Invalid resource server configuration: either managerResolver or jwt must be provided");
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
// src/server/security/http-status-entry-point.ts
|
|
2075
|
-
var httpStatusEntryPoint = (opts) => {
|
|
2076
|
-
return async (exchange, _error) => {
|
|
2077
|
-
const response = exchange.response;
|
|
2078
|
-
response.statusCode = opts.httpStatus.code;
|
|
2079
|
-
response.statusMessage = opts.httpStatus.message;
|
|
1504
|
+
};
|
|
1505
|
+
var contentSecurityPolicyServerHttpHeadersWriter = (policyDirectives, reportOnly) => {
|
|
1506
|
+
const headerName = reportOnly ? "content-security-policy-report-only" : "content-security-policy";
|
|
1507
|
+
const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1508
|
+
new MapHttpHeaders().add(headerName, policyDirectives)
|
|
1509
|
+
);
|
|
1510
|
+
return async (exchange) => {
|
|
1511
|
+
if (delegate !== void 0) {
|
|
1512
|
+
await delegate(exchange);
|
|
1513
|
+
}
|
|
2080
1514
|
};
|
|
2081
1515
|
};
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
}
|
|
2095
|
-
const match = await matcher(exchange);
|
|
2096
|
-
if (match.match) {
|
|
2097
|
-
if (logger6.enabledFor("debug")) {
|
|
2098
|
-
logger6.debug(`match found. using default entry point ${entryPoint}`);
|
|
2099
|
-
}
|
|
2100
|
-
return entryPoint(exchange, error);
|
|
2101
|
-
}
|
|
1516
|
+
var refererPolicyServerHttpHeadersWriter = (policy = "no-referrer") => {
|
|
1517
|
+
return staticServerHttpHeadersWriter(
|
|
1518
|
+
new MapHttpHeaders().add("referer-policy", policy)
|
|
1519
|
+
);
|
|
1520
|
+
};
|
|
1521
|
+
var crossOriginOpenerPolicyServerHttpHeadersWriter = (policy) => {
|
|
1522
|
+
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1523
|
+
new MapHttpHeaders().add("cross-origin-opener-policy", policy)
|
|
1524
|
+
);
|
|
1525
|
+
return async (exchange) => {
|
|
1526
|
+
if (delegate !== void 0) {
|
|
1527
|
+
await delegate(exchange);
|
|
2102
1528
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
1531
|
+
var crossOriginEmbedderPolicyServerHttpHeadersWriter = (policy) => {
|
|
1532
|
+
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1533
|
+
new MapHttpHeaders().add("cross-origin-embedder-policy", policy)
|
|
1534
|
+
);
|
|
1535
|
+
return async (exchange) => {
|
|
1536
|
+
if (delegate !== void 0) {
|
|
1537
|
+
await delegate(exchange);
|
|
2105
1538
|
}
|
|
2106
|
-
return defaultEntryPoint(exchange, error);
|
|
2107
1539
|
};
|
|
2108
1540
|
};
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
1541
|
+
var crossOriginResourcePolicyServerHttpHeadersWriter = (policy) => {
|
|
1542
|
+
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1543
|
+
new MapHttpHeaders().add("cross-origin-resource-policy", policy)
|
|
1544
|
+
);
|
|
1545
|
+
return async (exchange) => {
|
|
1546
|
+
if (delegate !== void 0) {
|
|
1547
|
+
await delegate(exchange);
|
|
2115
1548
|
}
|
|
2116
1549
|
};
|
|
2117
1550
|
};
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
const headers2 = exchange.request.headers;
|
|
2123
|
-
const h = headers2.list("X-Requested-With");
|
|
2124
|
-
if (h.includes("XMLHttpRequest")) {
|
|
2125
|
-
return { match: true };
|
|
1551
|
+
var compositeServerHttpHeadersWriter = (...writers) => {
|
|
1552
|
+
return async (exchange) => {
|
|
1553
|
+
for (const writer of writers) {
|
|
1554
|
+
await writer(exchange);
|
|
2126
1555
|
}
|
|
2127
|
-
return { match: false };
|
|
2128
1556
|
};
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
const manager = opts.manager;
|
|
2135
|
-
const restMatcher = mediaType({
|
|
2136
|
-
mediaTypes: [
|
|
2137
|
-
"application/atom+xml",
|
|
2138
|
-
"application/x-www-form-urlencoded",
|
|
2139
|
-
"application/json",
|
|
2140
|
-
"application/octet-stream",
|
|
2141
|
-
"application/xml",
|
|
2142
|
-
"multipart/form-data",
|
|
2143
|
-
"text/xml"
|
|
2144
|
-
],
|
|
2145
|
-
ignoredMediaTypes: ["*/*"]
|
|
2146
|
-
});
|
|
2147
|
-
const notHtmlMatcher = not(mediaType({ mediaTypes: ["text/html"] }));
|
|
2148
|
-
const restNoHtmlMatcher = and([notHtmlMatcher, restMatcher]);
|
|
2149
|
-
const preferredMatcher = or([xhrMatcher, restNoHtmlMatcher]);
|
|
2150
|
-
opts.defaultEntryPoints.push([preferredMatcher, entryPoint]);
|
|
2151
|
-
const failureHandler = opts.failureHandler ?? serverAuthenticationEntryPointFailureHandler({ entryPoint });
|
|
2152
|
-
const successHandler = delegatingSuccessHandler(opts.successHandlers ?? opts.defaultSuccessHandlers);
|
|
2153
|
-
const converter = httpBasicAuthenticationConverter({});
|
|
2154
|
-
return authenticationFilter({
|
|
2155
|
-
storage: opts.storage,
|
|
2156
|
-
manager,
|
|
2157
|
-
failureHandler,
|
|
2158
|
-
successHandler,
|
|
2159
|
-
converter
|
|
2160
|
-
});
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
// src/server/security/config.ts
|
|
2164
|
-
import { jwtVerifier, JwtVerifyError } from "@interopio/gateway/jose/jwt";
|
|
2165
|
-
|
|
2166
|
-
// src/server/security/error-filter.ts
|
|
2167
|
-
async function commenceAuthentication(exchange, authentication, entryPoint) {
|
|
2168
|
-
const cause = new InsufficientAuthenticationError(`Full authentication is required to access this resource.`);
|
|
2169
|
-
const e = new AuthenticationError("Access Denied", { cause });
|
|
2170
|
-
if (authentication) {
|
|
2171
|
-
e.authentication = authentication;
|
|
1557
|
+
};
|
|
1558
|
+
function headers(opts) {
|
|
1559
|
+
const writers = [];
|
|
1560
|
+
if (!opts?.cache?.disabled) {
|
|
1561
|
+
writers.push(cacheControlServerHttpHeadersWriter());
|
|
2172
1562
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
1563
|
+
if (!opts?.contentType?.disabled) {
|
|
1564
|
+
writers.push(contentTypeServerHttpHeadersWriter());
|
|
1565
|
+
}
|
|
1566
|
+
if (!opts?.hsts?.disabled) {
|
|
1567
|
+
writers.push(strictTransportSecurityServerHttpHeadersWriter(opts?.hsts?.maxAge ?? 365 * 24 * 60 * 60, opts?.hsts?.includeSubDomains ?? true, opts?.hsts?.preload ?? false));
|
|
1568
|
+
}
|
|
1569
|
+
if (!opts?.frameOptions?.disabled) {
|
|
1570
|
+
writers.push(frameOptionsServerHttpHeadersWriter(opts?.frameOptions?.mode ?? "DENY"));
|
|
1571
|
+
}
|
|
1572
|
+
if (!opts?.xss?.disabled) {
|
|
1573
|
+
writers.push(xssProtectionServerHttpHeadersWriter(opts?.xss?.headerValue ?? "0"));
|
|
1574
|
+
}
|
|
1575
|
+
if (!opts?.permissionsPolicy?.disabled) {
|
|
1576
|
+
writers.push(permissionsPolicyServerHttpHeadersWriter(opts?.permissionsPolicy?.policyDirectives));
|
|
1577
|
+
}
|
|
1578
|
+
if (!opts?.contentSecurityPolicy?.disabled) {
|
|
1579
|
+
writers.push(contentSecurityPolicyServerHttpHeadersWriter(opts?.contentSecurityPolicy?.policyDirectives ?? "default-src 'self'", opts?.contentSecurityPolicy?.reportOnly));
|
|
1580
|
+
}
|
|
1581
|
+
if (!opts?.refererPolicy?.disabled) {
|
|
1582
|
+
writers.push(refererPolicyServerHttpHeadersWriter(opts?.refererPolicy?.policy ?? "no-referrer"));
|
|
1583
|
+
}
|
|
1584
|
+
if (!opts?.crossOriginOpenerPolicy?.disabled) {
|
|
1585
|
+
writers.push(crossOriginOpenerPolicyServerHttpHeadersWriter(opts?.crossOriginOpenerPolicy?.policy));
|
|
1586
|
+
}
|
|
1587
|
+
if (!opts?.crossOriginEmbedderPolicy?.disabled) {
|
|
1588
|
+
writers.push(crossOriginEmbedderPolicyServerHttpHeadersWriter(opts?.crossOriginEmbedderPolicy?.policy));
|
|
1589
|
+
}
|
|
1590
|
+
if (!opts?.crossOriginResourcePolicy?.disabled) {
|
|
1591
|
+
writers.push(crossOriginResourcePolicyServerHttpHeadersWriter(opts?.crossOriginResourcePolicy?.policy));
|
|
1592
|
+
}
|
|
1593
|
+
if (opts?.writers) {
|
|
1594
|
+
writers.push(...opts.writers);
|
|
1595
|
+
}
|
|
1596
|
+
const writer = compositeServerHttpHeadersWriter(...writers);
|
|
2188
1597
|
return async (exchange, next) => {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
1598
|
+
await writer(exchange);
|
|
1599
|
+
await next();
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// src/server/security/entry-point-failure-handler.ts
|
|
1604
|
+
var serverAuthenticationEntryPointFailureHandler = (opts) => {
|
|
1605
|
+
const entryPoint = opts.entryPoint;
|
|
1606
|
+
const rethrowAuthenticationServiceError = opts?.rethrowAuthenticationServiceError ?? true;
|
|
1607
|
+
return async ({ exchange }, error) => {
|
|
1608
|
+
if (!rethrowAuthenticationServiceError) {
|
|
1609
|
+
return entryPoint(exchange, error);
|
|
1610
|
+
}
|
|
1611
|
+
if (!(error instanceof AuthenticationServiceError)) {
|
|
1612
|
+
return entryPoint(exchange, error);
|
|
2203
1613
|
}
|
|
1614
|
+
throw error;
|
|
2204
1615
|
};
|
|
2205
1616
|
};
|
|
2206
1617
|
|
|
2207
|
-
// src/server/security/
|
|
2208
|
-
var
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
} catch (error) {
|
|
2219
|
-
if (error instanceof AccessDeniedError) {
|
|
2220
|
-
if (logger7.enabledFor("debug")) {
|
|
2221
|
-
logger7.debug(`authorization failed: ${error.message}`);
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
throw error;
|
|
2225
|
-
}
|
|
2226
|
-
await next();
|
|
1618
|
+
// src/server/security/http-basic-entry-point.ts
|
|
1619
|
+
var DEFAULT_REALM = "Realm";
|
|
1620
|
+
var createHeaderValue = (realm) => {
|
|
1621
|
+
return `Basic realm="${realm}"`;
|
|
1622
|
+
};
|
|
1623
|
+
var httpBasicEntryPoint = (opts) => {
|
|
1624
|
+
const headerValue = createHeaderValue(opts?.realm ?? DEFAULT_REALM);
|
|
1625
|
+
return async (exchange, _error) => {
|
|
1626
|
+
const { response } = exchange;
|
|
1627
|
+
response.statusCode = 401;
|
|
1628
|
+
response.headers.set("WWW-Authenticate", headerValue);
|
|
2227
1629
|
};
|
|
2228
|
-
}
|
|
1630
|
+
};
|
|
2229
1631
|
|
|
2230
|
-
// src/server/security/
|
|
2231
|
-
var
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
const checkResult = await manager.authorize(authentication, { exchange });
|
|
2239
|
-
if (checkResult !== void 0) {
|
|
2240
|
-
decision = checkResult;
|
|
2241
|
-
break;
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
1632
|
+
// src/server/security/http-basic-converter.ts
|
|
1633
|
+
var BASIC = "Basic ";
|
|
1634
|
+
var httpBasicAuthenticationConverter = (opts) => {
|
|
1635
|
+
return async (exchange) => {
|
|
1636
|
+
const { request } = exchange;
|
|
1637
|
+
const authorization = request.headers.one("authorization");
|
|
1638
|
+
if (!authorization || !/basic/i.test(authorization.substring(0))) {
|
|
1639
|
+
return;
|
|
2244
1640
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
1641
|
+
const credentials = authorization.length <= BASIC.length ? "" : authorization.substring(BASIC.length);
|
|
1642
|
+
const decoded = Buffer.from(credentials, "base64").toString(opts?.credentialsEncoding ?? "utf-8");
|
|
1643
|
+
const parts = decoded.split(":", 2);
|
|
1644
|
+
if (parts.length !== 2) {
|
|
1645
|
+
return void 0;
|
|
1646
|
+
}
|
|
1647
|
+
return { type: "UsernamePassword", authenticated: false, principal: parts[0], credentials: parts[1] };
|
|
2247
1648
|
};
|
|
2248
|
-
|
|
2249
|
-
}
|
|
1649
|
+
};
|
|
2250
1650
|
|
|
2251
|
-
// src/server/
|
|
2252
|
-
import {
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
return true;
|
|
1651
|
+
// src/server/security/security-context.ts
|
|
1652
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1653
|
+
var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
|
|
1654
|
+
static hasSecurityContext(storage) {
|
|
1655
|
+
return storage.getStore()?.securityContext !== void 0;
|
|
2257
1656
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
const actualHost = url.host;
|
|
2261
|
-
const originUrl = URL.parse(origin);
|
|
2262
|
-
const originHost = originUrl?.host;
|
|
2263
|
-
const originProtocol = originUrl?.protocol;
|
|
2264
|
-
return actualProtocol === originProtocol && actualHost === originHost;
|
|
2265
|
-
}
|
|
2266
|
-
function isCorsRequest(request) {
|
|
2267
|
-
return request.headers.has("origin") && !isSameOrigin(request);
|
|
2268
|
-
}
|
|
2269
|
-
function isPreFlightRequest(request) {
|
|
2270
|
-
return request.method === "OPTIONS" && request.headers.has("origin") && request.headers.has("access-control-request-method");
|
|
2271
|
-
}
|
|
2272
|
-
var VARY_HEADERS = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"];
|
|
2273
|
-
var processRequest = (exchange, config) => {
|
|
2274
|
-
const { request, response } = exchange;
|
|
2275
|
-
const responseHeaders = response.headers;
|
|
2276
|
-
if (!responseHeaders.has("Vary")) {
|
|
2277
|
-
responseHeaders.set("Vary", VARY_HEADERS.join(", "));
|
|
2278
|
-
} else {
|
|
2279
|
-
const varyHeaders = responseHeaders.list("Vary");
|
|
2280
|
-
for (const header of VARY_HEADERS) {
|
|
2281
|
-
if (!varyHeaders.find((h) => h === header)) {
|
|
2282
|
-
varyHeaders.push(header);
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
responseHeaders.set("Vary", varyHeaders.join(", "));
|
|
1657
|
+
static async getSecurityContext(storage) {
|
|
1658
|
+
return await storage.getStore()?.securityContext;
|
|
2286
1659
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
return true;
|
|
2290
|
-
}
|
|
2291
|
-
} catch (e) {
|
|
2292
|
-
if (logger9.enabledFor("debug")) {
|
|
2293
|
-
logger9.debug(`reject: origin is malformed`);
|
|
2294
|
-
}
|
|
2295
|
-
rejectRequest(response);
|
|
2296
|
-
return false;
|
|
1660
|
+
static clearSecurityContext(storage) {
|
|
1661
|
+
delete storage.getStore()?.securityContext;
|
|
2297
1662
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
1663
|
+
static withSecurityContext(securityContext) {
|
|
1664
|
+
return (storage = new AsyncLocalStorage()) => {
|
|
1665
|
+
storage.getStore().securityContext = securityContext;
|
|
1666
|
+
return storage;
|
|
1667
|
+
};
|
|
2301
1668
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
return handleInternal(exchange, config, preFlightRequest);
|
|
1669
|
+
static withAuthentication(authentication) {
|
|
1670
|
+
return _AsyncStorageSecurityContextHolder.withSecurityContext(Promise.resolve({ authentication }));
|
|
2305
1671
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
1672
|
+
static async getContext(storage) {
|
|
1673
|
+
if (_AsyncStorageSecurityContextHolder.hasSecurityContext(storage)) {
|
|
1674
|
+
return _AsyncStorageSecurityContextHolder.getSecurityContext(storage);
|
|
1675
|
+
}
|
|
2309
1676
|
}
|
|
2310
|
-
return true;
|
|
2311
|
-
};
|
|
2312
|
-
var DEFAULT_PERMIT_ALL = ["*"];
|
|
2313
|
-
var DEFAULT_PERMIT_METHODS = ["GET", "HEAD", "POST"];
|
|
2314
|
-
var PERMIT_DEFAULT_CONFIG = {
|
|
2315
|
-
allowOrigins: DEFAULT_PERMIT_ALL,
|
|
2316
|
-
allowMethods: DEFAULT_PERMIT_METHODS,
|
|
2317
|
-
allowHeaders: DEFAULT_PERMIT_ALL,
|
|
2318
|
-
maxAge: 1800
|
|
2319
|
-
// 30 minutes
|
|
2320
1677
|
};
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
validateAllowCredentials(config);
|
|
2334
|
-
validateAllowPrivateNetwork(config);
|
|
2335
|
-
} else {
|
|
2336
|
-
config = {
|
|
2337
|
-
...config,
|
|
2338
|
-
allowOrigins: allowOrigins.map((origin) => {
|
|
2339
|
-
if (typeof origin === "string" && origin !== ALL) {
|
|
2340
|
-
origin = IOGateway6.Filtering.regexify(origin);
|
|
2341
|
-
if (typeof origin === "string") {
|
|
2342
|
-
return trimTrailingSlash(origin).toLowerCase();
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
return origin;
|
|
2346
|
-
})
|
|
2347
|
-
};
|
|
2348
|
-
}
|
|
1678
|
+
|
|
1679
|
+
// src/server/security/authentication-filter.ts
|
|
1680
|
+
async function authenticate(exchange, next, token, managerResolver, successHandler, storage) {
|
|
1681
|
+
const authManager = await managerResolver(exchange);
|
|
1682
|
+
const authentication = await authManager?.(token);
|
|
1683
|
+
if (authentication === void 0) {
|
|
1684
|
+
throw new Error("No authentication manager found for the exchange");
|
|
1685
|
+
}
|
|
1686
|
+
try {
|
|
1687
|
+
await onAuthenticationSuccess(authentication, { exchange, next }, successHandler, storage);
|
|
1688
|
+
} catch (e) {
|
|
1689
|
+
if (e instanceof AuthenticationError) {
|
|
2349
1690
|
}
|
|
2350
|
-
|
|
1691
|
+
throw e;
|
|
2351
1692
|
}
|
|
2352
1693
|
}
|
|
2353
|
-
function
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
}
|
|
2357
|
-
if (source === void 0) {
|
|
2358
|
-
return other === ALL ? [ALL] : other;
|
|
2359
|
-
}
|
|
2360
|
-
if (source == DEFAULT_PERMIT_ALL || source === DEFAULT_PERMIT_METHODS) {
|
|
2361
|
-
return other === ALL ? [ALL] : other;
|
|
2362
|
-
}
|
|
2363
|
-
if (other == DEFAULT_PERMIT_ALL || other === DEFAULT_PERMIT_METHODS) {
|
|
2364
|
-
return source === ALL ? [ALL] : source;
|
|
2365
|
-
}
|
|
2366
|
-
if (source === ALL || source.includes(ALL) || other === ALL || other.includes(ALL)) {
|
|
2367
|
-
return [ALL];
|
|
2368
|
-
}
|
|
2369
|
-
const combined = /* @__PURE__ */ new Set();
|
|
2370
|
-
source.forEach((v) => combined.add(v));
|
|
2371
|
-
other.forEach((v) => combined.add(v));
|
|
2372
|
-
return Array.from(combined);
|
|
1694
|
+
async function onAuthenticationSuccess(authentication, filterExchange, successHandler, storage) {
|
|
1695
|
+
AsyncStorageSecurityContextHolder.withAuthentication(authentication)(storage);
|
|
1696
|
+
await successHandler(filterExchange, authentication);
|
|
2373
1697
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
allowCredentials: other?.allowCredentials ?? source.allowCredentials,
|
|
2384
|
-
allowPrivateNetwork: other?.allowPrivateNetwork ?? source.allowPrivateNetwork,
|
|
2385
|
-
maxAge: other?.maxAge ?? source.maxAge
|
|
1698
|
+
function authenticationFilter(opts) {
|
|
1699
|
+
const auth = {
|
|
1700
|
+
matcher: anyExchange,
|
|
1701
|
+
successHandler: async ({ next }) => {
|
|
1702
|
+
await next();
|
|
1703
|
+
},
|
|
1704
|
+
converter: httpBasicAuthenticationConverter({}),
|
|
1705
|
+
failureHandler: serverAuthenticationEntryPointFailureHandler({ entryPoint: httpBasicEntryPoint({}) }),
|
|
1706
|
+
...opts
|
|
2386
1707
|
};
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
1708
|
+
let managerResolver = auth.managerResolver;
|
|
1709
|
+
if (managerResolver === void 0 && auth.manager !== void 0) {
|
|
1710
|
+
const manager = auth.manager;
|
|
1711
|
+
managerResolver = async (_exchange) => {
|
|
1712
|
+
return manager;
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
if (managerResolver === void 0) {
|
|
1716
|
+
throw new Error("Authentication filter requires a managerResolver or a manager");
|
|
1717
|
+
}
|
|
1718
|
+
return async (exchange, next) => {
|
|
1719
|
+
const matchResult = await auth.matcher(exchange);
|
|
1720
|
+
const token = matchResult.match ? await auth.converter(exchange) : void 0;
|
|
1721
|
+
if (token === void 0) {
|
|
2398
1722
|
await next();
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
try {
|
|
1726
|
+
await authenticate(exchange, next, token, managerResolver, auth.successHandler, auth.storage);
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
if (error instanceof AuthenticationError) {
|
|
1729
|
+
await auth.failureHandler({ exchange, next }, error);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
throw error;
|
|
2399
1733
|
}
|
|
2400
1734
|
};
|
|
2401
|
-
};
|
|
2402
|
-
var cors_default = corsFilter;
|
|
2403
|
-
var logger9 = getLogger("cors");
|
|
2404
|
-
function rejectRequest(response) {
|
|
2405
|
-
response.statusCode = 403;
|
|
2406
1735
|
}
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
1736
|
+
|
|
1737
|
+
// src/server/security/http-status-entry-point.ts
|
|
1738
|
+
var httpStatusEntryPoint = (opts) => {
|
|
1739
|
+
return async (exchange, _error) => {
|
|
1740
|
+
const response = exchange.response;
|
|
1741
|
+
response.statusCode = opts.httpStatus.code;
|
|
1742
|
+
response.statusMessage = opts.httpStatus.message;
|
|
1743
|
+
};
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1746
|
+
// src/server/security/delegating-entry-point.ts
|
|
1747
|
+
var logger2 = getLogger("auth.entry-point");
|
|
1748
|
+
var delegatingEntryPoint = (opts) => {
|
|
1749
|
+
const defaultEntryPoint = opts.defaultEntryPoint ?? (async ({ response }, _error) => {
|
|
1750
|
+
response.statusCode = 401;
|
|
1751
|
+
await response.end();
|
|
1752
|
+
});
|
|
1753
|
+
return async (exchange, error) => {
|
|
1754
|
+
for (const [matcher, entryPoint] of opts.entryPoints) {
|
|
1755
|
+
if (logger2.enabledFor("debug")) {
|
|
1756
|
+
logger2.debug(`trying to match using: ${matcher}`);
|
|
1757
|
+
}
|
|
1758
|
+
const match2 = await matcher(exchange);
|
|
1759
|
+
if (match2.match) {
|
|
1760
|
+
if (logger2.enabledFor("debug")) {
|
|
1761
|
+
logger2.debug(`match found. using default entry point ${entryPoint}`);
|
|
1762
|
+
}
|
|
1763
|
+
return entryPoint(exchange, error);
|
|
1764
|
+
}
|
|
2415
1765
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
}
|
|
2419
|
-
const requestMethod = getMethodToUse(request, preFlightRequest);
|
|
2420
|
-
const allowMethods = checkMethods(config, requestMethod);
|
|
2421
|
-
if (allowMethods === void 0) {
|
|
2422
|
-
if (logger9.enabledFor("debug")) {
|
|
2423
|
-
logger9.debug(`reject: HTTP '${requestMethod}' is not allowed`);
|
|
1766
|
+
if (logger2.enabledFor("debug")) {
|
|
1767
|
+
logger2.debug(`no match found. using default entry point ${defaultEntryPoint}`);
|
|
2424
1768
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
1769
|
+
return defaultEntryPoint(exchange, error);
|
|
1770
|
+
};
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
// src/server/security/delegating-success-handler.ts
|
|
1774
|
+
var delegatingSuccessHandler = (handlers) => {
|
|
1775
|
+
return async ({ exchange, next }, authentication) => {
|
|
1776
|
+
for (const handler of handlers) {
|
|
1777
|
+
await handler({ exchange, next }, authentication);
|
|
2433
1778
|
}
|
|
2434
|
-
|
|
2435
|
-
|
|
1779
|
+
};
|
|
1780
|
+
};
|
|
1781
|
+
|
|
1782
|
+
// src/server/security/http-basic.ts
|
|
1783
|
+
function httpBasic(opts) {
|
|
1784
|
+
const xhrMatcher = async (exchange) => {
|
|
1785
|
+
const headers2 = exchange.request.headers;
|
|
1786
|
+
const h = headers2.list("X-Requested-With");
|
|
1787
|
+
if (h.includes("XMLHttpRequest")) {
|
|
1788
|
+
return { match: true };
|
|
1789
|
+
}
|
|
1790
|
+
return { match: false };
|
|
1791
|
+
};
|
|
1792
|
+
const defaultEntryPoint = delegatingEntryPoint({
|
|
1793
|
+
entryPoints: [[xhrMatcher, httpStatusEntryPoint({ httpStatus: { code: 401 } })]],
|
|
1794
|
+
defaultEntryPoint: httpBasicEntryPoint({})
|
|
1795
|
+
});
|
|
1796
|
+
const entryPoint = opts.entryPoint ?? defaultEntryPoint;
|
|
1797
|
+
const manager = opts.manager;
|
|
1798
|
+
const restMatcher = mediaType({
|
|
1799
|
+
mediaTypes: [
|
|
1800
|
+
"application/atom+xml",
|
|
1801
|
+
"application/x-www-form-urlencoded",
|
|
1802
|
+
"application/json",
|
|
1803
|
+
"application/octet-stream",
|
|
1804
|
+
"application/xml",
|
|
1805
|
+
"multipart/form-data",
|
|
1806
|
+
"text/xml"
|
|
1807
|
+
],
|
|
1808
|
+
ignoredMediaTypes: ["*/*"]
|
|
1809
|
+
});
|
|
1810
|
+
const notHtmlMatcher = not(mediaType({ mediaTypes: ["text/html"] }));
|
|
1811
|
+
const restNoHtmlMatcher = and([notHtmlMatcher, restMatcher]);
|
|
1812
|
+
const preferredMatcher = or([xhrMatcher, restNoHtmlMatcher]);
|
|
1813
|
+
opts.defaultEntryPoints.push([preferredMatcher, entryPoint]);
|
|
1814
|
+
const failureHandler = opts.failureHandler ?? serverAuthenticationEntryPointFailureHandler({ entryPoint });
|
|
1815
|
+
const successHandler = delegatingSuccessHandler(opts.successHandlers ?? opts.defaultSuccessHandlers);
|
|
1816
|
+
const converter = httpBasicAuthenticationConverter({});
|
|
1817
|
+
return authenticationFilter({
|
|
1818
|
+
storage: opts.storage,
|
|
1819
|
+
manager,
|
|
1820
|
+
failureHandler,
|
|
1821
|
+
successHandler,
|
|
1822
|
+
converter
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// src/server/security/oauth2/token-error.ts
|
|
1827
|
+
var BearerTokenErrorCodes = {
|
|
1828
|
+
invalid_request: "invalid_request",
|
|
1829
|
+
invalid_token: "invalid_token",
|
|
1830
|
+
insufficient_scope: "insufficient_scope"
|
|
1831
|
+
};
|
|
1832
|
+
var DEFAULT_URI = "https://tools.ietf.org/html/rfc6750#section-3.1";
|
|
1833
|
+
function invalidToken(message) {
|
|
1834
|
+
return {
|
|
1835
|
+
errorCode: BearerTokenErrorCodes.invalid_token,
|
|
1836
|
+
httpStatus: 401,
|
|
1837
|
+
description: message,
|
|
1838
|
+
uri: DEFAULT_URI
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function invalidRequest(message) {
|
|
1842
|
+
return {
|
|
1843
|
+
errorCode: BearerTokenErrorCodes.invalid_request,
|
|
1844
|
+
httpStatus: 400,
|
|
1845
|
+
description: message,
|
|
1846
|
+
uri: DEFAULT_URI
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// src/server/security/oauth2/token-converter.ts
|
|
1851
|
+
var ACCESS_TOKEN_PARAMETER_NAME = "access_token";
|
|
1852
|
+
var authorizationPattern = /^Bearer\s+(?<token>[a-zA-Z0-9-._~+/]+=*)$/i;
|
|
1853
|
+
var Oauth2AuthenticationError = class extends AuthenticationError {
|
|
1854
|
+
error;
|
|
1855
|
+
constructor(error, message, options) {
|
|
1856
|
+
super(message ?? (typeof error === "string" ? void 0 : error.description), options);
|
|
1857
|
+
this.error = typeof error === "string" ? { errorCode: error } : error;
|
|
2436
1858
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
1859
|
+
};
|
|
1860
|
+
var isBearerTokenAuthenticationToken = (authentication) => {
|
|
1861
|
+
return authentication.type === "BearerToken";
|
|
1862
|
+
};
|
|
1863
|
+
var serverBearerTokenAuthenticationConverter = (opts) => {
|
|
1864
|
+
return async (exchange) => {
|
|
1865
|
+
const { request } = exchange;
|
|
1866
|
+
return Promise.all([
|
|
1867
|
+
resolveFromAuthorizationHeader(request.headers, opts?.headerName).then((token) => token !== void 0 ? [token] : void 0),
|
|
1868
|
+
resolveFromQueryString(request, opts?.uriQueryParameter),
|
|
1869
|
+
resolveFromBody(exchange, opts?.formEncodedBodyParameter)
|
|
1870
|
+
]).then((rs) => rs.filter((r) => r !== void 0).flat(1)).then(resolveToken).then((token) => {
|
|
1871
|
+
if (token) return { authenticated: false, type: "BearerToken", token };
|
|
1872
|
+
});
|
|
1873
|
+
};
|
|
1874
|
+
};
|
|
1875
|
+
async function resolveToken(accessTokens) {
|
|
1876
|
+
if (accessTokens.length === 0) {
|
|
1877
|
+
return;
|
|
2440
1878
|
}
|
|
2441
|
-
if (
|
|
2442
|
-
|
|
1879
|
+
if (accessTokens.length > 1) {
|
|
1880
|
+
const error = invalidRequest("Found multiple access tokens in the request");
|
|
1881
|
+
throw new Oauth2AuthenticationError(error);
|
|
2443
1882
|
}
|
|
2444
|
-
const
|
|
2445
|
-
if (
|
|
2446
|
-
|
|
1883
|
+
const accessToken = accessTokens[0];
|
|
1884
|
+
if (!accessToken || accessToken.length === 0) {
|
|
1885
|
+
const error = invalidRequest("The requested access token parameter is an empty string");
|
|
1886
|
+
throw new Oauth2AuthenticationError(error);
|
|
2447
1887
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
1888
|
+
return accessToken;
|
|
1889
|
+
}
|
|
1890
|
+
async function resolveFromAuthorizationHeader(headers2, headerName = "authorization") {
|
|
1891
|
+
const authorization = headers2.one(headerName);
|
|
1892
|
+
if (!authorization || !/bearer/i.test(authorization.substring(0))) {
|
|
1893
|
+
return;
|
|
2450
1894
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
1895
|
+
const match2 = authorizationPattern.exec(authorization);
|
|
1896
|
+
if (match2 === null) {
|
|
1897
|
+
const error = invalidToken("Bearer token is malformed");
|
|
1898
|
+
throw new Oauth2AuthenticationError(error);
|
|
2453
1899
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
1900
|
+
return match2.groups?.token;
|
|
1901
|
+
}
|
|
1902
|
+
async function resolveTokens(parameters) {
|
|
1903
|
+
const accessTokens = parameters.getAll(ACCESS_TOKEN_PARAMETER_NAME);
|
|
1904
|
+
if (accessTokens.length === 0) {
|
|
1905
|
+
return;
|
|
2456
1906
|
}
|
|
2457
|
-
return
|
|
1907
|
+
return accessTokens;
|
|
2458
1908
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
if (config.allowCredentials === true && config.allowOrigins === ALL) {
|
|
2463
|
-
throw new Error(`when allowCredentials is true allowOrigins cannot be "*"`);
|
|
1909
|
+
async function resolveFromQueryString(request, allow = false) {
|
|
1910
|
+
if (!allow || request.method !== "GET") {
|
|
1911
|
+
return;
|
|
2464
1912
|
}
|
|
1913
|
+
return resolveTokens(request.URL.searchParams);
|
|
2465
1914
|
}
|
|
2466
|
-
function
|
|
2467
|
-
|
|
2468
|
-
|
|
1915
|
+
async function resolveFromBody(exchange, allow = false) {
|
|
1916
|
+
const { request } = exchange;
|
|
1917
|
+
if (!allow || "application/x-www-form-urlencoded" !== request.headers.one("content-type") || request.method !== "POST") {
|
|
1918
|
+
return;
|
|
2469
1919
|
}
|
|
1920
|
+
return resolveTokens(await exchange.request.formData);
|
|
2470
1921
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
return origin;
|
|
2484
|
-
}
|
|
1922
|
+
var token_converter_default = serverBearerTokenAuthenticationConverter;
|
|
1923
|
+
|
|
1924
|
+
// src/server/security/oauth2/token-entry-point.ts
|
|
1925
|
+
function computeWWWAuthenticate(parameters) {
|
|
1926
|
+
let wwwAuthenticate = "Bearer";
|
|
1927
|
+
if (parameters.size !== 0) {
|
|
1928
|
+
wwwAuthenticate += " ";
|
|
1929
|
+
let i = 0;
|
|
1930
|
+
for (const [key, value] of parameters) {
|
|
1931
|
+
wwwAuthenticate += `${key}="${value}"`;
|
|
1932
|
+
if (i !== parameters.size - 1) {
|
|
1933
|
+
wwwAuthenticate += ", ";
|
|
2485
1934
|
}
|
|
1935
|
+
i++;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return wwwAuthenticate;
|
|
1939
|
+
}
|
|
1940
|
+
var isBearerTokenError = (error) => {
|
|
1941
|
+
return error.httpStatus !== void 0;
|
|
1942
|
+
};
|
|
1943
|
+
function getStatus(authError) {
|
|
1944
|
+
if (authError instanceof Oauth2AuthenticationError) {
|
|
1945
|
+
const { error } = authError;
|
|
1946
|
+
if (isBearerTokenError(error)) {
|
|
1947
|
+
return error.httpStatus;
|
|
2486
1948
|
}
|
|
2487
1949
|
}
|
|
1950
|
+
return 401;
|
|
2488
1951
|
}
|
|
2489
|
-
function
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
1952
|
+
function createParameters(authError, realm) {
|
|
1953
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
1954
|
+
if (realm) {
|
|
1955
|
+
parameters.set("realm", realm);
|
|
1956
|
+
}
|
|
1957
|
+
if (authError instanceof Oauth2AuthenticationError) {
|
|
1958
|
+
const { error } = authError;
|
|
1959
|
+
parameters.set("error", error.errorCode);
|
|
1960
|
+
if (error.description) {
|
|
1961
|
+
parameters.set("error_description", error.description);
|
|
2494
1962
|
}
|
|
2495
|
-
if (
|
|
2496
|
-
|
|
1963
|
+
if (error.uri) {
|
|
1964
|
+
parameters.set("error_uri", error.uri);
|
|
1965
|
+
}
|
|
1966
|
+
if (isBearerTokenError(error) && error.scope) {
|
|
1967
|
+
parameters.set("scope", error.scope);
|
|
2497
1968
|
}
|
|
2498
1969
|
}
|
|
1970
|
+
return parameters;
|
|
2499
1971
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
1972
|
+
var bearerTokenServerAuthenticationEntryPoint = (opts) => {
|
|
1973
|
+
return async (exchange, error) => {
|
|
1974
|
+
const status = getStatus(error);
|
|
1975
|
+
const parameters = createParameters(error, opts?.realmName);
|
|
1976
|
+
const wwwAuthenticate = computeWWWAuthenticate(parameters);
|
|
1977
|
+
const { response } = exchange;
|
|
1978
|
+
response.headers.set("WWW-Authenticate", wwwAuthenticate);
|
|
1979
|
+
response.statusCode = status;
|
|
1980
|
+
await response.end();
|
|
1981
|
+
};
|
|
1982
|
+
};
|
|
1983
|
+
var token_entry_point_default = bearerTokenServerAuthenticationEntryPoint;
|
|
1984
|
+
|
|
1985
|
+
// src/server/security/oauth2/jwt-auth-manager.ts
|
|
1986
|
+
var jwtAuthConverter = (opts) => {
|
|
1987
|
+
const principalClaimName = opts?.principalClaimName ?? "sub";
|
|
1988
|
+
return (jwt) => {
|
|
1989
|
+
const name = jwt.getClaimAsString(principalClaimName);
|
|
1990
|
+
return { type: "JwtToken", authenticated: true, name };
|
|
1991
|
+
};
|
|
1992
|
+
};
|
|
1993
|
+
var asyncJwtConverter = (converter) => {
|
|
1994
|
+
return async (jwt) => {
|
|
1995
|
+
return converter(jwt);
|
|
1996
|
+
};
|
|
1997
|
+
};
|
|
1998
|
+
var JwtError = class extends Error {
|
|
1999
|
+
};
|
|
2000
|
+
var BadJwtError = class extends JwtError {
|
|
2001
|
+
};
|
|
2002
|
+
function onError(error) {
|
|
2003
|
+
if (error instanceof BadJwtError) {
|
|
2004
|
+
return new Oauth2AuthenticationError(invalidToken(error.message), error.message, { cause: error });
|
|
2510
2005
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2006
|
+
throw new AuthenticationServiceError(error.message, { cause: error });
|
|
2007
|
+
}
|
|
2008
|
+
function jwtAuthManager(opts) {
|
|
2009
|
+
const decoder = opts.decoder;
|
|
2010
|
+
const authConverter = opts.authConverter ?? asyncJwtConverter(jwtAuthConverter({}));
|
|
2011
|
+
return async (authentication) => {
|
|
2012
|
+
if (isBearerTokenAuthenticationToken(authentication)) {
|
|
2013
|
+
const token = authentication.token;
|
|
2014
|
+
try {
|
|
2015
|
+
const jwt = await decoder(token);
|
|
2016
|
+
return await authConverter(jwt);
|
|
2017
|
+
} catch (e) {
|
|
2018
|
+
if (e instanceof JwtError) {
|
|
2019
|
+
throw onError(e);
|
|
2524
2020
|
}
|
|
2021
|
+
throw e;
|
|
2525
2022
|
}
|
|
2526
2023
|
}
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// src/server/security/oauth2-resource-server.ts
|
|
2028
|
+
function resourceServer(opts) {
|
|
2029
|
+
const entryPoint = opts.entryPoint ?? token_entry_point_default({});
|
|
2030
|
+
const converter = opts?.converter ?? token_converter_default({});
|
|
2031
|
+
const failureHandler = opts.failureHandler ?? serverAuthenticationEntryPointFailureHandler({ entryPoint });
|
|
2032
|
+
if (opts.managerResolver !== void 0) {
|
|
2033
|
+
return authenticationFilter({
|
|
2034
|
+
storage: opts.storage,
|
|
2035
|
+
converter,
|
|
2036
|
+
failureHandler,
|
|
2037
|
+
managerResolver: opts.managerResolver
|
|
2038
|
+
});
|
|
2527
2039
|
}
|
|
2528
|
-
if (
|
|
2529
|
-
|
|
2040
|
+
if (opts.jwt !== void 0) {
|
|
2041
|
+
const manager = opts.jwt.manager ?? jwtAuthManager(opts.jwt);
|
|
2042
|
+
return authenticationFilter({
|
|
2043
|
+
storage: opts.storage,
|
|
2044
|
+
converter,
|
|
2045
|
+
failureHandler,
|
|
2046
|
+
managerResolver: async (_exchange) => {
|
|
2047
|
+
return manager;
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2530
2050
|
}
|
|
2051
|
+
throw new Error("Invalid resource server configuration: either managerResolver or jwt must be provided");
|
|
2531
2052
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2053
|
+
|
|
2054
|
+
// src/server/security/config.ts
|
|
2055
|
+
import { jwtVerifier, JwtVerifyError } from "@interopio/gateway/jose/jwt";
|
|
2056
|
+
|
|
2057
|
+
// src/server/security/error-filter.ts
|
|
2058
|
+
async function commenceAuthentication(exchange, authentication, entryPoint) {
|
|
2059
|
+
const cause = new InsufficientAuthenticationError(`Full authentication is required to access this resource.`);
|
|
2060
|
+
const e = new AuthenticationError("Access Denied", { cause });
|
|
2061
|
+
if (authentication) {
|
|
2062
|
+
e.authentication = authentication;
|
|
2063
|
+
}
|
|
2064
|
+
await entryPoint(exchange, e);
|
|
2537
2065
|
}
|
|
2538
|
-
function
|
|
2539
|
-
|
|
2540
|
-
|
|
2066
|
+
function httpStatusAccessDeniedHandler(status, message) {
|
|
2067
|
+
return async (exchange, _error) => {
|
|
2068
|
+
exchange.response.statusCode = status;
|
|
2069
|
+
exchange.response.statusMessage = message;
|
|
2070
|
+
exchange.response.headers.set("Content-Type", "text/plain");
|
|
2071
|
+
const bytes = Buffer.from("Access Denied", "utf-8");
|
|
2072
|
+
exchange.response.headers.set("Content-Length", bytes.length);
|
|
2073
|
+
await exchange.response.end(bytes);
|
|
2074
|
+
};
|
|
2541
2075
|
}
|
|
2542
|
-
var
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2076
|
+
var errorFilter = (opts) => {
|
|
2077
|
+
const accessDeniedHandler = httpStatusAccessDeniedHandler(403, "Forbidden");
|
|
2078
|
+
const authenticationEntryPoint = opts.authenticationEntryPoint ?? httpBasicEntryPoint();
|
|
2079
|
+
return async (exchange, next) => {
|
|
2080
|
+
try {
|
|
2081
|
+
await next();
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
if (error instanceof AccessDeniedError) {
|
|
2084
|
+
const principal = await exchange.principal();
|
|
2085
|
+
if (!isAuthentication(principal)) {
|
|
2086
|
+
await commenceAuthentication(exchange, void 0, authenticationEntryPoint);
|
|
2087
|
+
} else {
|
|
2088
|
+
if (!principal.authenticated) {
|
|
2089
|
+
return accessDeniedHandler(exchange, error);
|
|
2090
|
+
}
|
|
2091
|
+
await commenceAuthentication(exchange, principal, authenticationEntryPoint);
|
|
2092
|
+
}
|
|
2548
2093
|
}
|
|
2549
2094
|
}
|
|
2550
2095
|
};
|
|
2551
2096
|
};
|
|
2552
2097
|
|
|
2098
|
+
// src/server/security/delegating-authorization-manager.ts
|
|
2099
|
+
var logger3 = getLogger("auth");
|
|
2100
|
+
function delegatingAuthorizationManager(opts) {
|
|
2101
|
+
const check = async (authentication, exchange) => {
|
|
2102
|
+
let decision;
|
|
2103
|
+
for (const [matcher, manager] of opts.mappings) {
|
|
2104
|
+
if ((await matcher(exchange))?.match) {
|
|
2105
|
+
logger3.debug(`checking authorization on '${exchange.request.path}' using [${matcher}, ${manager}]`);
|
|
2106
|
+
const checkResult = await manager.authorize(authentication, { exchange });
|
|
2107
|
+
if (checkResult !== void 0) {
|
|
2108
|
+
decision = checkResult;
|
|
2109
|
+
break;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
decision ??= new AuthorizationDecision(false);
|
|
2114
|
+
return decision;
|
|
2115
|
+
};
|
|
2116
|
+
return new DefaultAuthorizationManager(check);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
// src/server/security/authorization-filter.ts
|
|
2120
|
+
var logger4 = getLogger("security.auth");
|
|
2121
|
+
function authorizationFilter(opts) {
|
|
2122
|
+
const { manager, storage } = opts;
|
|
2123
|
+
return async (exchange, next) => {
|
|
2124
|
+
const promise = AsyncStorageSecurityContextHolder.getContext(storage).then((c) => c?.authentication);
|
|
2125
|
+
try {
|
|
2126
|
+
await manager.verify(promise, exchange);
|
|
2127
|
+
if (logger4.enabledFor("debug")) {
|
|
2128
|
+
logger4.debug("authorization successful");
|
|
2129
|
+
}
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
if (error instanceof AccessDeniedError) {
|
|
2132
|
+
if (logger4.enabledFor("debug")) {
|
|
2133
|
+
logger4.debug(`authorization failed: ${error.message}`);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
throw error;
|
|
2137
|
+
}
|
|
2138
|
+
await next();
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2553
2142
|
// src/server/security/config.ts
|
|
2554
2143
|
var filterOrder = {
|
|
2555
2144
|
first: Number.MAX_SAFE_INTEGER,
|
|
@@ -2717,42 +2306,6 @@ var config_default = (config, context) => {
|
|
|
2717
2306
|
return middleware;
|
|
2718
2307
|
};
|
|
2719
2308
|
|
|
2720
|
-
// src/app/cors.ts
|
|
2721
|
-
function mockUpgradeExchange(path) {
|
|
2722
|
-
const request = new MockHttpRequest(path, "GET");
|
|
2723
|
-
request.headers.set("upgrade", "websocket");
|
|
2724
|
-
request["_req"] = { upgrade: true };
|
|
2725
|
-
return new DefaultWebExchange(request, new MockHttpResponse());
|
|
2726
|
-
}
|
|
2727
|
-
async function createCorsConfigSource(context) {
|
|
2728
|
-
const { sockets: routes3, cors } = context;
|
|
2729
|
-
const defaultCorsConfig = combineCorsConfig(PERMIT_DEFAULT_CONFIG, context.corsConfig);
|
|
2730
|
-
const validatedConfigs = [];
|
|
2731
|
-
for (const [path, route] of routes3) {
|
|
2732
|
-
let routeCorsConfig = defaultCorsConfig;
|
|
2733
|
-
const upgradeExchange = mockUpgradeExchange(path);
|
|
2734
|
-
for (const [matcher, config] of cors) {
|
|
2735
|
-
if ((await matcher(upgradeExchange)).match) {
|
|
2736
|
-
routeCorsConfig = combineCorsConfig(routeCorsConfig, config);
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
routeCorsConfig = combineCorsConfig(routeCorsConfig, {
|
|
2740
|
-
allowOrigins: route.originFilters?.allow,
|
|
2741
|
-
allowMethods: ["GET", "CONNECT"],
|
|
2742
|
-
allowHeaders: ["upgrade", "connection", "origin", "sec-websocket-key", "sec-websocket-version", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2743
|
-
exposeHeaders: ["sec-websocket-accept", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2744
|
-
allowCredentials: route.authorize?.access !== "permitted"
|
|
2745
|
-
});
|
|
2746
|
-
validatedConfigs.push([and([upgradeMatcher, pattern(path)]), validateCorsConfig(routeCorsConfig)]);
|
|
2747
|
-
}
|
|
2748
|
-
for (const [matcher, config] of cors) {
|
|
2749
|
-
const routeCorsConfig = combineCorsConfig(defaultCorsConfig, config);
|
|
2750
|
-
validatedConfigs.push([matcher, validateCorsConfig(routeCorsConfig)]);
|
|
2751
|
-
}
|
|
2752
|
-
validatedConfigs.push([pattern(/\/api\/.*/), validateCorsConfig(defaultCorsConfig)]);
|
|
2753
|
-
return matchingCorsConfigSource({ mappings: validatedConfigs });
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
2309
|
// src/app/auth.ts
|
|
2757
2310
|
function createSecurityConfig(context) {
|
|
2758
2311
|
const authorize = [];
|
|
@@ -2790,7 +2343,7 @@ async function httpSecurity(context) {
|
|
|
2790
2343
|
}
|
|
2791
2344
|
|
|
2792
2345
|
// src/server.ts
|
|
2793
|
-
var
|
|
2346
|
+
var logger5 = getLogger("app");
|
|
2794
2347
|
function secureContextOptions(ssl) {
|
|
2795
2348
|
const options = {};
|
|
2796
2349
|
if (ssl.key) options.key = readFileSync(ssl.key);
|
|
@@ -2798,7 +2351,7 @@ function secureContextOptions(ssl) {
|
|
|
2798
2351
|
if (ssl.ca) options.ca = readFileSync(ssl.ca);
|
|
2799
2352
|
return options;
|
|
2800
2353
|
}
|
|
2801
|
-
async function createListener(
|
|
2354
|
+
async function createListener(context, onSocketError) {
|
|
2802
2355
|
const storage = context.storage;
|
|
2803
2356
|
const security = await httpSecurity(context);
|
|
2804
2357
|
const listener = compose(
|
|
@@ -2811,35 +2364,36 @@ async function createListener(middleware, context, onSocketError) {
|
|
|
2811
2364
|
const { request, response } = exchange;
|
|
2812
2365
|
const upgradeMatchResult = await upgradeMatcher(exchange);
|
|
2813
2366
|
if ((request.method === "GET" || request.method === "CONNECT") && upgradeMatchResult.match) {
|
|
2814
|
-
const socket =
|
|
2367
|
+
const socket = response.unsafeServerResponse.socket;
|
|
2815
2368
|
const host = request.host;
|
|
2816
|
-
const info2 = socketKey(
|
|
2369
|
+
const info2 = socketKey(socket);
|
|
2817
2370
|
if (route.wss) {
|
|
2818
2371
|
socket.removeListener("error", onSocketError);
|
|
2819
2372
|
const wss = route.wss;
|
|
2820
2373
|
if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
|
|
2821
|
-
|
|
2374
|
+
logger5.warn(`${info2} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
|
|
2822
2375
|
socket.destroy();
|
|
2823
2376
|
return;
|
|
2824
2377
|
}
|
|
2825
2378
|
const origin = request.headers.one("origin");
|
|
2826
2379
|
if (!acceptsOrigin(origin, route.originFilters)) {
|
|
2827
|
-
|
|
2380
|
+
logger5.info(`${info2} dropping ws connection request from ${host} on ${path}. origin ${origin ?? "<missing>"}`);
|
|
2828
2381
|
socket.destroy();
|
|
2829
2382
|
return;
|
|
2830
2383
|
}
|
|
2831
|
-
if (
|
|
2832
|
-
|
|
2384
|
+
if (logger5.enabledFor("debug")) {
|
|
2385
|
+
logger5.debug(`${info2} accepted new ws connection request from ${host} on ${path}`);
|
|
2833
2386
|
}
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2387
|
+
const upgradeHead = response.unsafeServerResponse.upgradeHead;
|
|
2388
|
+
wss.handleUpgrade(request.unsafeIncomingMessage, socket, upgradeHead, (ws2) => {
|
|
2389
|
+
response.unsafeServerResponse.markHeadersSent();
|
|
2390
|
+
ws2.on("pong", () => ws2["connected"] = true);
|
|
2391
|
+
ws2.on("ping", () => {
|
|
2838
2392
|
});
|
|
2839
|
-
wss.emit("connection",
|
|
2393
|
+
wss.emit("connection", ws2, request.unsafeIncomingMessage);
|
|
2840
2394
|
});
|
|
2841
2395
|
} else {
|
|
2842
|
-
|
|
2396
|
+
logger5.warn(`${info2} rejected upgrade request from ${host} on ${path}`);
|
|
2843
2397
|
socket.destroy();
|
|
2844
2398
|
}
|
|
2845
2399
|
} else {
|
|
@@ -2847,8 +2401,8 @@ async function createListener(middleware, context, onSocketError) {
|
|
|
2847
2401
|
await next();
|
|
2848
2402
|
return;
|
|
2849
2403
|
}
|
|
2850
|
-
if (
|
|
2851
|
-
|
|
2404
|
+
if (logger5.enabledFor("debug")) {
|
|
2405
|
+
logger5.debug(`rejecting request for ${path} with method ${request.method} from ${request.socket.remoteAddress}:${request.socket.remotePort} with headers: ${JSON.stringify(request.headers)}`);
|
|
2852
2406
|
}
|
|
2853
2407
|
response.statusCode = 426;
|
|
2854
2408
|
response.headers.set("Upgrade", "websocket").set("Connection", "Upgrade").set("Content-Type", "text/plain");
|
|
@@ -2858,12 +2412,12 @@ async function createListener(middleware, context, onSocketError) {
|
|
|
2858
2412
|
await next();
|
|
2859
2413
|
}
|
|
2860
2414
|
},
|
|
2861
|
-
...middleware,
|
|
2862
|
-
//
|
|
2415
|
+
...context.middleware,
|
|
2416
|
+
// health check
|
|
2863
2417
|
async ({ request, response }, next) => {
|
|
2864
2418
|
if (request.method === "GET" && request.path === "/health") {
|
|
2865
2419
|
response.statusCode = 200;
|
|
2866
|
-
await response.end(
|
|
2420
|
+
await response.end(http2.STATUS_CODES[200]);
|
|
2867
2421
|
} else {
|
|
2868
2422
|
await next();
|
|
2869
2423
|
}
|
|
@@ -2879,24 +2433,25 @@ async function createListener(middleware, context, onSocketError) {
|
|
|
2879
2433
|
// not found
|
|
2880
2434
|
async ({ response }, _next) => {
|
|
2881
2435
|
response.statusCode = 404;
|
|
2882
|
-
await response.end(
|
|
2436
|
+
await response.end(http2.STATUS_CODES[404]);
|
|
2883
2437
|
}
|
|
2884
2438
|
);
|
|
2885
|
-
return (request, response) => {
|
|
2439
|
+
return async (request, response) => {
|
|
2886
2440
|
request.socket.addListener("error", onSocketError);
|
|
2887
2441
|
const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2442
|
+
request.exchange = exchange;
|
|
2443
|
+
return await storage.run({ exchange }, async () => {
|
|
2444
|
+
if (logger5.enabledFor("debug")) {
|
|
2445
|
+
const socket = exchange.request.socket;
|
|
2446
|
+
if (logger5.enabledFor("debug")) {
|
|
2447
|
+
logger5.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
|
|
2893
2448
|
}
|
|
2894
2449
|
}
|
|
2895
2450
|
try {
|
|
2896
2451
|
return await listener(exchange);
|
|
2897
2452
|
} catch (e) {
|
|
2898
|
-
if (
|
|
2899
|
-
|
|
2453
|
+
if (logger5.enabledFor("warn")) {
|
|
2454
|
+
logger5.warn(`error processing request for ${exchange.path}`, e);
|
|
2900
2455
|
}
|
|
2901
2456
|
} finally {
|
|
2902
2457
|
await exchange.response.end();
|
|
@@ -2931,10 +2486,10 @@ function regexAwareReplacer(_key, value) {
|
|
|
2931
2486
|
}
|
|
2932
2487
|
var Factory = async (options) => {
|
|
2933
2488
|
const ssl = options.ssl;
|
|
2934
|
-
const createServer = ssl ? (options2, handler) => https.createServer({ ...options2, ...secureContextOptions(ssl) }, handler) : (options2, handler) =>
|
|
2489
|
+
const createServer = ssl ? (options2, handler) => https.createServer({ ...options2, ...secureContextOptions(ssl) }, handler) : (options2, handler) => http2.createServer(options2, handler);
|
|
2935
2490
|
const monitor = memoryMonitor(options.memory);
|
|
2936
|
-
const middleware = [];
|
|
2937
2491
|
const context = {
|
|
2492
|
+
middleware: [],
|
|
2938
2493
|
corsConfig: options.cors,
|
|
2939
2494
|
cors: [],
|
|
2940
2495
|
authConfig: options.auth,
|
|
@@ -2942,53 +2497,40 @@ var Factory = async (options) => {
|
|
|
2942
2497
|
storage: new AsyncLocalStorage2(),
|
|
2943
2498
|
sockets: /* @__PURE__ */ new Map()
|
|
2944
2499
|
};
|
|
2945
|
-
const gw =
|
|
2500
|
+
const gw = IOGateway6.Factory({ ...options.gateway });
|
|
2946
2501
|
if (options.gateway) {
|
|
2947
2502
|
const config = options.gateway;
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
ping: options.gateway.ping,
|
|
2952
|
-
factory: core_default.bind(gw),
|
|
2953
|
-
maxConnections: config.limits?.max_connections,
|
|
2954
|
-
authorize: config.authorize,
|
|
2955
|
-
originFilters: regexifyOriginFilters(config.origins)
|
|
2956
|
-
});
|
|
2957
|
-
}
|
|
2958
|
-
if (options.mesh) {
|
|
2959
|
-
const connections = new InMemoryNodeConnections(options.mesh.timeout ?? 6e4);
|
|
2960
|
-
const authorize = options.mesh.authorize;
|
|
2961
|
-
middleware.push(...routes_default(connections, context, authorize));
|
|
2962
|
-
const ping = options.mesh.ping ?? 3e4;
|
|
2963
|
-
const originFilters = regexifyOriginFilters(options.mesh.origins);
|
|
2964
|
-
context.sockets.set("/broker", { factory: core_default2, ping, originFilters, authorize });
|
|
2965
|
-
context.sockets.set("/cluster", { factory: core_default4, ping, originFilters, authorize });
|
|
2966
|
-
context.sockets.set("/relays", { factory: core_default3, ping, originFilters, authorize });
|
|
2503
|
+
await configure(async (configurer) => {
|
|
2504
|
+
configurer.socket({ path: config.route, factory: core_default.bind(gw), options: config });
|
|
2505
|
+
}, options, context);
|
|
2967
2506
|
}
|
|
2968
|
-
if (options.
|
|
2969
|
-
|
|
2507
|
+
if (options.app) {
|
|
2508
|
+
await configure(options.app, options, context);
|
|
2970
2509
|
}
|
|
2971
2510
|
const ports = portRange(options.port ?? 0);
|
|
2972
2511
|
const host = options.host;
|
|
2973
|
-
const onSocketError = (err) =>
|
|
2974
|
-
const listener = await createListener(
|
|
2512
|
+
const onSocketError = (err) => logger5.error(`socket error: ${err}`, err);
|
|
2513
|
+
const listener = await createListener(context, onSocketError);
|
|
2975
2514
|
const serverP = new Promise((resolve, reject) => {
|
|
2976
|
-
const server2 = createServer({
|
|
2515
|
+
const server2 = createServer({
|
|
2516
|
+
IncomingMessage: ExtendedHttpIncomingMessage,
|
|
2517
|
+
ServerResponse: ExtendedHttpServerResponse
|
|
2518
|
+
}, listener);
|
|
2977
2519
|
server2.on("error", (e) => {
|
|
2978
2520
|
if (e["code"] === "EADDRINUSE") {
|
|
2979
|
-
|
|
2521
|
+
logger5.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
|
|
2980
2522
|
const { value: port } = ports.next();
|
|
2981
2523
|
if (port) {
|
|
2982
|
-
|
|
2524
|
+
logger5.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
|
|
2983
2525
|
server2.close();
|
|
2984
2526
|
server2.listen(port, host);
|
|
2985
2527
|
} else {
|
|
2986
|
-
|
|
2528
|
+
logger5.warn(`all configured port(s) ${options.port} are in use. closing...`);
|
|
2987
2529
|
server2.close();
|
|
2988
2530
|
reject(e);
|
|
2989
2531
|
}
|
|
2990
2532
|
} else {
|
|
2991
|
-
|
|
2533
|
+
logger5.error(`server error: ${e.message}`, e);
|
|
2992
2534
|
reject(e);
|
|
2993
2535
|
}
|
|
2994
2536
|
});
|
|
@@ -2996,10 +2538,35 @@ var Factory = async (options) => {
|
|
|
2996
2538
|
const info2 = server2.address();
|
|
2997
2539
|
for (const [path, route] of context.sockets) {
|
|
2998
2540
|
try {
|
|
2999
|
-
|
|
3000
|
-
const wss = new WebSocketServer({ noServer: true });
|
|
2541
|
+
logger5.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
|
|
2542
|
+
const wss = new ws.WebSocketServer({ noServer: true });
|
|
3001
2543
|
const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info2.port}${path}`;
|
|
3002
|
-
const handler = await route.factory({ endpoint
|
|
2544
|
+
const handler = await route.factory({ endpoint });
|
|
2545
|
+
wss.on("error", (err) => {
|
|
2546
|
+
logger5.error(`error starting the ws server for [${path}]`, err);
|
|
2547
|
+
}).on("listening", () => {
|
|
2548
|
+
logger5.info(`ws server for [${path}] is listening`);
|
|
2549
|
+
}).on("connection", (socket, req) => {
|
|
2550
|
+
const { request, principal } = req.exchange;
|
|
2551
|
+
const url = request.URL;
|
|
2552
|
+
const headers2 = new MapHttpHeaders();
|
|
2553
|
+
for (const key of request.headers.keys()) {
|
|
2554
|
+
headers2.set(key, request.headers.get(key));
|
|
2555
|
+
}
|
|
2556
|
+
const cookies = request.cookies;
|
|
2557
|
+
const handshake = {
|
|
2558
|
+
url,
|
|
2559
|
+
headers: headers2,
|
|
2560
|
+
cookies,
|
|
2561
|
+
principal,
|
|
2562
|
+
protocol: socket.protocol,
|
|
2563
|
+
remoteAddress: request.socket.remoteAddress,
|
|
2564
|
+
remoteFamily: request.socket.remoteFamily,
|
|
2565
|
+
remotePort: request.socket.remotePort,
|
|
2566
|
+
logPrefix: socketKey(request.socket)
|
|
2567
|
+
};
|
|
2568
|
+
handler(socket, handshake);
|
|
2569
|
+
});
|
|
3003
2570
|
const pingInterval = route.ping;
|
|
3004
2571
|
if (pingInterval) {
|
|
3005
2572
|
const pingIntervalId = setInterval(() => {
|
|
@@ -3018,30 +2585,28 @@ var Factory = async (options) => {
|
|
|
3018
2585
|
route.wss = wss;
|
|
3019
2586
|
route.close = handler.close?.bind(handler);
|
|
3020
2587
|
} catch (e) {
|
|
3021
|
-
|
|
2588
|
+
logger5.warn(`failed to init route ${path}`, e);
|
|
3022
2589
|
}
|
|
3023
2590
|
}
|
|
3024
|
-
|
|
2591
|
+
logger5.info(`http server listening on ${info2.address}:${info2.port}`);
|
|
3025
2592
|
resolve(server2);
|
|
3026
2593
|
});
|
|
3027
2594
|
server2.on("upgrade", (req, socket, head) => {
|
|
3028
|
-
socket.
|
|
2595
|
+
socket.on("error", onSocketError);
|
|
3029
2596
|
try {
|
|
3030
|
-
|
|
3031
|
-
const res = new http.ServerResponse(req);
|
|
3032
|
-
res.assignSocket(socket);
|
|
2597
|
+
const res = ExtendedHttpServerResponse.forUpgrade(req, socket, head);
|
|
3033
2598
|
listener(req, res);
|
|
3034
2599
|
} catch (err) {
|
|
3035
|
-
|
|
2600
|
+
logger5.error(`upgrade error: ${err}`, err);
|
|
3036
2601
|
}
|
|
3037
2602
|
}).on("close", async () => {
|
|
3038
|
-
|
|
2603
|
+
logger5.info(`http server closed.`);
|
|
3039
2604
|
});
|
|
3040
2605
|
try {
|
|
3041
2606
|
const { value: port } = ports.next();
|
|
3042
2607
|
server2.listen(port, host);
|
|
3043
2608
|
} catch (e) {
|
|
3044
|
-
|
|
2609
|
+
logger5.error(`error starting web socket server`, e);
|
|
3045
2610
|
reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
|
|
3046
2611
|
}
|
|
3047
2612
|
});
|
|
@@ -3054,13 +2619,13 @@ var Factory = async (options) => {
|
|
|
3054
2619
|
if (route.close) {
|
|
3055
2620
|
await route.close();
|
|
3056
2621
|
}
|
|
3057
|
-
|
|
2622
|
+
logger5.info(`stopping ws server for [${path}]. clients: ${route.wss?.clients?.size ?? 0}`);
|
|
3058
2623
|
route.wss?.clients?.forEach((client) => {
|
|
3059
2624
|
client.terminate();
|
|
3060
2625
|
});
|
|
3061
2626
|
route.wss?.close();
|
|
3062
2627
|
} catch (e) {
|
|
3063
|
-
|
|
2628
|
+
logger5.warn(`error closing route ${path}`, e);
|
|
3064
2629
|
}
|
|
3065
2630
|
}
|
|
3066
2631
|
await promisify((cb) => {
|
|
@@ -3070,6 +2635,9 @@ var Factory = async (options) => {
|
|
|
3070
2635
|
if (monitor) {
|
|
3071
2636
|
await stop(monitor);
|
|
3072
2637
|
}
|
|
2638
|
+
if (gw) {
|
|
2639
|
+
await gw.stop();
|
|
2640
|
+
}
|
|
3073
2641
|
}
|
|
3074
2642
|
}();
|
|
3075
2643
|
};
|