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