@interopio/gateway-server 0.6.2-beta → 0.7.0-beta

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