@interopio/gateway-server 0.5.2-beta → 0.6.1-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +17 -0
- package/dist/gateway-ent.cjs +6 -2
- package/dist/gateway-ent.cjs.map +2 -2
- package/dist/gateway-ent.js +6 -2
- package/dist/gateway-ent.js.map +2 -2
- package/dist/index.cjs +817 -507
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +818 -508
- package/dist/index.js.map +4 -4
- package/dist/metrics/publisher/rest.cjs.map +2 -2
- package/dist/metrics/publisher/rest.js.map +2 -2
- package/gateway-server.d.ts +32 -4
- package/package.json +2 -2
- package/readme.md +37 -0
package/dist/index.js
CHANGED
|
@@ -138,7 +138,7 @@ var HttpServerRequest = class {
|
|
|
138
138
|
this._body ??= new Promise((resolve, reject) => {
|
|
139
139
|
const chunks = [];
|
|
140
140
|
this._req.on("error", (err) => reject(err)).on("data", (chunk) => chunks.push(chunk)).on("end", () => {
|
|
141
|
-
resolve(new Blob(chunks));
|
|
141
|
+
resolve(new Blob(chunks, { type: this.headers.one("content-type") }));
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
144
|
return this._body;
|
|
@@ -155,74 +155,79 @@ var HttpServerRequest = class {
|
|
|
155
155
|
}
|
|
156
156
|
get json() {
|
|
157
157
|
return this.body.then(async (blob) => {
|
|
158
|
-
|
|
158
|
+
if (blob.size === 0) {
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
const text = await blob.text();
|
|
162
|
+
const json = JSON.parse(text);
|
|
159
163
|
return json;
|
|
160
164
|
});
|
|
161
165
|
}
|
|
162
166
|
};
|
|
163
167
|
var IncomingMessageHeaders = class {
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
#msg;
|
|
169
|
+
constructor(msg) {
|
|
170
|
+
this.#msg = msg;
|
|
166
171
|
}
|
|
167
172
|
has(name) {
|
|
168
|
-
return this.
|
|
173
|
+
return this.#msg.headers[name] !== void 0;
|
|
169
174
|
}
|
|
170
175
|
get(name) {
|
|
171
|
-
return this.
|
|
176
|
+
return this.#msg.headers[name];
|
|
172
177
|
}
|
|
173
178
|
list(name) {
|
|
174
|
-
return toList(this.
|
|
179
|
+
return toList(this.#msg.headers[name]);
|
|
175
180
|
}
|
|
176
181
|
one(name) {
|
|
177
|
-
const value = this.
|
|
182
|
+
const value = this.#msg.headers[name];
|
|
178
183
|
if (Array.isArray(value)) {
|
|
179
184
|
return value[0];
|
|
180
185
|
}
|
|
181
186
|
return value;
|
|
182
187
|
}
|
|
183
188
|
keys() {
|
|
184
|
-
return Object.keys(this.
|
|
189
|
+
return Object.keys(this.#msg.headers).values();
|
|
185
190
|
}
|
|
186
191
|
};
|
|
187
192
|
var OutgoingMessageHeaders = class {
|
|
188
|
-
|
|
193
|
+
#msg;
|
|
189
194
|
constructor(msg) {
|
|
190
|
-
this
|
|
195
|
+
this.#msg = msg;
|
|
191
196
|
}
|
|
192
197
|
has(name) {
|
|
193
|
-
return this.
|
|
198
|
+
return this.#msg.hasHeader(name);
|
|
194
199
|
}
|
|
195
200
|
keys() {
|
|
196
|
-
return this.
|
|
201
|
+
return this.#msg.getHeaderNames().values();
|
|
197
202
|
}
|
|
198
203
|
get(name) {
|
|
199
|
-
return this.
|
|
204
|
+
return this.#msg.getHeader(name);
|
|
200
205
|
}
|
|
201
206
|
one(name) {
|
|
202
|
-
const value = this.
|
|
207
|
+
const value = this.#msg.getHeader(name);
|
|
203
208
|
if (Array.isArray(value)) {
|
|
204
209
|
return value[0];
|
|
205
210
|
}
|
|
206
211
|
return value;
|
|
207
212
|
}
|
|
208
213
|
set(name, value) {
|
|
209
|
-
if (!this.
|
|
214
|
+
if (!this.#msg.headersSent) {
|
|
210
215
|
if (Array.isArray(value)) {
|
|
211
216
|
value = value.map((v) => typeof v === "number" ? String(v) : v);
|
|
212
217
|
} else if (typeof value === "number") {
|
|
213
218
|
value = String(value);
|
|
214
219
|
}
|
|
215
220
|
if (value) {
|
|
216
|
-
this.
|
|
221
|
+
this.#msg.setHeader(name, value);
|
|
217
222
|
} else {
|
|
218
|
-
this.
|
|
223
|
+
this.#msg.removeHeader(name);
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
226
|
return this;
|
|
222
227
|
}
|
|
223
228
|
add(name, value) {
|
|
224
|
-
if (!this.
|
|
225
|
-
this.
|
|
229
|
+
if (!this.#msg.headersSent) {
|
|
230
|
+
this.#msg.appendHeader(name, value);
|
|
226
231
|
}
|
|
227
232
|
return this;
|
|
228
233
|
}
|
|
@@ -270,7 +275,7 @@ var HttpServerResponse = class {
|
|
|
270
275
|
}
|
|
271
276
|
end(chunk) {
|
|
272
277
|
if (!this._res.headersSent) {
|
|
273
|
-
return new Promise((resolve
|
|
278
|
+
return new Promise((resolve) => {
|
|
274
279
|
if (chunk === void 0) {
|
|
275
280
|
this._res.end(() => {
|
|
276
281
|
resolve(true);
|
|
@@ -300,6 +305,8 @@ var HttpServerResponse = class {
|
|
|
300
305
|
}
|
|
301
306
|
};
|
|
302
307
|
var DefaultWebExchange = class extends WebExchange {
|
|
308
|
+
request;
|
|
309
|
+
response;
|
|
303
310
|
constructor(request, response) {
|
|
304
311
|
super();
|
|
305
312
|
this.request = request;
|
|
@@ -388,6 +395,92 @@ var MapHttpHeaders = class extends Map {
|
|
|
388
395
|
return this;
|
|
389
396
|
}
|
|
390
397
|
};
|
|
398
|
+
var MockHttpRequest = class {
|
|
399
|
+
#url;
|
|
400
|
+
#body;
|
|
401
|
+
headers = new MapHttpHeaders();
|
|
402
|
+
constructor(url, method) {
|
|
403
|
+
if (typeof url === "string") {
|
|
404
|
+
if (URL.canParse(url)) {
|
|
405
|
+
url = new URL(url);
|
|
406
|
+
} else if (URL.canParse(url, "http://localhost")) {
|
|
407
|
+
url = new URL(url, "http://localhost");
|
|
408
|
+
} else {
|
|
409
|
+
throw new TypeError("URL cannot parse url");
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
this.#url = url;
|
|
413
|
+
this.method = method ?? "GET";
|
|
414
|
+
this.headers.set("Host", this.#url.hostname);
|
|
415
|
+
this.path = this.#url.pathname ?? "/";
|
|
416
|
+
}
|
|
417
|
+
method;
|
|
418
|
+
path;
|
|
419
|
+
get host() {
|
|
420
|
+
return requestToHost(this, this.#url.host);
|
|
421
|
+
}
|
|
422
|
+
get protocol() {
|
|
423
|
+
return requestToProtocol(this, this.#url.protocol.slice(0, -1));
|
|
424
|
+
}
|
|
425
|
+
get cookies() {
|
|
426
|
+
return parseCookies(this);
|
|
427
|
+
}
|
|
428
|
+
get body() {
|
|
429
|
+
const body = this.#body;
|
|
430
|
+
return body ? Promise.resolve(body) : Promise.reject(new Error(`no body set`));
|
|
431
|
+
}
|
|
432
|
+
get json() {
|
|
433
|
+
return this.body.then(async (blob) => JSON.parse(await blob.text()));
|
|
434
|
+
}
|
|
435
|
+
get text() {
|
|
436
|
+
return this.body.then(async (blob) => await blob.text());
|
|
437
|
+
}
|
|
438
|
+
set body(value) {
|
|
439
|
+
this.#body = value;
|
|
440
|
+
if (!this.headers.has("content-type")) {
|
|
441
|
+
this.headers.set("content-type", value.type || "application/octet-stream");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
get formData() {
|
|
445
|
+
return this.body.then(async (b) => new URLSearchParams(await b.text()));
|
|
446
|
+
}
|
|
447
|
+
get URL() {
|
|
448
|
+
return new URL(this.path + this.#url.search + this.#url.hash, `${this.protocol}://${this.host}`);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
var MockHttpResponse = class {
|
|
452
|
+
statusCode;
|
|
453
|
+
headers = new MapHttpHeaders();
|
|
454
|
+
cookies = [];
|
|
455
|
+
addCookie(cookie) {
|
|
456
|
+
this.cookies.push(cookie);
|
|
457
|
+
return this;
|
|
458
|
+
}
|
|
459
|
+
_body;
|
|
460
|
+
async end(chunk) {
|
|
461
|
+
if (this._body) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
switch (typeof chunk) {
|
|
465
|
+
case "string":
|
|
466
|
+
this._body = new Blob([chunk], { type: "text/plain" });
|
|
467
|
+
break;
|
|
468
|
+
case "object":
|
|
469
|
+
if (chunk instanceof Blob) {
|
|
470
|
+
this._body = chunk;
|
|
471
|
+
} else if (chunk !== null) {
|
|
472
|
+
this._body = new Blob([JSON.stringify(chunk)], { type: "application/json" });
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
case "undefined":
|
|
476
|
+
this._body = new Blob([]);
|
|
477
|
+
break;
|
|
478
|
+
default:
|
|
479
|
+
throw new Error(`Unsupported chunk type: ${typeof chunk}`);
|
|
480
|
+
}
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
391
484
|
|
|
392
485
|
// src/gateway/ws/core.ts
|
|
393
486
|
import { IOGateway as IOGateway2 } from "@interopio/gateway";
|
|
@@ -558,22 +651,129 @@ var InMemoryNodeConnections = class {
|
|
|
558
651
|
}
|
|
559
652
|
};
|
|
560
653
|
|
|
654
|
+
// src/server/util/matchers.ts
|
|
655
|
+
var or = (matchers) => {
|
|
656
|
+
return async (exchange) => {
|
|
657
|
+
for (const matcher of matchers) {
|
|
658
|
+
const result = await matcher(exchange);
|
|
659
|
+
if (result.match) {
|
|
660
|
+
return { match: true };
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return { match: false };
|
|
664
|
+
};
|
|
665
|
+
};
|
|
666
|
+
var and = (matchers) => {
|
|
667
|
+
const matcher = async (exchange) => {
|
|
668
|
+
for (const matcher2 of matchers) {
|
|
669
|
+
const result = await matcher2(exchange);
|
|
670
|
+
if (!result.match) {
|
|
671
|
+
return { match: false };
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return { match: true };
|
|
675
|
+
};
|
|
676
|
+
matcher.toString = () => `and(${matchers.map((m) => m.toString()).join(", ")})`;
|
|
677
|
+
return matcher;
|
|
678
|
+
};
|
|
679
|
+
var not = (matcher) => {
|
|
680
|
+
return async (exchange) => {
|
|
681
|
+
const result = await matcher(exchange);
|
|
682
|
+
return { match: !result.match };
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
var anyExchange = async (_exchange) => {
|
|
686
|
+
return { match: true };
|
|
687
|
+
};
|
|
688
|
+
anyExchange.toString = () => "any-exchange";
|
|
689
|
+
var pattern = (pattern2, opts) => {
|
|
690
|
+
const method = opts?.method;
|
|
691
|
+
const matcher = async (exchange) => {
|
|
692
|
+
const request = exchange.request;
|
|
693
|
+
const path = request.path;
|
|
694
|
+
if (method !== void 0 && request.method !== method) {
|
|
695
|
+
return { match: false };
|
|
696
|
+
}
|
|
697
|
+
if (typeof pattern2 === "string") {
|
|
698
|
+
if (path === pattern2) {
|
|
699
|
+
return { match: true };
|
|
700
|
+
} else {
|
|
701
|
+
return { match: false };
|
|
702
|
+
}
|
|
703
|
+
} else {
|
|
704
|
+
const match = pattern2.exec(path);
|
|
705
|
+
if (match !== null) {
|
|
706
|
+
return { match: true };
|
|
707
|
+
} else {
|
|
708
|
+
return { match: false };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
matcher.toString = () => {
|
|
713
|
+
return `pattern(${pattern2.toString()})${method ? ` method=${method}` : ""}`;
|
|
714
|
+
};
|
|
715
|
+
return matcher;
|
|
716
|
+
};
|
|
717
|
+
var mediaType = (opts) => {
|
|
718
|
+
const shouldIgnore = (requestedMediaType) => {
|
|
719
|
+
if (opts.ignoredMediaTypes !== void 0) {
|
|
720
|
+
for (const ignoredMediaType of opts.ignoredMediaTypes) {
|
|
721
|
+
if (requestedMediaType === ignoredMediaType || ignoredMediaType === "*/*") {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return false;
|
|
727
|
+
};
|
|
728
|
+
return async (exchange) => {
|
|
729
|
+
const request = exchange.request;
|
|
730
|
+
let requestMediaTypes;
|
|
731
|
+
try {
|
|
732
|
+
requestMediaTypes = request.headers.list("accept");
|
|
733
|
+
} catch (e) {
|
|
734
|
+
return { match: false };
|
|
735
|
+
}
|
|
736
|
+
for (const requestedMediaType of requestMediaTypes) {
|
|
737
|
+
if (shouldIgnore(requestedMediaType)) {
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
for (const mediaType2 of opts.mediaTypes) {
|
|
741
|
+
if (requestedMediaType.startsWith(mediaType2)) {
|
|
742
|
+
return { match: true };
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return { match: false };
|
|
747
|
+
};
|
|
748
|
+
};
|
|
749
|
+
var upgradeMatcher = async ({ request }) => {
|
|
750
|
+
const match = request["_req"]?.["upgrade"] && request.headers.one("upgrade")?.toLowerCase() === "websocket";
|
|
751
|
+
return { match };
|
|
752
|
+
};
|
|
753
|
+
upgradeMatcher.toString = () => "websocket upgrade";
|
|
754
|
+
|
|
561
755
|
// src/mesh/rest-directory/routes.ts
|
|
562
|
-
function routes(connections) {
|
|
756
|
+
function routes(connections, config, authorize) {
|
|
757
|
+
config.cors.push(
|
|
758
|
+
[pattern("/api/nodes"), { allowMethods: ["GET", "POST"] }],
|
|
759
|
+
[pattern(/\/api\/nodes\/(?<nodeId>.*)/), { allowMethods: ["GET", "DELETE"] }]
|
|
760
|
+
);
|
|
761
|
+
if (authorize) {
|
|
762
|
+
config.authorize.push([pattern(/\/api\/nodes(\/.*)?/), authorize]);
|
|
763
|
+
}
|
|
563
764
|
return [
|
|
564
765
|
async (ctx, next) => {
|
|
565
766
|
if (ctx.method === "POST" && ctx.path === "/api/nodes") {
|
|
566
767
|
const json = await ctx.request.json;
|
|
567
768
|
if (!Array.isArray(json)) {
|
|
568
769
|
ctx.response.statusCode = 400;
|
|
569
|
-
ctx.response.
|
|
770
|
+
await ctx.response.end();
|
|
570
771
|
} else {
|
|
571
772
|
const nodes = json;
|
|
572
773
|
const result = connections.announce(nodes);
|
|
573
|
-
const body = JSON.stringify(result);
|
|
574
|
-
ctx.response.headers.set("content-type", "application/json");
|
|
774
|
+
const body = new Blob([JSON.stringify(result)], { type: "application/json" });
|
|
575
775
|
ctx.response.statusCode = 200;
|
|
576
|
-
ctx.response.
|
|
776
|
+
await ctx.response.end(body);
|
|
577
777
|
}
|
|
578
778
|
} else {
|
|
579
779
|
await next();
|
|
@@ -584,7 +784,7 @@ function routes(connections) {
|
|
|
584
784
|
const nodeId = path?.substring("/api/nodes/".length);
|
|
585
785
|
connections.remove(nodeId);
|
|
586
786
|
response.statusCode = 200;
|
|
587
|
-
response.
|
|
787
|
+
await response.end();
|
|
588
788
|
} else {
|
|
589
789
|
await next();
|
|
590
790
|
}
|
|
@@ -945,68 +1145,38 @@ var core_default4 = create4;
|
|
|
945
1145
|
|
|
946
1146
|
// src/metrics/routes.ts
|
|
947
1147
|
var logger5 = getLogger("metrics");
|
|
948
|
-
|
|
949
|
-
function loggedIn(auth, ctx) {
|
|
950
|
-
if (auth) {
|
|
951
|
-
const value = ctx.request.cookies.find((cookie) => cookie.name === COOKIE_NAME)?.value;
|
|
952
|
-
return value && parseInt(value) > Date.now();
|
|
953
|
-
}
|
|
954
|
-
return true;
|
|
955
|
-
}
|
|
956
|
-
async function routes2(config) {
|
|
1148
|
+
async function routes2(config, { cors, authorize }) {
|
|
957
1149
|
const { jsonFileAppender } = await import("@interopio/gateway/metrics/publisher/file");
|
|
958
1150
|
const appender = jsonFileAppender(logger5);
|
|
959
1151
|
await appender.open(config.file?.location ?? "metrics.ndjson", config.file?.append ?? true);
|
|
1152
|
+
cors.push([pattern("/api/metrics"), { allowMethods: ["GET", "POST"] }]);
|
|
1153
|
+
if (config.authorize) {
|
|
1154
|
+
authorize.push([pattern("/api/metrics"), config.authorize]);
|
|
1155
|
+
}
|
|
960
1156
|
return [
|
|
961
1157
|
async (ctx, next) => {
|
|
962
1158
|
if (ctx.method === "GET" && ctx.path === "/api/metrics") {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
} else {
|
|
966
|
-
ctx.response.statusCode = 302;
|
|
967
|
-
ctx.response.headers.set("location", "/api/login?redirectTo=/api/metrics");
|
|
968
|
-
}
|
|
969
|
-
ctx.response._res.end();
|
|
970
|
-
} else {
|
|
971
|
-
await next();
|
|
972
|
-
}
|
|
973
|
-
},
|
|
974
|
-
async (ctx, next) => {
|
|
975
|
-
if (ctx.method === "GET" && ctx.path === "/api/login") {
|
|
976
|
-
const redirectTo = new URLSearchParams(ctx.request.query ?? void 0).get("redirectTo");
|
|
977
|
-
const expires = Date.now() + 180 * 1e3;
|
|
978
|
-
ctx.response.addCookie({ name: COOKIE_NAME, value: `${expires}`, maxAge: Infinity, path: "/api", sameSite: "strict" });
|
|
979
|
-
if (redirectTo) {
|
|
980
|
-
ctx.response.statusCode = 302;
|
|
981
|
-
ctx.response.headers.set("location", redirectTo);
|
|
982
|
-
} else {
|
|
983
|
-
ctx.response.statusCode = 200;
|
|
984
|
-
}
|
|
985
|
-
ctx.response._res.end();
|
|
1159
|
+
ctx.response.statusCode = 200;
|
|
1160
|
+
await ctx.response.end();
|
|
986
1161
|
} else {
|
|
987
1162
|
await next();
|
|
988
1163
|
}
|
|
989
1164
|
},
|
|
990
|
-
async (
|
|
991
|
-
if (
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
logger5.debug(`${JSON.stringify(update)}`);
|
|
1000
|
-
}
|
|
1001
|
-
if ((config.file?.status ?? false) || update.status === void 0) {
|
|
1002
|
-
await appender.write(update);
|
|
1003
|
-
}
|
|
1004
|
-
} catch (e) {
|
|
1005
|
-
logger5.error(`error processing metrics`, e);
|
|
1165
|
+
async ({ request, response }, next) => {
|
|
1166
|
+
if (request.method === "POST" && request.path === "/api/metrics") {
|
|
1167
|
+
response.statusCode = 202;
|
|
1168
|
+
await response.end();
|
|
1169
|
+
try {
|
|
1170
|
+
const json = await request.json;
|
|
1171
|
+
const update = json;
|
|
1172
|
+
if (logger5.enabledFor("debug")) {
|
|
1173
|
+
logger5.debug(`${JSON.stringify(update)}`);
|
|
1006
1174
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1175
|
+
if ((config.file?.status ?? false) || update.status === void 0) {
|
|
1176
|
+
await appender.write(update);
|
|
1177
|
+
}
|
|
1178
|
+
} catch (e) {
|
|
1179
|
+
logger5.error(`error processing metrics`, e);
|
|
1010
1180
|
}
|
|
1011
1181
|
} else {
|
|
1012
1182
|
await next();
|
|
@@ -1087,9 +1257,9 @@ var localIp = (() => {
|
|
|
1087
1257
|
return a.length > 0 ? a[0] : void 0;
|
|
1088
1258
|
}
|
|
1089
1259
|
const addresses = Object.values(networkInterfaces()).flatMap((details) => {
|
|
1090
|
-
return (details ?? []).filter((
|
|
1091
|
-
}).reduce((acc,
|
|
1092
|
-
acc[
|
|
1260
|
+
return (details ?? []).filter((info2) => info2.family === "IPv4");
|
|
1261
|
+
}).reduce((acc, info2) => {
|
|
1262
|
+
acc[info2.internal ? "internal" : "external"].push(info2);
|
|
1093
1263
|
return acc;
|
|
1094
1264
|
}, { internal: [], external: [] });
|
|
1095
1265
|
return (first(addresses.internal) ?? first(addresses.external))?.address;
|
|
@@ -1236,7 +1406,7 @@ async function stop(m) {
|
|
|
1236
1406
|
return await run(m, "stop");
|
|
1237
1407
|
}
|
|
1238
1408
|
|
|
1239
|
-
// src/
|
|
1409
|
+
// src/app/ws-client-verify.ts
|
|
1240
1410
|
import { IOGateway as IOGateway5 } from "@interopio/gateway";
|
|
1241
1411
|
var log3 = getLogger("gateway.ws.client-verify");
|
|
1242
1412
|
function acceptsMissing(originFilters) {
|
|
@@ -1308,272 +1478,57 @@ function regexifyOriginFilters(originFilters) {
|
|
|
1308
1478
|
}
|
|
1309
1479
|
}
|
|
1310
1480
|
|
|
1311
|
-
// src/server/
|
|
1312
|
-
import
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const url = request.URL;
|
|
1319
|
-
const actualProtocol = url.protocol;
|
|
1320
|
-
const actualHost = url.host;
|
|
1321
|
-
const originUrl = new URL(origin);
|
|
1322
|
-
const originHost = originUrl.host;
|
|
1323
|
-
const originProtocol = originUrl.protocol;
|
|
1324
|
-
return actualProtocol === originProtocol && actualHost === originHost;
|
|
1325
|
-
}
|
|
1326
|
-
function isCorsRequest(request) {
|
|
1327
|
-
return request.headers.has("origin") && !isSameOrigin(request);
|
|
1328
|
-
}
|
|
1329
|
-
function isPreFlightRequest(request) {
|
|
1330
|
-
return request.method === "OPTIONS" && request.headers.has("origin") && request.headers.has("access-control-request-method");
|
|
1331
|
-
}
|
|
1332
|
-
var VARY_HEADERS = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"];
|
|
1333
|
-
function processRequest(exchange, config) {
|
|
1334
|
-
const { request, response } = exchange;
|
|
1335
|
-
const responseHeaders = response.headers;
|
|
1336
|
-
if (!responseHeaders.has("Vary")) {
|
|
1337
|
-
responseHeaders.set("Vary", VARY_HEADERS.join(", "));
|
|
1338
|
-
} else {
|
|
1339
|
-
const varyHeaders = responseHeaders.list("Vary");
|
|
1340
|
-
for (const header of VARY_HEADERS) {
|
|
1341
|
-
if (!varyHeaders.find((h) => h === header)) {
|
|
1342
|
-
varyHeaders.push(header);
|
|
1343
|
-
}
|
|
1481
|
+
// src/server/server-header.ts
|
|
1482
|
+
import info from "@interopio/gateway-server/package.json" with { type: "json" };
|
|
1483
|
+
var serverHeader = (server) => {
|
|
1484
|
+
server ??= `${info.name} - v${info.version}`;
|
|
1485
|
+
return async ({ response }, next) => {
|
|
1486
|
+
if (server !== false && !response.headers.has("server")) {
|
|
1487
|
+
response.headers.set("Server", server);
|
|
1344
1488
|
}
|
|
1345
|
-
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1489
|
+
await next();
|
|
1490
|
+
};
|
|
1491
|
+
};
|
|
1492
|
+
var server_header_default = (server) => serverHeader(server);
|
|
1493
|
+
|
|
1494
|
+
// src/app/route.ts
|
|
1495
|
+
function findSocketRoute({ request }, { sockets: routes3 }) {
|
|
1496
|
+
const path = request.path ?? "/";
|
|
1497
|
+
const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
|
|
1498
|
+
if (path === "/" && route2.default === true) {
|
|
1349
1499
|
return true;
|
|
1350
1500
|
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
logger6.debug(`reject: origin is malformed`);
|
|
1354
|
-
}
|
|
1355
|
-
rejectRequest(response);
|
|
1356
|
-
return false;
|
|
1357
|
-
}
|
|
1358
|
-
if (responseHeaders.has("access-control-allow-origin")) {
|
|
1359
|
-
logger6.trace(`skip: already contains "Access-Control-Allow-Origin"`);
|
|
1360
|
-
return true;
|
|
1361
|
-
}
|
|
1362
|
-
const preFlightRequest = isPreFlightRequest(request);
|
|
1363
|
-
if (config) {
|
|
1364
|
-
return handleInternal(exchange, config, preFlightRequest);
|
|
1365
|
-
}
|
|
1366
|
-
if (preFlightRequest) {
|
|
1367
|
-
rejectRequest(response);
|
|
1368
|
-
return false;
|
|
1369
|
-
}
|
|
1370
|
-
return true;
|
|
1501
|
+
});
|
|
1502
|
+
return [route, path];
|
|
1371
1503
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
return origin.toLowerCase();
|
|
1383
|
-
}
|
|
1384
|
-
return origin;
|
|
1385
|
-
});
|
|
1504
|
+
|
|
1505
|
+
// src/server/security/http-headers.ts
|
|
1506
|
+
var staticServerHttpHeadersWriter = (headers2) => {
|
|
1507
|
+
return async (exchange) => {
|
|
1508
|
+
let containsNoHeaders = true;
|
|
1509
|
+
const { response } = exchange;
|
|
1510
|
+
for (const name of headers2.keys()) {
|
|
1511
|
+
if (response.headers.has(name)) {
|
|
1512
|
+
containsNoHeaders = false;
|
|
1513
|
+
}
|
|
1386
1514
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
validateConfig(config);
|
|
1392
|
-
return async (ctx, next) => {
|
|
1393
|
-
const isValid = processRequest(ctx, config);
|
|
1394
|
-
if (!isValid || isPreFlightRequest(ctx.request)) {
|
|
1395
|
-
await ctx.response.end();
|
|
1396
|
-
} else {
|
|
1397
|
-
await next();
|
|
1515
|
+
if (containsNoHeaders) {
|
|
1516
|
+
for (const [name, value] of headers2) {
|
|
1517
|
+
response.headers.set(name, value);
|
|
1518
|
+
}
|
|
1398
1519
|
}
|
|
1399
1520
|
};
|
|
1400
1521
|
};
|
|
1401
|
-
var
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
if (allowOrigin === void 0) {
|
|
1412
|
-
if (logger6.enabledFor("debug")) {
|
|
1413
|
-
logger6.debug(`reject: '${requestOrigin}' origin is not allowed`);
|
|
1414
|
-
}
|
|
1415
|
-
rejectRequest(response);
|
|
1416
|
-
return false;
|
|
1417
|
-
}
|
|
1418
|
-
const requestMethod = getMethodToUse(request, preFlightRequest);
|
|
1419
|
-
const allowMethods = checkMethods(config, requestMethod);
|
|
1420
|
-
if (allowMethods === void 0) {
|
|
1421
|
-
if (logger6.enabledFor("debug")) {
|
|
1422
|
-
logger6.debug(`reject: HTTP '${requestMethod}' is not allowed`);
|
|
1423
|
-
}
|
|
1424
|
-
rejectRequest(response);
|
|
1425
|
-
return false;
|
|
1426
|
-
}
|
|
1427
|
-
const requestHeaders = getHeadersToUse(request, preFlightRequest);
|
|
1428
|
-
const allowHeaders = checkHeaders(config, requestHeaders);
|
|
1429
|
-
if (preFlightRequest && allowHeaders === void 0) {
|
|
1430
|
-
if (logger6.enabledFor("debug")) {
|
|
1431
|
-
logger6.debug(`reject: headers '${requestHeaders}' are not allowed`);
|
|
1432
|
-
}
|
|
1433
|
-
rejectRequest(response);
|
|
1434
|
-
return false;
|
|
1435
|
-
}
|
|
1436
|
-
responseHeaders.set("access-control-allow-origin", allowOrigin);
|
|
1437
|
-
if (preFlightRequest) {
|
|
1438
|
-
responseHeaders.set("access-control-allow-methods", allowMethods.join(","));
|
|
1439
|
-
}
|
|
1440
|
-
if (preFlightRequest && allowHeaders !== void 0 && allowHeaders.length > 0) {
|
|
1441
|
-
responseHeaders.set("access-control-allow-headers", allowHeaders.join(", "));
|
|
1442
|
-
}
|
|
1443
|
-
const exposeHeaders = config.headers?.expose;
|
|
1444
|
-
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
1445
|
-
responseHeaders.set("access-control-expose-headers", exposeHeaders.join(", "));
|
|
1446
|
-
}
|
|
1447
|
-
if (config.credentials?.allow) {
|
|
1448
|
-
responseHeaders.set("access-control-allow-credentials", "true");
|
|
1449
|
-
}
|
|
1450
|
-
if (config.privateNetwork?.allow && request.headers.one("access-control-request-private-network") === "true") {
|
|
1451
|
-
responseHeaders.set("access-control-allow-private-network", "true");
|
|
1452
|
-
}
|
|
1453
|
-
return true;
|
|
1454
|
-
}
|
|
1455
|
-
var ALL = "*";
|
|
1456
|
-
var DEFAULT_METHODS = ["GET", "HEAD"];
|
|
1457
|
-
function validateAllowCredentials(config) {
|
|
1458
|
-
if (config.credentials?.allow === true && config.origins?.allow === ALL) {
|
|
1459
|
-
throw new Error(`when credentials.allow is true origins.allow cannot be "*"`);
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
function validateAllowPrivateNetwork(config) {
|
|
1463
|
-
if (config.privateNetwork?.allow === true && config.origins?.allow === ALL) {
|
|
1464
|
-
throw new Error(`when privateNetwork.allow is true origins.allow cannot be "*"`);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
function checkOrigin(config, origin) {
|
|
1468
|
-
if (origin) {
|
|
1469
|
-
const allowedOrigins = config.origins?.allow;
|
|
1470
|
-
if (allowedOrigins) {
|
|
1471
|
-
if (allowedOrigins === ALL) {
|
|
1472
|
-
validateAllowCredentials(config);
|
|
1473
|
-
validateAllowPrivateNetwork(config);
|
|
1474
|
-
return ALL;
|
|
1475
|
-
}
|
|
1476
|
-
const originToCheck = trimTrailingSlash(origin.toLowerCase());
|
|
1477
|
-
for (const allowedOrigin of allowedOrigins) {
|
|
1478
|
-
if (allowedOrigin === ALL || IOGateway6.Filtering.valueMatches(allowedOrigin, originToCheck)) {
|
|
1479
|
-
return origin;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
function checkMethods(config, requestMethod) {
|
|
1486
|
-
if (requestMethod) {
|
|
1487
|
-
const allowedMethods = config.methods?.allow ?? DEFAULT_METHODS;
|
|
1488
|
-
if (allowedMethods === ALL) {
|
|
1489
|
-
return [requestMethod];
|
|
1490
|
-
}
|
|
1491
|
-
if (IOGateway6.Filtering.valuesMatch(allowedMethods, requestMethod)) {
|
|
1492
|
-
return allowedMethods;
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
function checkHeaders(config, requestHeaders) {
|
|
1497
|
-
if (requestHeaders === void 0) {
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
if (requestHeaders.length == 0) {
|
|
1501
|
-
return [];
|
|
1502
|
-
}
|
|
1503
|
-
const allowedHeaders = config.headers?.allow;
|
|
1504
|
-
if (allowedHeaders === void 0) {
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
const allowAnyHeader = allowedHeaders === ALL;
|
|
1508
|
-
const result = [];
|
|
1509
|
-
for (const requestHeader of requestHeaders) {
|
|
1510
|
-
const value = requestHeader?.trim();
|
|
1511
|
-
if (value) {
|
|
1512
|
-
if (allowAnyHeader) {
|
|
1513
|
-
result.push(value);
|
|
1514
|
-
} else {
|
|
1515
|
-
for (const allowedHeader of allowedHeaders) {
|
|
1516
|
-
if (value.toLowerCase() == allowedHeader) {
|
|
1517
|
-
result.push(value);
|
|
1518
|
-
break;
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
if (result.length > 0) {
|
|
1525
|
-
return result;
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
function trimTrailingSlash(origin) {
|
|
1529
|
-
return origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
1530
|
-
}
|
|
1531
|
-
function getMethodToUse(request, isPreFlight) {
|
|
1532
|
-
return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
|
|
1533
|
-
}
|
|
1534
|
-
function getHeadersToUse(request, isPreFlight) {
|
|
1535
|
-
const headers2 = request.headers;
|
|
1536
|
-
return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
// src/server/server-header.ts
|
|
1540
|
-
var serverHeader = (server) => {
|
|
1541
|
-
return async ({ response }, next) => {
|
|
1542
|
-
if (!response.headers.has("server")) {
|
|
1543
|
-
response.headers.set("Server", server);
|
|
1544
|
-
}
|
|
1545
|
-
await next();
|
|
1546
|
-
};
|
|
1547
|
-
};
|
|
1548
|
-
var server_header_default = (server = "gateway-server") => serverHeader(server);
|
|
1549
|
-
|
|
1550
|
-
// src/server/security/http-headers.ts
|
|
1551
|
-
var staticServerHttpHeadersWriter = (headers2) => {
|
|
1552
|
-
return async (exchange) => {
|
|
1553
|
-
let containsNoHeaders = true;
|
|
1554
|
-
const { response } = exchange;
|
|
1555
|
-
for (const name of headers2.keys()) {
|
|
1556
|
-
if (response.headers.has(name)) {
|
|
1557
|
-
containsNoHeaders = false;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
if (containsNoHeaders) {
|
|
1561
|
-
for (const [name, value] of headers2) {
|
|
1562
|
-
response.headers.set(name, value);
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
};
|
|
1566
|
-
};
|
|
1567
|
-
var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1568
|
-
new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
|
|
1569
|
-
);
|
|
1570
|
-
var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1571
|
-
new MapHttpHeaders().add("x-content-type-options", "nosniff")
|
|
1572
|
-
);
|
|
1573
|
-
var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
|
|
1574
|
-
let headerValue = `max-age=${maxAgeInSeconds}`;
|
|
1575
|
-
if (includeSubDomains) {
|
|
1576
|
-
headerValue += " ; includeSubDomains";
|
|
1522
|
+
var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1523
|
+
new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
|
|
1524
|
+
);
|
|
1525
|
+
var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1526
|
+
new MapHttpHeaders().add("x-content-type-options", "nosniff")
|
|
1527
|
+
);
|
|
1528
|
+
var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
|
|
1529
|
+
let headerValue = `max-age=${maxAgeInSeconds}`;
|
|
1530
|
+
if (includeSubDomains) {
|
|
1531
|
+
headerValue += " ; includeSubDomains";
|
|
1577
1532
|
}
|
|
1578
1533
|
if (preload) {
|
|
1579
1534
|
headerValue += " ; preload";
|
|
@@ -1733,18 +1688,18 @@ var AuthorizationDecision = class {
|
|
|
1733
1688
|
granted;
|
|
1734
1689
|
};
|
|
1735
1690
|
var DefaultAuthorizationManager = class {
|
|
1736
|
-
check;
|
|
1691
|
+
#check;
|
|
1737
1692
|
constructor(check) {
|
|
1738
|
-
this
|
|
1693
|
+
this.#check = check;
|
|
1739
1694
|
}
|
|
1740
1695
|
async verify(authentication, object) {
|
|
1741
|
-
const decision = await this
|
|
1696
|
+
const decision = await this.#check(authentication, object);
|
|
1742
1697
|
if (!decision?.granted) {
|
|
1743
1698
|
throw new AccessDeniedError("Access denied");
|
|
1744
1699
|
}
|
|
1745
1700
|
}
|
|
1746
1701
|
async authorize(authentication, object) {
|
|
1747
|
-
return await this
|
|
1702
|
+
return await this.#check(authentication, object);
|
|
1748
1703
|
}
|
|
1749
1704
|
};
|
|
1750
1705
|
var AuthenticationServiceError = class extends AuthenticationError {
|
|
@@ -1798,71 +1753,6 @@ var httpBasicAuthenticationConverter = (opts) => {
|
|
|
1798
1753
|
};
|
|
1799
1754
|
};
|
|
1800
1755
|
|
|
1801
|
-
// src/server/util/matchers.ts
|
|
1802
|
-
var or = (matchers) => {
|
|
1803
|
-
return async (exchange) => {
|
|
1804
|
-
for (const matcher of matchers) {
|
|
1805
|
-
const result = await matcher(exchange);
|
|
1806
|
-
if (result.match) {
|
|
1807
|
-
return { match: true };
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
return { match: false };
|
|
1811
|
-
};
|
|
1812
|
-
};
|
|
1813
|
-
var and = (matchers) => {
|
|
1814
|
-
return async (exchange) => {
|
|
1815
|
-
for (const matcher of matchers) {
|
|
1816
|
-
const result = await matcher(exchange);
|
|
1817
|
-
if (!result.match) {
|
|
1818
|
-
return { match: false };
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
return { match: true };
|
|
1822
|
-
};
|
|
1823
|
-
};
|
|
1824
|
-
var not = (matcher) => {
|
|
1825
|
-
return async (exchange) => {
|
|
1826
|
-
const result = await matcher(exchange);
|
|
1827
|
-
return { match: !result.match };
|
|
1828
|
-
};
|
|
1829
|
-
};
|
|
1830
|
-
var anyExchange = async (_exchange) => {
|
|
1831
|
-
return { match: true };
|
|
1832
|
-
};
|
|
1833
|
-
var mediaType = (opts) => {
|
|
1834
|
-
const shouldIgnore = (requestedMediaType) => {
|
|
1835
|
-
if (opts.ignoredMediaTypes !== void 0) {
|
|
1836
|
-
for (const ignoredMediaType of opts.ignoredMediaTypes) {
|
|
1837
|
-
if (requestedMediaType === ignoredMediaType || ignoredMediaType === "*/*") {
|
|
1838
|
-
return true;
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
return false;
|
|
1843
|
-
};
|
|
1844
|
-
return async (exchange) => {
|
|
1845
|
-
const request = exchange.request;
|
|
1846
|
-
let requestMediaTypes;
|
|
1847
|
-
try {
|
|
1848
|
-
requestMediaTypes = request.headers.list("accept");
|
|
1849
|
-
} catch (e) {
|
|
1850
|
-
return { match: false };
|
|
1851
|
-
}
|
|
1852
|
-
for (const requestedMediaType of requestMediaTypes) {
|
|
1853
|
-
if (shouldIgnore(requestedMediaType)) {
|
|
1854
|
-
continue;
|
|
1855
|
-
}
|
|
1856
|
-
for (const mediaType2 of opts.mediaTypes) {
|
|
1857
|
-
if (requestedMediaType.startsWith(mediaType2)) {
|
|
1858
|
-
return { match: true };
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
return { match: false };
|
|
1863
|
-
};
|
|
1864
|
-
};
|
|
1865
|
-
|
|
1866
1756
|
// src/server/security/security-context.ts
|
|
1867
1757
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1868
1758
|
var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
|
|
@@ -2176,6 +2066,7 @@ var httpStatusEntryPoint = (opts) => {
|
|
|
2176
2066
|
};
|
|
2177
2067
|
|
|
2178
2068
|
// src/server/security/delegating-entry-point.ts
|
|
2069
|
+
var logger6 = getLogger("auth.entry-point");
|
|
2179
2070
|
var delegatingEntryPoint = (opts) => {
|
|
2180
2071
|
const defaultEntryPoint = opts.defaultEntryPoint ?? (async ({ response }, _error) => {
|
|
2181
2072
|
response.statusCode = 401;
|
|
@@ -2183,11 +2074,20 @@ var delegatingEntryPoint = (opts) => {
|
|
|
2183
2074
|
});
|
|
2184
2075
|
return async (exchange, error) => {
|
|
2185
2076
|
for (const [matcher, entryPoint] of opts.entryPoints) {
|
|
2077
|
+
if (logger6.enabledFor("debug")) {
|
|
2078
|
+
logger6.debug(`trying to match using: ${matcher}`);
|
|
2079
|
+
}
|
|
2186
2080
|
const match = await matcher(exchange);
|
|
2187
2081
|
if (match.match) {
|
|
2082
|
+
if (logger6.enabledFor("debug")) {
|
|
2083
|
+
logger6.debug(`match found. using default entry point ${entryPoint}`);
|
|
2084
|
+
}
|
|
2188
2085
|
return entryPoint(exchange, error);
|
|
2189
2086
|
}
|
|
2190
2087
|
}
|
|
2088
|
+
if (logger6.enabledFor("debug")) {
|
|
2089
|
+
logger6.debug(`no match found. using default entry point ${defaultEntryPoint}`);
|
|
2090
|
+
}
|
|
2191
2091
|
return defaultEntryPoint(exchange, error);
|
|
2192
2092
|
};
|
|
2193
2093
|
};
|
|
@@ -2195,8 +2095,8 @@ var delegatingEntryPoint = (opts) => {
|
|
|
2195
2095
|
// src/server/security/delegating-success-handler.ts
|
|
2196
2096
|
var delegatingSuccessHandler = (handlers) => {
|
|
2197
2097
|
return async ({ exchange, next }, authentication) => {
|
|
2198
|
-
for (const
|
|
2199
|
-
await
|
|
2098
|
+
for (const handler of handlers) {
|
|
2099
|
+
await handler({ exchange, next }, authentication);
|
|
2200
2100
|
}
|
|
2201
2101
|
};
|
|
2202
2102
|
};
|
|
@@ -2246,7 +2146,7 @@ function httpBasic(opts) {
|
|
|
2246
2146
|
}
|
|
2247
2147
|
|
|
2248
2148
|
// src/server/security/config.ts
|
|
2249
|
-
import { jwtVerifier } from "@interopio/gateway/jose/jwt";
|
|
2149
|
+
import { jwtVerifier, JwtVerifyError } from "@interopio/gateway/jose/jwt";
|
|
2250
2150
|
|
|
2251
2151
|
// src/server/security/error-filter.ts
|
|
2252
2152
|
async function commenceAuthentication(exchange, authentication, entryPoint) {
|
|
@@ -2290,14 +2190,21 @@ var errorFilter = (opts) => {
|
|
|
2290
2190
|
};
|
|
2291
2191
|
|
|
2292
2192
|
// src/server/security/authorization-filter.ts
|
|
2193
|
+
var logger7 = getLogger("security.auth");
|
|
2293
2194
|
function authorizationFilter(opts) {
|
|
2294
2195
|
const { manager, storage } = opts;
|
|
2295
2196
|
return async (exchange, next) => {
|
|
2296
2197
|
const promise = AsyncStorageSecurityContextHolder.getContext(storage).then((c) => c?.authentication);
|
|
2297
2198
|
try {
|
|
2298
2199
|
await manager.verify(promise, exchange);
|
|
2200
|
+
if (logger7.enabledFor("debug")) {
|
|
2201
|
+
logger7.debug("authorization successful");
|
|
2202
|
+
}
|
|
2299
2203
|
} catch (error) {
|
|
2300
2204
|
if (error instanceof AccessDeniedError) {
|
|
2205
|
+
if (logger7.enabledFor("debug")) {
|
|
2206
|
+
logger7.debug(`authorization failed: ${error.message}`);
|
|
2207
|
+
}
|
|
2301
2208
|
}
|
|
2302
2209
|
throw error;
|
|
2303
2210
|
}
|
|
@@ -2306,12 +2213,14 @@ function authorizationFilter(opts) {
|
|
|
2306
2213
|
}
|
|
2307
2214
|
|
|
2308
2215
|
// src/server/security/delegating-authorization-manager.ts
|
|
2216
|
+
var logger8 = getLogger("auth");
|
|
2309
2217
|
function delegatingAuthorizationManager(opts) {
|
|
2310
2218
|
const check = async (authentication, exchange) => {
|
|
2311
2219
|
let decision;
|
|
2312
2220
|
for (const [matcher, manager] of opts.mappings) {
|
|
2313
2221
|
if ((await matcher(exchange))?.match) {
|
|
2314
|
-
|
|
2222
|
+
logger8.debug(`checking authorization on '${exchange.path}' using [${matcher}, ${manager}]`);
|
|
2223
|
+
const checkResult = await manager.authorize(authentication, { exchange });
|
|
2315
2224
|
if (checkResult !== void 0) {
|
|
2316
2225
|
decision = checkResult;
|
|
2317
2226
|
break;
|
|
@@ -2324,6 +2233,308 @@ function delegatingAuthorizationManager(opts) {
|
|
|
2324
2233
|
return new DefaultAuthorizationManager(check);
|
|
2325
2234
|
}
|
|
2326
2235
|
|
|
2236
|
+
// src/server/cors.ts
|
|
2237
|
+
import { IOGateway as IOGateway6 } from "@interopio/gateway";
|
|
2238
|
+
function isSameOrigin(request) {
|
|
2239
|
+
const origin = request.headers.one("origin");
|
|
2240
|
+
if (origin === void 0) {
|
|
2241
|
+
return true;
|
|
2242
|
+
}
|
|
2243
|
+
const url = request.URL;
|
|
2244
|
+
const actualProtocol = url.protocol;
|
|
2245
|
+
const actualHost = url.host;
|
|
2246
|
+
const originUrl = URL.parse(origin);
|
|
2247
|
+
const originHost = originUrl?.host;
|
|
2248
|
+
const originProtocol = originUrl?.protocol;
|
|
2249
|
+
return actualProtocol === originProtocol && actualHost === originHost;
|
|
2250
|
+
}
|
|
2251
|
+
function isCorsRequest(request) {
|
|
2252
|
+
return request.headers.has("origin") && !isSameOrigin(request);
|
|
2253
|
+
}
|
|
2254
|
+
function isPreFlightRequest(request) {
|
|
2255
|
+
return request.method === "OPTIONS" && request.headers.has("origin") && request.headers.has("access-control-request-method");
|
|
2256
|
+
}
|
|
2257
|
+
var VARY_HEADERS = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"];
|
|
2258
|
+
var processRequest = (exchange, config) => {
|
|
2259
|
+
const { request, response } = exchange;
|
|
2260
|
+
const responseHeaders = response.headers;
|
|
2261
|
+
if (!responseHeaders.has("Vary")) {
|
|
2262
|
+
responseHeaders.set("Vary", VARY_HEADERS.join(", "));
|
|
2263
|
+
} else {
|
|
2264
|
+
const varyHeaders = responseHeaders.list("Vary");
|
|
2265
|
+
for (const header of VARY_HEADERS) {
|
|
2266
|
+
if (!varyHeaders.find((h) => h === header)) {
|
|
2267
|
+
varyHeaders.push(header);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
responseHeaders.set("Vary", varyHeaders.join(", "));
|
|
2271
|
+
}
|
|
2272
|
+
try {
|
|
2273
|
+
if (!isCorsRequest(request)) {
|
|
2274
|
+
return true;
|
|
2275
|
+
}
|
|
2276
|
+
} catch (e) {
|
|
2277
|
+
if (logger9.enabledFor("debug")) {
|
|
2278
|
+
logger9.debug(`reject: origin is malformed`);
|
|
2279
|
+
}
|
|
2280
|
+
rejectRequest(response);
|
|
2281
|
+
return false;
|
|
2282
|
+
}
|
|
2283
|
+
if (responseHeaders.has("access-control-allow-origin")) {
|
|
2284
|
+
logger9.trace(`skip: already contains "Access-Control-Allow-Origin"`);
|
|
2285
|
+
return true;
|
|
2286
|
+
}
|
|
2287
|
+
const preFlightRequest = isPreFlightRequest(request);
|
|
2288
|
+
if (config) {
|
|
2289
|
+
return handleInternal(exchange, config, preFlightRequest);
|
|
2290
|
+
}
|
|
2291
|
+
if (preFlightRequest) {
|
|
2292
|
+
rejectRequest(response);
|
|
2293
|
+
return false;
|
|
2294
|
+
}
|
|
2295
|
+
return true;
|
|
2296
|
+
};
|
|
2297
|
+
var DEFAULT_PERMIT_ALL = ["*"];
|
|
2298
|
+
var DEFAULT_PERMIT_METHODS = ["GET", "HEAD", "POST"];
|
|
2299
|
+
var PERMIT_DEFAULT_CONFIG = {
|
|
2300
|
+
allowOrigins: DEFAULT_PERMIT_ALL,
|
|
2301
|
+
allowMethods: DEFAULT_PERMIT_METHODS,
|
|
2302
|
+
allowHeaders: DEFAULT_PERMIT_ALL,
|
|
2303
|
+
maxAge: 1800
|
|
2304
|
+
// 30 minutes
|
|
2305
|
+
};
|
|
2306
|
+
function validateCorsConfig(config) {
|
|
2307
|
+
if (config) {
|
|
2308
|
+
const allowHeaders = config.allowHeaders;
|
|
2309
|
+
if (allowHeaders && allowHeaders !== ALL) {
|
|
2310
|
+
config = {
|
|
2311
|
+
...config,
|
|
2312
|
+
allowHeaders: allowHeaders.map((header) => header.toLowerCase())
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
const allowOrigins = config.allowOrigins;
|
|
2316
|
+
if (allowOrigins) {
|
|
2317
|
+
if (allowOrigins === "*") {
|
|
2318
|
+
validateAllowCredentials(config);
|
|
2319
|
+
validateAllowPrivateNetwork(config);
|
|
2320
|
+
} else {
|
|
2321
|
+
config = {
|
|
2322
|
+
...config,
|
|
2323
|
+
allowOrigins: allowOrigins.map((origin) => {
|
|
2324
|
+
if (typeof origin === "string" && origin !== ALL) {
|
|
2325
|
+
origin = IOGateway6.Filtering.regexify(origin);
|
|
2326
|
+
if (typeof origin === "string") {
|
|
2327
|
+
return trimTrailingSlash(origin).toLowerCase();
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
return origin;
|
|
2331
|
+
})
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
return config;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
function combine(source, other) {
|
|
2339
|
+
if (other === void 0) {
|
|
2340
|
+
return source !== void 0 ? source === ALL ? [ALL] : source : [];
|
|
2341
|
+
}
|
|
2342
|
+
if (source === void 0) {
|
|
2343
|
+
return other === ALL ? [ALL] : other;
|
|
2344
|
+
}
|
|
2345
|
+
if (source == DEFAULT_PERMIT_ALL || source === DEFAULT_PERMIT_METHODS) {
|
|
2346
|
+
return other === ALL ? [ALL] : other;
|
|
2347
|
+
}
|
|
2348
|
+
if (other == DEFAULT_PERMIT_ALL || other === DEFAULT_PERMIT_METHODS) {
|
|
2349
|
+
return source === ALL ? [ALL] : source;
|
|
2350
|
+
}
|
|
2351
|
+
if (source === ALL || source.includes(ALL) || other === ALL || other.includes(ALL)) {
|
|
2352
|
+
return [ALL];
|
|
2353
|
+
}
|
|
2354
|
+
const combined = /* @__PURE__ */ new Set();
|
|
2355
|
+
source.forEach((v) => combined.add(v));
|
|
2356
|
+
other.forEach((v) => combined.add(v));
|
|
2357
|
+
return Array.from(combined);
|
|
2358
|
+
}
|
|
2359
|
+
var combineCorsConfig = (source, other) => {
|
|
2360
|
+
if (other === void 0) {
|
|
2361
|
+
return source;
|
|
2362
|
+
}
|
|
2363
|
+
const config = {
|
|
2364
|
+
allowOrigins: combine(source.allowOrigins, other?.allowOrigins),
|
|
2365
|
+
allowMethods: combine(source.allowMethods, other?.allowMethods),
|
|
2366
|
+
allowHeaders: combine(source.allowHeaders, other?.allowHeaders),
|
|
2367
|
+
exposeHeaders: combine(source.exposeHeaders, other?.exposeHeaders),
|
|
2368
|
+
allowCredentials: other?.allowCredentials ?? source.allowCredentials,
|
|
2369
|
+
allowPrivateNetwork: other?.allowPrivateNetwork ?? source.allowPrivateNetwork,
|
|
2370
|
+
maxAge: other?.maxAge ?? source.maxAge
|
|
2371
|
+
};
|
|
2372
|
+
return config;
|
|
2373
|
+
};
|
|
2374
|
+
var corsFilter = (opts) => {
|
|
2375
|
+
const source = opts.corsConfigSource;
|
|
2376
|
+
const processor = opts.corsProcessor ?? processRequest;
|
|
2377
|
+
return async (ctx, next) => {
|
|
2378
|
+
const config = await source(ctx);
|
|
2379
|
+
const isValid = processor(ctx, config);
|
|
2380
|
+
if (!isValid || isPreFlightRequest(ctx.request)) {
|
|
2381
|
+
return;
|
|
2382
|
+
} else {
|
|
2383
|
+
await next();
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
};
|
|
2387
|
+
var cors_default = corsFilter;
|
|
2388
|
+
var logger9 = getLogger("cors");
|
|
2389
|
+
function rejectRequest(response) {
|
|
2390
|
+
response.statusCode = 403;
|
|
2391
|
+
}
|
|
2392
|
+
function handleInternal(exchange, config, preFlightRequest) {
|
|
2393
|
+
const { request, response } = exchange;
|
|
2394
|
+
const responseHeaders = response.headers;
|
|
2395
|
+
const requestOrigin = request.headers.one("origin");
|
|
2396
|
+
const allowOrigin = checkOrigin(config, requestOrigin);
|
|
2397
|
+
if (allowOrigin === void 0) {
|
|
2398
|
+
if (logger9.enabledFor("debug")) {
|
|
2399
|
+
logger9.debug(`reject: '${requestOrigin}' origin is not allowed`);
|
|
2400
|
+
}
|
|
2401
|
+
rejectRequest(response);
|
|
2402
|
+
return false;
|
|
2403
|
+
}
|
|
2404
|
+
const requestMethod = getMethodToUse(request, preFlightRequest);
|
|
2405
|
+
const allowMethods = checkMethods(config, requestMethod);
|
|
2406
|
+
if (allowMethods === void 0) {
|
|
2407
|
+
if (logger9.enabledFor("debug")) {
|
|
2408
|
+
logger9.debug(`reject: HTTP '${requestMethod}' is not allowed`);
|
|
2409
|
+
}
|
|
2410
|
+
rejectRequest(response);
|
|
2411
|
+
return false;
|
|
2412
|
+
}
|
|
2413
|
+
const requestHeaders = getHeadersToUse(request, preFlightRequest);
|
|
2414
|
+
const allowHeaders = checkHeaders(config, requestHeaders);
|
|
2415
|
+
if (preFlightRequest && allowHeaders === void 0) {
|
|
2416
|
+
if (logger9.enabledFor("debug")) {
|
|
2417
|
+
logger9.debug(`reject: headers '${requestHeaders}' are not allowed`);
|
|
2418
|
+
}
|
|
2419
|
+
rejectRequest(response);
|
|
2420
|
+
return false;
|
|
2421
|
+
}
|
|
2422
|
+
responseHeaders.set("Access-Control-Allow-Origin", allowOrigin);
|
|
2423
|
+
if (preFlightRequest) {
|
|
2424
|
+
responseHeaders.set("Access-Control-Allow-Methods", allowMethods.join(","));
|
|
2425
|
+
}
|
|
2426
|
+
if (preFlightRequest && allowHeaders !== void 0 && allowHeaders.length > 0) {
|
|
2427
|
+
responseHeaders.set("Access-Control-Allow-Headers", allowHeaders.join(", "));
|
|
2428
|
+
}
|
|
2429
|
+
const exposeHeaders = config.exposeHeaders;
|
|
2430
|
+
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
2431
|
+
responseHeaders.set("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
2432
|
+
}
|
|
2433
|
+
if (config.allowCredentials) {
|
|
2434
|
+
responseHeaders.set("Access-Control-Allow-Credentials", "true");
|
|
2435
|
+
}
|
|
2436
|
+
if (config.allowPrivateNetwork && request.headers.one("access-control-request-private-network") === "true") {
|
|
2437
|
+
responseHeaders.set("Access-Control-Allow-Private-Network", "true");
|
|
2438
|
+
}
|
|
2439
|
+
if (preFlightRequest && config.maxAge !== void 0) {
|
|
2440
|
+
responseHeaders.set("Access-Control-Max-Age", config.maxAge.toString());
|
|
2441
|
+
}
|
|
2442
|
+
return true;
|
|
2443
|
+
}
|
|
2444
|
+
var ALL = "*";
|
|
2445
|
+
var DEFAULT_METHODS = ["GET", "HEAD"];
|
|
2446
|
+
function validateAllowCredentials(config) {
|
|
2447
|
+
if (config.allowCredentials === true && config.allowOrigins === ALL) {
|
|
2448
|
+
throw new Error(`when allowCredentials is true allowOrigins cannot be "*"`);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
function validateAllowPrivateNetwork(config) {
|
|
2452
|
+
if (config.allowPrivateNetwork === true && config.allowOrigins === ALL) {
|
|
2453
|
+
throw new Error(`when allowPrivateNetwork is true allowOrigins cannot be "*"`);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
function checkOrigin(config, origin) {
|
|
2457
|
+
if (origin) {
|
|
2458
|
+
const allowedOrigins = config.allowOrigins;
|
|
2459
|
+
if (allowedOrigins) {
|
|
2460
|
+
if (allowedOrigins === ALL) {
|
|
2461
|
+
validateAllowCredentials(config);
|
|
2462
|
+
validateAllowPrivateNetwork(config);
|
|
2463
|
+
return ALL;
|
|
2464
|
+
}
|
|
2465
|
+
const originToCheck = trimTrailingSlash(origin.toLowerCase());
|
|
2466
|
+
for (const allowedOrigin of allowedOrigins) {
|
|
2467
|
+
if (allowedOrigin === ALL || IOGateway6.Filtering.valueMatches(allowedOrigin, originToCheck)) {
|
|
2468
|
+
return origin;
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
function checkMethods(config, requestMethod) {
|
|
2475
|
+
if (requestMethod) {
|
|
2476
|
+
const allowedMethods = config.allowMethods ?? DEFAULT_METHODS;
|
|
2477
|
+
if (allowedMethods === ALL) {
|
|
2478
|
+
return [requestMethod];
|
|
2479
|
+
}
|
|
2480
|
+
if (IOGateway6.Filtering.valuesMatch(allowedMethods, requestMethod)) {
|
|
2481
|
+
return allowedMethods;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
function checkHeaders(config, requestHeaders) {
|
|
2486
|
+
if (requestHeaders === void 0) {
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
if (requestHeaders.length == 0) {
|
|
2490
|
+
return [];
|
|
2491
|
+
}
|
|
2492
|
+
const allowedHeaders = config.allowHeaders;
|
|
2493
|
+
if (allowedHeaders === void 0) {
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
const allowAnyHeader = allowedHeaders === ALL || allowedHeaders.includes(ALL);
|
|
2497
|
+
const result = [];
|
|
2498
|
+
for (const requestHeader of requestHeaders) {
|
|
2499
|
+
const value = requestHeader?.trim();
|
|
2500
|
+
if (value) {
|
|
2501
|
+
if (allowAnyHeader) {
|
|
2502
|
+
result.push(value);
|
|
2503
|
+
} else {
|
|
2504
|
+
for (const allowedHeader of allowedHeaders) {
|
|
2505
|
+
if (value.toLowerCase() === allowedHeader) {
|
|
2506
|
+
result.push(value);
|
|
2507
|
+
break;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
if (result.length > 0) {
|
|
2514
|
+
return result;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
function trimTrailingSlash(origin) {
|
|
2518
|
+
return origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
2519
|
+
}
|
|
2520
|
+
function getMethodToUse(request, isPreFlight) {
|
|
2521
|
+
return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
|
|
2522
|
+
}
|
|
2523
|
+
function getHeadersToUse(request, isPreFlight) {
|
|
2524
|
+
const headers2 = request.headers;
|
|
2525
|
+
return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
|
|
2526
|
+
}
|
|
2527
|
+
var matchingCorsConfigSource = (opts) => {
|
|
2528
|
+
return async (exchange) => {
|
|
2529
|
+
for (const [matcher, config] of opts.mappings) {
|
|
2530
|
+
if ((await matcher(exchange)).match) {
|
|
2531
|
+
logger9.debug(`resolved cors config on '${exchange.path}' using ${matcher}: ${JSON.stringify(config)}`);
|
|
2532
|
+
return config;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
};
|
|
2537
|
+
|
|
2327
2538
|
// src/server/security/config.ts
|
|
2328
2539
|
var filterOrder = {
|
|
2329
2540
|
first: Number.MAX_SAFE_INTEGER,
|
|
@@ -2337,7 +2548,7 @@ var filterOrder = {
|
|
|
2337
2548
|
last: Number.MAX_SAFE_INTEGER
|
|
2338
2549
|
};
|
|
2339
2550
|
var filterOrderSymbol = Symbol.for("filterOrder");
|
|
2340
|
-
var config_default = (config,
|
|
2551
|
+
var config_default = (config, context) => {
|
|
2341
2552
|
const middleware = [];
|
|
2342
2553
|
class ServerHttpSecurity {
|
|
2343
2554
|
#authenticationEntryPoint;
|
|
@@ -2361,6 +2572,11 @@ var config_default = (config, storage) => {
|
|
|
2361
2572
|
writer[filterOrderSymbol] = filterOrder.http_headers;
|
|
2362
2573
|
middleware.push(writer);
|
|
2363
2574
|
}
|
|
2575
|
+
if (config.cors?.disabled !== true && context.corsConfigSource !== void 0) {
|
|
2576
|
+
const filter = cors_default({ corsConfigSource: context.corsConfigSource });
|
|
2577
|
+
filter[filterOrderSymbol] = filterOrder.cors;
|
|
2578
|
+
middleware.push(filter);
|
|
2579
|
+
}
|
|
2364
2580
|
if (config.basic !== void 0 && config.basic?.disabled !== true) {
|
|
2365
2581
|
const username = config.basic.user?.name.toLowerCase();
|
|
2366
2582
|
const password = config.basic.user?.password ?? "";
|
|
@@ -2379,7 +2595,7 @@ var config_default = (config, storage) => {
|
|
|
2379
2595
|
}
|
|
2380
2596
|
];
|
|
2381
2597
|
const filter = httpBasic({
|
|
2382
|
-
storage,
|
|
2598
|
+
storage: context.storage,
|
|
2383
2599
|
manager,
|
|
2384
2600
|
defaultEntryPoints: this.#defaultEntryPoints,
|
|
2385
2601
|
defaultSuccessHandlers
|
|
@@ -2389,20 +2605,43 @@ var config_default = (config, storage) => {
|
|
|
2389
2605
|
}
|
|
2390
2606
|
if (config.jwt !== void 0 && config.jwt.disabled !== true) {
|
|
2391
2607
|
const verifier = jwtVerifier({
|
|
2392
|
-
issuerBaseUri: config.jwt.
|
|
2608
|
+
issuerBaseUri: config.jwt.issuerUri,
|
|
2393
2609
|
issuer: config.jwt.issuer,
|
|
2394
2610
|
audience: config.jwt.audience
|
|
2395
2611
|
});
|
|
2396
2612
|
const decoder = async (token) => {
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2613
|
+
try {
|
|
2614
|
+
const { payload } = await verifier(token);
|
|
2615
|
+
return {
|
|
2616
|
+
subject: payload.sub,
|
|
2617
|
+
getClaimAsString(claim) {
|
|
2618
|
+
return payload[claim];
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
} catch (e) {
|
|
2622
|
+
if (e instanceof JwtVerifyError) {
|
|
2623
|
+
throw new BadJwtError(e.message, { cause: e });
|
|
2402
2624
|
}
|
|
2403
|
-
|
|
2625
|
+
throw new JwtError("error occurred while attempting to decoding jwt", { cause: e });
|
|
2626
|
+
}
|
|
2404
2627
|
};
|
|
2405
|
-
const
|
|
2628
|
+
const authenticationConverter = token_converter_default({ uriQueryParameter: true });
|
|
2629
|
+
const authenticationConverterMatcher = async (exchange) => {
|
|
2630
|
+
try {
|
|
2631
|
+
const a = await authenticationConverter(exchange);
|
|
2632
|
+
return { match: a !== void 0 };
|
|
2633
|
+
} catch (e) {
|
|
2634
|
+
return { match: false };
|
|
2635
|
+
}
|
|
2636
|
+
};
|
|
2637
|
+
const entryPoint = token_entry_point_default({});
|
|
2638
|
+
this.#defaultEntryPoints.push([authenticationConverterMatcher, entryPoint]);
|
|
2639
|
+
const filter = resourceServer({
|
|
2640
|
+
storage: context.storage,
|
|
2641
|
+
entryPoint,
|
|
2642
|
+
converter: authenticationConverter,
|
|
2643
|
+
jwt: { decoder }
|
|
2644
|
+
});
|
|
2406
2645
|
filter[filterOrderSymbol] = filterOrder.authentication;
|
|
2407
2646
|
middleware.push(filter);
|
|
2408
2647
|
}
|
|
@@ -2424,10 +2663,12 @@ var config_default = (config, storage) => {
|
|
|
2424
2663
|
serverMatcher = matcher;
|
|
2425
2664
|
}
|
|
2426
2665
|
let manager2;
|
|
2427
|
-
if (access2.access === "
|
|
2666
|
+
if (access2.access === "permitted") {
|
|
2428
2667
|
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(true));
|
|
2429
|
-
|
|
2668
|
+
manager2.toString = () => "AuthorizationManager[permitted]";
|
|
2669
|
+
} else if (access2.access === "denied") {
|
|
2430
2670
|
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(false));
|
|
2671
|
+
manager2.toString = () => "AuthorizationManager[denied]";
|
|
2431
2672
|
} else if (access2.access === "authenticated") {
|
|
2432
2673
|
manager2 = new DefaultAuthorizationManager(async (p) => {
|
|
2433
2674
|
const authentication = await p;
|
|
@@ -2436,6 +2677,7 @@ var config_default = (config, storage) => {
|
|
|
2436
2677
|
}
|
|
2437
2678
|
return new AuthorizationDecision(false);
|
|
2438
2679
|
});
|
|
2680
|
+
manager2.toString = () => "AuthorizationManager[authenticated]";
|
|
2439
2681
|
} else {
|
|
2440
2682
|
throw new Error(`Unknown access type: ${JSON.stringify(access2)}`);
|
|
2441
2683
|
}
|
|
@@ -2444,7 +2686,7 @@ var config_default = (config, storage) => {
|
|
|
2444
2686
|
return delegatingAuthorizationManager({ mappings });
|
|
2445
2687
|
};
|
|
2446
2688
|
const manager = buildAuthorizationManager(config.authorize);
|
|
2447
|
-
const filter = authorizationFilter({ manager, storage });
|
|
2689
|
+
const filter = authorizationFilter({ manager, storage: context.storage });
|
|
2448
2690
|
filter[filterOrderSymbol] = filterOrder.authorization;
|
|
2449
2691
|
middleware.push(filter);
|
|
2450
2692
|
}
|
|
@@ -2460,8 +2702,80 @@ var config_default = (config, storage) => {
|
|
|
2460
2702
|
return middleware;
|
|
2461
2703
|
};
|
|
2462
2704
|
|
|
2705
|
+
// src/app/cors.ts
|
|
2706
|
+
function mockUpgradeExchange(path) {
|
|
2707
|
+
const request = new MockHttpRequest(path, "GET");
|
|
2708
|
+
request.headers.set("upgrade", "websocket");
|
|
2709
|
+
request["_req"] = { upgrade: true };
|
|
2710
|
+
return new DefaultWebExchange(request, new MockHttpResponse());
|
|
2711
|
+
}
|
|
2712
|
+
async function createCorsConfigSource(context) {
|
|
2713
|
+
const { sockets: routes3, cors } = context;
|
|
2714
|
+
const defaultCorsConfig = combineCorsConfig(PERMIT_DEFAULT_CONFIG, context.corsConfig);
|
|
2715
|
+
const validatedConfigs = [];
|
|
2716
|
+
for (const [path, route] of routes3) {
|
|
2717
|
+
let routeCorsConfig = defaultCorsConfig;
|
|
2718
|
+
const upgradeExchange = mockUpgradeExchange(path);
|
|
2719
|
+
for (const [matcher, config] of cors) {
|
|
2720
|
+
if ((await matcher(upgradeExchange)).match) {
|
|
2721
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, config);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, {
|
|
2725
|
+
allowOrigins: route.originFilters?.allow,
|
|
2726
|
+
allowMethods: ["GET", "CONNECT"],
|
|
2727
|
+
allowHeaders: ["upgrade", "connection", "origin", "sec-websocket-key", "sec-websocket-version", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2728
|
+
exposeHeaders: ["sec-websocket-accept", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2729
|
+
allowCredentials: route.authorize?.access !== "permitted"
|
|
2730
|
+
});
|
|
2731
|
+
validatedConfigs.push([and([upgradeMatcher, pattern(path)]), validateCorsConfig(routeCorsConfig)]);
|
|
2732
|
+
}
|
|
2733
|
+
for (const [matcher, config] of cors) {
|
|
2734
|
+
const routeCorsConfig = combineCorsConfig(defaultCorsConfig, config);
|
|
2735
|
+
validatedConfigs.push([matcher, validateCorsConfig(routeCorsConfig)]);
|
|
2736
|
+
}
|
|
2737
|
+
validatedConfigs.push([pattern(/\/api\/.*/), validateCorsConfig(defaultCorsConfig)]);
|
|
2738
|
+
return matchingCorsConfigSource({ mappings: validatedConfigs });
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
// src/app/auth.ts
|
|
2742
|
+
function createSecurityConfig(context) {
|
|
2743
|
+
const authorize = [];
|
|
2744
|
+
const defaultAccess = { access: context.authConfig?.type !== "none" ? "authenticated" : "permitted" };
|
|
2745
|
+
for (const [path, route] of context.sockets) {
|
|
2746
|
+
const rule = route.authorize ?? defaultAccess;
|
|
2747
|
+
let matcher = pattern(path, { method: "GET" });
|
|
2748
|
+
matcher = and([upgradeMatcher, matcher]);
|
|
2749
|
+
authorize.push([matcher, rule]);
|
|
2750
|
+
}
|
|
2751
|
+
authorize.push([pattern("/", { method: "GET" }), { access: "permitted" }]);
|
|
2752
|
+
authorize.push([pattern("/favicon.ico", { method: "GET" }), { access: "permitted" }]);
|
|
2753
|
+
authorize.push([pattern("/health", { method: "GET" }), { access: "permitted" }]);
|
|
2754
|
+
if (context.authorize.length > 0) {
|
|
2755
|
+
authorize.push(...context.authorize);
|
|
2756
|
+
}
|
|
2757
|
+
authorize.push(["any-exchange", defaultAccess]);
|
|
2758
|
+
return {
|
|
2759
|
+
authorize,
|
|
2760
|
+
basic: {
|
|
2761
|
+
disabled: context.authConfig?.type !== "basic",
|
|
2762
|
+
...context.authConfig?.basic
|
|
2763
|
+
},
|
|
2764
|
+
jwt: {
|
|
2765
|
+
disabled: context.authConfig?.type !== "oauth2",
|
|
2766
|
+
...context.authConfig?.oauth2?.jwt
|
|
2767
|
+
}
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
async function httpSecurity(context) {
|
|
2771
|
+
const corsConfigSource = await createCorsConfigSource(context);
|
|
2772
|
+
const config = createSecurityConfig(context);
|
|
2773
|
+
const { storage } = context;
|
|
2774
|
+
return config_default(config, { storage, corsConfigSource });
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2463
2777
|
// src/server.ts
|
|
2464
|
-
var
|
|
2778
|
+
var logger10 = getLogger("app");
|
|
2465
2779
|
function secureContextOptions(ssl) {
|
|
2466
2780
|
const options = {};
|
|
2467
2781
|
if (ssl.key) options.key = readFileSync(ssl.key);
|
|
@@ -2469,60 +2783,38 @@ function secureContextOptions(ssl) {
|
|
|
2469
2783
|
if (ssl.ca) options.ca = readFileSync(ssl.ca);
|
|
2470
2784
|
return options;
|
|
2471
2785
|
}
|
|
2472
|
-
function createListener(
|
|
2786
|
+
async function createListener(middleware, context, onSocketError) {
|
|
2787
|
+
const storage = context.storage;
|
|
2788
|
+
const security = await httpSecurity(context);
|
|
2473
2789
|
const listener = compose(
|
|
2474
|
-
server_header_default(),
|
|
2475
|
-
...
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
basic: {
|
|
2484
|
-
disabled: true,
|
|
2485
|
-
realm: "Gateway Server"
|
|
2486
|
-
},
|
|
2487
|
-
jwt: {
|
|
2488
|
-
disabled: true
|
|
2489
|
-
}
|
|
2490
|
-
}, storage),
|
|
2491
|
-
...cors_default({
|
|
2492
|
-
origins: { allow: [/http:\/\/localhost(:\d+)?/, /file:\//] },
|
|
2493
|
-
methods: { allow: ["GET", "HEAD", "POST", "DELETE"] },
|
|
2494
|
-
headers: { allow: "*" },
|
|
2495
|
-
credentials: { allow: true }
|
|
2496
|
-
}),
|
|
2497
|
-
...middleware,
|
|
2498
|
-
async ({ request, response }, next) => {
|
|
2499
|
-
const path = request.path ?? "/";
|
|
2500
|
-
const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
|
|
2501
|
-
if (path === "/" && route2.default === true) {
|
|
2502
|
-
return true;
|
|
2503
|
-
}
|
|
2504
|
-
});
|
|
2505
|
-
if (route) {
|
|
2506
|
-
if (request.method === "GET" && request._req["upgrade"] && request.headers.one("upgrade")?.toLowerCase() === "websocket") {
|
|
2790
|
+
server_header_default(context.serverHeader),
|
|
2791
|
+
...security,
|
|
2792
|
+
// websocket upgrade handler
|
|
2793
|
+
async (exchange, next) => {
|
|
2794
|
+
const [route, path] = findSocketRoute(exchange, context);
|
|
2795
|
+
if (route !== void 0) {
|
|
2796
|
+
const { request, response } = exchange;
|
|
2797
|
+
const upgradeMatchResult = await upgradeMatcher(exchange);
|
|
2798
|
+
if ((request.method === "GET" || request.method === "CONNECT") && upgradeMatchResult.match) {
|
|
2507
2799
|
const socket = request.socket;
|
|
2508
2800
|
const host = request.host;
|
|
2509
|
-
const
|
|
2510
|
-
if (route
|
|
2801
|
+
const info2 = socketKey(request._req.socket);
|
|
2802
|
+
if (route.wss) {
|
|
2511
2803
|
socket.removeListener("error", onSocketError);
|
|
2512
2804
|
const wss = route.wss;
|
|
2513
2805
|
if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
|
|
2514
|
-
|
|
2806
|
+
logger10.warn(`${info2} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
|
|
2515
2807
|
socket.destroy();
|
|
2516
2808
|
return;
|
|
2517
2809
|
}
|
|
2518
|
-
const origin = request.headers
|
|
2810
|
+
const origin = request.headers.one("origin");
|
|
2519
2811
|
if (!acceptsOrigin(origin, route.originFilters)) {
|
|
2520
|
-
|
|
2812
|
+
logger10.info(`${info2} dropping ws connection request from ${host} on ${path}. origin ${origin ?? "<missing>"}`);
|
|
2521
2813
|
socket.destroy();
|
|
2522
2814
|
return;
|
|
2523
2815
|
}
|
|
2524
|
-
if (
|
|
2525
|
-
|
|
2816
|
+
if (logger10.enabledFor("debug")) {
|
|
2817
|
+
logger10.debug(`${info2} accepted new ws connection request from ${host} on ${path}`);
|
|
2526
2818
|
}
|
|
2527
2819
|
wss.handleUpgrade(request._req, socket, request._req["_upgradeHead"], (ws) => {
|
|
2528
2820
|
response._res["_header"] = true;
|
|
@@ -2532,29 +2824,36 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2532
2824
|
wss.emit("connection", ws, request._req);
|
|
2533
2825
|
});
|
|
2534
2826
|
} else {
|
|
2535
|
-
|
|
2827
|
+
logger10.warn(`${info2} rejected upgrade request from ${host} on ${path}`);
|
|
2536
2828
|
socket.destroy();
|
|
2537
2829
|
}
|
|
2538
2830
|
} else {
|
|
2539
|
-
if (
|
|
2540
|
-
|
|
2831
|
+
if (route.default) {
|
|
2832
|
+
await next();
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
if (logger10.enabledFor("debug")) {
|
|
2836
|
+
logger10.debug(`rejecting request for ${path} with method ${request.method} from ${request.socket.remoteAddress}:${request.socket.remotePort} with headers: ${JSON.stringify(request._req.rawHeaders)}`);
|
|
2541
2837
|
}
|
|
2542
2838
|
response.statusCode = 426;
|
|
2543
|
-
response.
|
|
2839
|
+
response.headers.set("Upgrade", "websocket").set("Connection", "Upgrade").set("Content-Type", "text/plain");
|
|
2544
2840
|
await response.end(`This service [${request.path}] requires use of the websocket protocol.`);
|
|
2545
2841
|
}
|
|
2546
2842
|
} else {
|
|
2547
2843
|
await next();
|
|
2548
2844
|
}
|
|
2549
2845
|
},
|
|
2846
|
+
...middleware,
|
|
2847
|
+
// helth check
|
|
2550
2848
|
async ({ request, response }, next) => {
|
|
2551
2849
|
if (request.method === "GET" && request.path === "/health") {
|
|
2552
2850
|
response.statusCode = 200;
|
|
2553
|
-
response.
|
|
2851
|
+
await response.end(http.STATUS_CODES[200]);
|
|
2554
2852
|
} else {
|
|
2555
2853
|
await next();
|
|
2556
2854
|
}
|
|
2557
2855
|
},
|
|
2856
|
+
// home page
|
|
2558
2857
|
async ({ request, response }, next) => {
|
|
2559
2858
|
if (request.method === "GET" && request.path === "/") {
|
|
2560
2859
|
await response.end(`io.Gateway Server`);
|
|
@@ -2562,7 +2861,8 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2562
2861
|
await next();
|
|
2563
2862
|
}
|
|
2564
2863
|
},
|
|
2565
|
-
|
|
2864
|
+
// not found
|
|
2865
|
+
async ({ response }, _next) => {
|
|
2566
2866
|
response.statusCode = 404;
|
|
2567
2867
|
await response.end(http.STATUS_CODES[404]);
|
|
2568
2868
|
}
|
|
@@ -2571,17 +2871,17 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2571
2871
|
request.socket.addListener("error", onSocketError);
|
|
2572
2872
|
const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
|
|
2573
2873
|
return storage.run({ exchange }, async () => {
|
|
2574
|
-
if (
|
|
2874
|
+
if (logger10.enabledFor("debug")) {
|
|
2575
2875
|
const socket = exchange.request._req.socket;
|
|
2576
|
-
if (
|
|
2577
|
-
|
|
2876
|
+
if (logger10.enabledFor("debug")) {
|
|
2877
|
+
logger10.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
|
|
2578
2878
|
}
|
|
2579
2879
|
}
|
|
2580
2880
|
try {
|
|
2581
2881
|
return await listener(exchange);
|
|
2582
2882
|
} catch (e) {
|
|
2583
|
-
if (
|
|
2584
|
-
|
|
2883
|
+
if (logger10.enabledFor("warn")) {
|
|
2884
|
+
logger10.warn(`error processing request for ${exchange.path}`, e);
|
|
2585
2885
|
}
|
|
2586
2886
|
} finally {
|
|
2587
2887
|
await exchange.response.end();
|
|
@@ -2616,65 +2916,75 @@ function regexAwareReplacer(_key, value) {
|
|
|
2616
2916
|
}
|
|
2617
2917
|
var Factory = async (options) => {
|
|
2618
2918
|
const ssl = options.ssl;
|
|
2619
|
-
const createServer = ssl ? (options2,
|
|
2919
|
+
const createServer = ssl ? (options2, handler) => https.createServer({ ...options2, ...secureContextOptions(ssl) }, handler) : (options2, handler) => http.createServer(options2, handler);
|
|
2620
2920
|
const monitor = memoryMonitor(options.memory);
|
|
2621
2921
|
const middleware = [];
|
|
2622
|
-
const
|
|
2922
|
+
const context = {
|
|
2923
|
+
corsConfig: options.cors,
|
|
2924
|
+
cors: [],
|
|
2925
|
+
authConfig: options.auth,
|
|
2926
|
+
authorize: [],
|
|
2927
|
+
storage: new AsyncLocalStorage2(),
|
|
2928
|
+
sockets: /* @__PURE__ */ new Map()
|
|
2929
|
+
};
|
|
2623
2930
|
const gw = IOGateway7.Factory({ ...options.gateway });
|
|
2624
2931
|
if (options.gateway) {
|
|
2625
2932
|
const config = options.gateway;
|
|
2626
|
-
|
|
2933
|
+
const route = config.route ?? "/";
|
|
2934
|
+
context.sockets.set(route, {
|
|
2627
2935
|
default: config.route === void 0,
|
|
2628
2936
|
ping: options.gateway.ping,
|
|
2629
2937
|
factory: core_default.bind(gw),
|
|
2630
2938
|
maxConnections: config.limits?.max_connections,
|
|
2939
|
+
authorize: config.authorize,
|
|
2631
2940
|
originFilters: regexifyOriginFilters(config.origins)
|
|
2632
2941
|
});
|
|
2633
2942
|
}
|
|
2634
2943
|
if (options.mesh) {
|
|
2635
2944
|
const connections = new InMemoryNodeConnections(options.mesh.timeout ?? 6e4);
|
|
2636
|
-
|
|
2945
|
+
const authorize = options.mesh.authorize;
|
|
2946
|
+
middleware.push(...routes_default(connections, context, authorize));
|
|
2637
2947
|
const ping = options.mesh.ping ?? 3e4;
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2948
|
+
const originFilters = regexifyOriginFilters(options.mesh.origins);
|
|
2949
|
+
context.sockets.set("/broker", { factory: core_default2, ping, originFilters, authorize });
|
|
2950
|
+
context.sockets.set("/cluster", { factory: core_default4, ping, originFilters, authorize });
|
|
2951
|
+
context.sockets.set("/relays", { factory: core_default3, ping, originFilters, authorize });
|
|
2641
2952
|
}
|
|
2642
2953
|
if (options.metrics) {
|
|
2643
|
-
middleware.push(...await routes_default2(options.metrics));
|
|
2954
|
+
middleware.push(...await routes_default2(options.metrics, context));
|
|
2644
2955
|
}
|
|
2645
2956
|
const ports = portRange(options.port ?? 0);
|
|
2646
2957
|
const host = options.host;
|
|
2647
|
-
const
|
|
2958
|
+
const onSocketError = (err) => logger10.error(`socket error: ${err}`, err);
|
|
2959
|
+
const listener = await createListener(middleware, context, onSocketError);
|
|
2648
2960
|
const serverP = new Promise((resolve, reject) => {
|
|
2649
|
-
const onSocketError = (err) => logger7.error(`socket error: ${err}`, err);
|
|
2650
|
-
const listener = createListener(storage, middleware, routes3, onSocketError);
|
|
2651
2961
|
const server2 = createServer({}, listener);
|
|
2652
2962
|
server2.on("error", (e) => {
|
|
2653
2963
|
if (e["code"] === "EADDRINUSE") {
|
|
2654
|
-
|
|
2964
|
+
logger10.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
|
|
2655
2965
|
const { value: port } = ports.next();
|
|
2656
2966
|
if (port) {
|
|
2657
|
-
|
|
2967
|
+
logger10.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
|
|
2658
2968
|
server2.close();
|
|
2659
2969
|
server2.listen(port, host);
|
|
2660
2970
|
} else {
|
|
2661
|
-
|
|
2971
|
+
logger10.warn(`all configured port(s) ${options.port} are in use. closing...`);
|
|
2662
2972
|
server2.close();
|
|
2663
2973
|
reject(e);
|
|
2664
2974
|
}
|
|
2665
2975
|
} else {
|
|
2666
|
-
|
|
2976
|
+
logger10.error(`server error: ${e.message}`, e);
|
|
2667
2977
|
reject(e);
|
|
2668
2978
|
}
|
|
2669
2979
|
});
|
|
2670
2980
|
server2.on("listening", async () => {
|
|
2671
|
-
const
|
|
2672
|
-
for (const [path, route] of
|
|
2981
|
+
const info2 = server2.address();
|
|
2982
|
+
for (const [path, route] of context.sockets) {
|
|
2673
2983
|
try {
|
|
2674
|
-
|
|
2984
|
+
logger10.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
|
|
2675
2985
|
const wss = new WebSocketServer({ noServer: true });
|
|
2676
|
-
const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${
|
|
2677
|
-
const
|
|
2986
|
+
const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info2.port}${path}`;
|
|
2987
|
+
const handler = await route.factory({ endpoint, wss, storage: context.storage });
|
|
2678
2988
|
const pingInterval = route.ping;
|
|
2679
2989
|
if (pingInterval) {
|
|
2680
2990
|
const pingIntervalId = setInterval(() => {
|
|
@@ -2691,12 +3001,12 @@ var Factory = async (options) => {
|
|
|
2691
3001
|
});
|
|
2692
3002
|
}
|
|
2693
3003
|
route.wss = wss;
|
|
2694
|
-
route.close =
|
|
3004
|
+
route.close = handler.close?.bind(handler);
|
|
2695
3005
|
} catch (e) {
|
|
2696
|
-
|
|
3006
|
+
logger10.warn(`failed to init route ${path}`, e);
|
|
2697
3007
|
}
|
|
2698
3008
|
}
|
|
2699
|
-
|
|
3009
|
+
logger10.info(`http server listening on ${info2.address}:${info2.port}`);
|
|
2700
3010
|
resolve(server2);
|
|
2701
3011
|
});
|
|
2702
3012
|
server2.on("upgrade", (req, socket, head) => {
|
|
@@ -2707,16 +3017,16 @@ var Factory = async (options) => {
|
|
|
2707
3017
|
res.assignSocket(socket);
|
|
2708
3018
|
listener(req, res);
|
|
2709
3019
|
} catch (err) {
|
|
2710
|
-
|
|
3020
|
+
logger10.error(`upgrade error: ${err}`, err);
|
|
2711
3021
|
}
|
|
2712
3022
|
}).on("close", async () => {
|
|
2713
|
-
|
|
3023
|
+
logger10.info(`http server closed.`);
|
|
2714
3024
|
});
|
|
2715
3025
|
try {
|
|
2716
3026
|
const { value: port } = ports.next();
|
|
2717
3027
|
server2.listen(port, host);
|
|
2718
3028
|
} catch (e) {
|
|
2719
|
-
|
|
3029
|
+
logger10.error(`error starting web socket server`, e);
|
|
2720
3030
|
reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
|
|
2721
3031
|
}
|
|
2722
3032
|
});
|
|
@@ -2724,18 +3034,18 @@ var Factory = async (options) => {
|
|
|
2724
3034
|
return new class {
|
|
2725
3035
|
gateway = gw;
|
|
2726
3036
|
async close() {
|
|
2727
|
-
for (const [path, route] of
|
|
3037
|
+
for (const [path, route] of context.sockets) {
|
|
2728
3038
|
try {
|
|
2729
3039
|
if (route.close) {
|
|
2730
3040
|
await route.close();
|
|
2731
3041
|
}
|
|
2732
|
-
|
|
3042
|
+
logger10.info(`stopping ws server for [${path}]. clients: ${route.wss?.clients?.size ?? 0}`);
|
|
2733
3043
|
route.wss?.clients?.forEach((client) => {
|
|
2734
3044
|
client.terminate();
|
|
2735
3045
|
});
|
|
2736
3046
|
route.wss?.close();
|
|
2737
3047
|
} catch (e) {
|
|
2738
|
-
|
|
3048
|
+
logger10.warn(`error closing route ${path}`, e);
|
|
2739
3049
|
}
|
|
2740
3050
|
}
|
|
2741
3051
|
await promisify((cb) => {
|