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