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