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

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