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