@interopio/gateway-server 0.5.2-beta → 0.6.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +9 -0
- package/dist/gateway-ent.cjs +5 -2
- package/dist/gateway-ent.cjs.map +2 -2
- package/dist/gateway-ent.js +5 -2
- package/dist/gateway-ent.js.map +2 -2
- package/dist/index.cjs +809 -501
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +810 -502
- 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.cjs
CHANGED
|
@@ -169,7 +169,7 @@ var HttpServerRequest = class {
|
|
|
169
169
|
this._body ??= new Promise((resolve, reject) => {
|
|
170
170
|
const chunks = [];
|
|
171
171
|
this._req.on("error", (err) => reject(err)).on("data", (chunk) => chunks.push(chunk)).on("end", () => {
|
|
172
|
-
resolve(new Blob(chunks));
|
|
172
|
+
resolve(new Blob(chunks, { type: this.headers.one("content-type") }));
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
175
|
return this._body;
|
|
@@ -186,74 +186,79 @@ var HttpServerRequest = class {
|
|
|
186
186
|
}
|
|
187
187
|
get json() {
|
|
188
188
|
return this.body.then(async (blob) => {
|
|
189
|
-
|
|
189
|
+
if (blob.size === 0) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
const text = await blob.text();
|
|
193
|
+
const json = JSON.parse(text);
|
|
190
194
|
return json;
|
|
191
195
|
});
|
|
192
196
|
}
|
|
193
197
|
};
|
|
194
198
|
var IncomingMessageHeaders = class {
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
#msg;
|
|
200
|
+
constructor(msg) {
|
|
201
|
+
this.#msg = msg;
|
|
197
202
|
}
|
|
198
203
|
has(name) {
|
|
199
|
-
return this.
|
|
204
|
+
return this.#msg.headers[name] !== void 0;
|
|
200
205
|
}
|
|
201
206
|
get(name) {
|
|
202
|
-
return this.
|
|
207
|
+
return this.#msg.headers[name];
|
|
203
208
|
}
|
|
204
209
|
list(name) {
|
|
205
|
-
return toList(this.
|
|
210
|
+
return toList(this.#msg.headers[name]);
|
|
206
211
|
}
|
|
207
212
|
one(name) {
|
|
208
|
-
const value = this.
|
|
213
|
+
const value = this.#msg.headers[name];
|
|
209
214
|
if (Array.isArray(value)) {
|
|
210
215
|
return value[0];
|
|
211
216
|
}
|
|
212
217
|
return value;
|
|
213
218
|
}
|
|
214
219
|
keys() {
|
|
215
|
-
return Object.keys(this.
|
|
220
|
+
return Object.keys(this.#msg.headers).values();
|
|
216
221
|
}
|
|
217
222
|
};
|
|
218
223
|
var OutgoingMessageHeaders = class {
|
|
219
|
-
|
|
224
|
+
#msg;
|
|
220
225
|
constructor(msg) {
|
|
221
|
-
this
|
|
226
|
+
this.#msg = msg;
|
|
222
227
|
}
|
|
223
228
|
has(name) {
|
|
224
|
-
return this.
|
|
229
|
+
return this.#msg.hasHeader(name);
|
|
225
230
|
}
|
|
226
231
|
keys() {
|
|
227
|
-
return this.
|
|
232
|
+
return this.#msg.getHeaderNames().values();
|
|
228
233
|
}
|
|
229
234
|
get(name) {
|
|
230
|
-
return this.
|
|
235
|
+
return this.#msg.getHeader(name);
|
|
231
236
|
}
|
|
232
237
|
one(name) {
|
|
233
|
-
const value = this.
|
|
238
|
+
const value = this.#msg.getHeader(name);
|
|
234
239
|
if (Array.isArray(value)) {
|
|
235
240
|
return value[0];
|
|
236
241
|
}
|
|
237
242
|
return value;
|
|
238
243
|
}
|
|
239
244
|
set(name, value) {
|
|
240
|
-
if (!this.
|
|
245
|
+
if (!this.#msg.headersSent) {
|
|
241
246
|
if (Array.isArray(value)) {
|
|
242
247
|
value = value.map((v) => typeof v === "number" ? String(v) : v);
|
|
243
248
|
} else if (typeof value === "number") {
|
|
244
249
|
value = String(value);
|
|
245
250
|
}
|
|
246
251
|
if (value) {
|
|
247
|
-
this.
|
|
252
|
+
this.#msg.setHeader(name, value);
|
|
248
253
|
} else {
|
|
249
|
-
this.
|
|
254
|
+
this.#msg.removeHeader(name);
|
|
250
255
|
}
|
|
251
256
|
}
|
|
252
257
|
return this;
|
|
253
258
|
}
|
|
254
259
|
add(name, value) {
|
|
255
|
-
if (!this.
|
|
256
|
-
this.
|
|
260
|
+
if (!this.#msg.headersSent) {
|
|
261
|
+
this.#msg.appendHeader(name, value);
|
|
257
262
|
}
|
|
258
263
|
return this;
|
|
259
264
|
}
|
|
@@ -301,7 +306,7 @@ var HttpServerResponse = class {
|
|
|
301
306
|
}
|
|
302
307
|
end(chunk) {
|
|
303
308
|
if (!this._res.headersSent) {
|
|
304
|
-
return new Promise((resolve
|
|
309
|
+
return new Promise((resolve) => {
|
|
305
310
|
if (chunk === void 0) {
|
|
306
311
|
this._res.end(() => {
|
|
307
312
|
resolve(true);
|
|
@@ -331,6 +336,8 @@ var HttpServerResponse = class {
|
|
|
331
336
|
}
|
|
332
337
|
};
|
|
333
338
|
var DefaultWebExchange = class extends WebExchange {
|
|
339
|
+
request;
|
|
340
|
+
response;
|
|
334
341
|
constructor(request, response) {
|
|
335
342
|
super();
|
|
336
343
|
this.request = request;
|
|
@@ -419,6 +426,92 @@ var MapHttpHeaders = class extends Map {
|
|
|
419
426
|
return this;
|
|
420
427
|
}
|
|
421
428
|
};
|
|
429
|
+
var MockHttpRequest = class {
|
|
430
|
+
#url;
|
|
431
|
+
#body;
|
|
432
|
+
headers = new MapHttpHeaders();
|
|
433
|
+
constructor(url, method) {
|
|
434
|
+
if (typeof url === "string") {
|
|
435
|
+
if (URL.canParse(url)) {
|
|
436
|
+
url = new URL(url);
|
|
437
|
+
} else if (URL.canParse(url, "http://localhost")) {
|
|
438
|
+
url = new URL(url, "http://localhost");
|
|
439
|
+
} else {
|
|
440
|
+
throw new TypeError("URL cannot parse url");
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
this.#url = url;
|
|
444
|
+
this.method = method ?? "GET";
|
|
445
|
+
this.headers.set("Host", this.#url.hostname);
|
|
446
|
+
this.path = this.#url.pathname ?? "/";
|
|
447
|
+
}
|
|
448
|
+
method;
|
|
449
|
+
path;
|
|
450
|
+
get host() {
|
|
451
|
+
return requestToHost(this, this.#url.host);
|
|
452
|
+
}
|
|
453
|
+
get protocol() {
|
|
454
|
+
return requestToProtocol(this, this.#url.protocol.slice(0, -1));
|
|
455
|
+
}
|
|
456
|
+
get cookies() {
|
|
457
|
+
return parseCookies(this);
|
|
458
|
+
}
|
|
459
|
+
get body() {
|
|
460
|
+
const body = this.#body;
|
|
461
|
+
return body ? Promise.resolve(body) : Promise.reject(new Error(`no body set`));
|
|
462
|
+
}
|
|
463
|
+
get json() {
|
|
464
|
+
return this.body.then(async (blob) => JSON.parse(await blob.text()));
|
|
465
|
+
}
|
|
466
|
+
get text() {
|
|
467
|
+
return this.body.then(async (blob) => await blob.text());
|
|
468
|
+
}
|
|
469
|
+
set body(value) {
|
|
470
|
+
this.#body = value;
|
|
471
|
+
if (!this.headers.has("content-type")) {
|
|
472
|
+
this.headers.set("content-type", value.type || "application/octet-stream");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
get formData() {
|
|
476
|
+
return this.body.then(async (b) => new URLSearchParams(await b.text()));
|
|
477
|
+
}
|
|
478
|
+
get URL() {
|
|
479
|
+
return new URL(this.path + this.#url.search + this.#url.hash, `${this.protocol}://${this.host}`);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
var MockHttpResponse = class {
|
|
483
|
+
statusCode;
|
|
484
|
+
headers = new MapHttpHeaders();
|
|
485
|
+
cookies = [];
|
|
486
|
+
addCookie(cookie) {
|
|
487
|
+
this.cookies.push(cookie);
|
|
488
|
+
return this;
|
|
489
|
+
}
|
|
490
|
+
_body;
|
|
491
|
+
async end(chunk) {
|
|
492
|
+
if (this._body) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
switch (typeof chunk) {
|
|
496
|
+
case "string":
|
|
497
|
+
this._body = new Blob([chunk], { type: "text/plain" });
|
|
498
|
+
break;
|
|
499
|
+
case "object":
|
|
500
|
+
if (chunk instanceof Blob) {
|
|
501
|
+
this._body = chunk;
|
|
502
|
+
} else if (chunk !== null) {
|
|
503
|
+
this._body = new Blob([JSON.stringify(chunk)], { type: "application/json" });
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
case "undefined":
|
|
507
|
+
this._body = new Blob([]);
|
|
508
|
+
break;
|
|
509
|
+
default:
|
|
510
|
+
throw new Error(`Unsupported chunk type: ${typeof chunk}`);
|
|
511
|
+
}
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
422
515
|
|
|
423
516
|
// src/gateway/ws/core.ts
|
|
424
517
|
var import_gateway2 = require("@interopio/gateway");
|
|
@@ -589,22 +682,129 @@ var InMemoryNodeConnections = class {
|
|
|
589
682
|
}
|
|
590
683
|
};
|
|
591
684
|
|
|
685
|
+
// src/server/util/matchers.ts
|
|
686
|
+
var or = (matchers) => {
|
|
687
|
+
return async (exchange) => {
|
|
688
|
+
for (const matcher of matchers) {
|
|
689
|
+
const result = await matcher(exchange);
|
|
690
|
+
if (result.match) {
|
|
691
|
+
return { match: true };
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return { match: false };
|
|
695
|
+
};
|
|
696
|
+
};
|
|
697
|
+
var and = (matchers) => {
|
|
698
|
+
const matcher = async (exchange) => {
|
|
699
|
+
for (const matcher2 of matchers) {
|
|
700
|
+
const result = await matcher2(exchange);
|
|
701
|
+
if (!result.match) {
|
|
702
|
+
return { match: false };
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return { match: true };
|
|
706
|
+
};
|
|
707
|
+
matcher.toString = () => `and(${matchers.map((m) => m.toString()).join(", ")})`;
|
|
708
|
+
return matcher;
|
|
709
|
+
};
|
|
710
|
+
var not = (matcher) => {
|
|
711
|
+
return async (exchange) => {
|
|
712
|
+
const result = await matcher(exchange);
|
|
713
|
+
return { match: !result.match };
|
|
714
|
+
};
|
|
715
|
+
};
|
|
716
|
+
var anyExchange = async (_exchange) => {
|
|
717
|
+
return { match: true };
|
|
718
|
+
};
|
|
719
|
+
anyExchange.toString = () => "any-exchange";
|
|
720
|
+
var pattern = (pattern2, opts) => {
|
|
721
|
+
const method = opts?.method;
|
|
722
|
+
const matcher = async (exchange) => {
|
|
723
|
+
const request = exchange.request;
|
|
724
|
+
const path = request.path;
|
|
725
|
+
if (method !== void 0 && request.method !== method) {
|
|
726
|
+
return { match: false };
|
|
727
|
+
}
|
|
728
|
+
if (typeof pattern2 === "string") {
|
|
729
|
+
if (path === pattern2) {
|
|
730
|
+
return { match: true };
|
|
731
|
+
} else {
|
|
732
|
+
return { match: false };
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
const match = pattern2.exec(path);
|
|
736
|
+
if (match !== null) {
|
|
737
|
+
return { match: true };
|
|
738
|
+
} else {
|
|
739
|
+
return { match: false };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
matcher.toString = () => {
|
|
744
|
+
return `pattern(${pattern2.toString()})${method ? ` method=${method}` : ""}`;
|
|
745
|
+
};
|
|
746
|
+
return matcher;
|
|
747
|
+
};
|
|
748
|
+
var mediaType = (opts) => {
|
|
749
|
+
const shouldIgnore = (requestedMediaType) => {
|
|
750
|
+
if (opts.ignoredMediaTypes !== void 0) {
|
|
751
|
+
for (const ignoredMediaType of opts.ignoredMediaTypes) {
|
|
752
|
+
if (requestedMediaType === ignoredMediaType || ignoredMediaType === "*/*") {
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return false;
|
|
758
|
+
};
|
|
759
|
+
return async (exchange) => {
|
|
760
|
+
const request = exchange.request;
|
|
761
|
+
let requestMediaTypes;
|
|
762
|
+
try {
|
|
763
|
+
requestMediaTypes = request.headers.list("accept");
|
|
764
|
+
} catch (e) {
|
|
765
|
+
return { match: false };
|
|
766
|
+
}
|
|
767
|
+
for (const requestedMediaType of requestMediaTypes) {
|
|
768
|
+
if (shouldIgnore(requestedMediaType)) {
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
for (const mediaType2 of opts.mediaTypes) {
|
|
772
|
+
if (requestedMediaType.startsWith(mediaType2)) {
|
|
773
|
+
return { match: true };
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return { match: false };
|
|
778
|
+
};
|
|
779
|
+
};
|
|
780
|
+
var upgradeMatcher = async ({ request }) => {
|
|
781
|
+
const match = request["_req"]?.["upgrade"] && request.headers.one("upgrade")?.toLowerCase() === "websocket";
|
|
782
|
+
return { match };
|
|
783
|
+
};
|
|
784
|
+
upgradeMatcher.toString = () => "websocket upgrade";
|
|
785
|
+
|
|
592
786
|
// src/mesh/rest-directory/routes.ts
|
|
593
|
-
function routes(connections) {
|
|
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
|
+
}
|
|
594
795
|
return [
|
|
595
796
|
async (ctx, next) => {
|
|
596
797
|
if (ctx.method === "POST" && ctx.path === "/api/nodes") {
|
|
597
798
|
const json = await ctx.request.json;
|
|
598
799
|
if (!Array.isArray(json)) {
|
|
599
800
|
ctx.response.statusCode = 400;
|
|
600
|
-
ctx.response.
|
|
801
|
+
await ctx.response.end();
|
|
601
802
|
} else {
|
|
602
803
|
const nodes = json;
|
|
603
804
|
const result = connections.announce(nodes);
|
|
604
|
-
const body = JSON.stringify(result);
|
|
605
|
-
ctx.response.headers.set("content-type", "application/json");
|
|
805
|
+
const body = new Blob([JSON.stringify(result)], { type: "application/json" });
|
|
606
806
|
ctx.response.statusCode = 200;
|
|
607
|
-
ctx.response.
|
|
807
|
+
await ctx.response.end(body);
|
|
608
808
|
}
|
|
609
809
|
} else {
|
|
610
810
|
await next();
|
|
@@ -615,7 +815,7 @@ function routes(connections) {
|
|
|
615
815
|
const nodeId = path?.substring("/api/nodes/".length);
|
|
616
816
|
connections.remove(nodeId);
|
|
617
817
|
response.statusCode = 200;
|
|
618
|
-
response.
|
|
818
|
+
await response.end();
|
|
619
819
|
} else {
|
|
620
820
|
await next();
|
|
621
821
|
}
|
|
@@ -976,68 +1176,38 @@ var core_default4 = create4;
|
|
|
976
1176
|
|
|
977
1177
|
// src/metrics/routes.ts
|
|
978
1178
|
var logger5 = getLogger("metrics");
|
|
979
|
-
|
|
980
|
-
function loggedIn(auth, ctx) {
|
|
981
|
-
if (auth) {
|
|
982
|
-
const value = ctx.request.cookies.find((cookie) => cookie.name === COOKIE_NAME)?.value;
|
|
983
|
-
return value && parseInt(value) > Date.now();
|
|
984
|
-
}
|
|
985
|
-
return true;
|
|
986
|
-
}
|
|
987
|
-
async function routes2(config) {
|
|
1179
|
+
async function routes2(config, { cors, authorize }) {
|
|
988
1180
|
const { jsonFileAppender } = await import("@interopio/gateway/metrics/publisher/file");
|
|
989
1181
|
const appender = jsonFileAppender(logger5);
|
|
990
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
|
+
}
|
|
991
1187
|
return [
|
|
992
1188
|
async (ctx, next) => {
|
|
993
1189
|
if (ctx.method === "GET" && ctx.path === "/api/metrics") {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
} else {
|
|
997
|
-
ctx.response.statusCode = 302;
|
|
998
|
-
ctx.response.headers.set("location", "/api/login?redirectTo=/api/metrics");
|
|
999
|
-
}
|
|
1000
|
-
ctx.response._res.end();
|
|
1001
|
-
} else {
|
|
1002
|
-
await next();
|
|
1003
|
-
}
|
|
1004
|
-
},
|
|
1005
|
-
async (ctx, next) => {
|
|
1006
|
-
if (ctx.method === "GET" && ctx.path === "/api/login") {
|
|
1007
|
-
const redirectTo = new URLSearchParams(ctx.request.query ?? void 0).get("redirectTo");
|
|
1008
|
-
const expires = Date.now() + 180 * 1e3;
|
|
1009
|
-
ctx.response.addCookie({ name: COOKIE_NAME, value: `${expires}`, maxAge: Infinity, path: "/api", sameSite: "strict" });
|
|
1010
|
-
if (redirectTo) {
|
|
1011
|
-
ctx.response.statusCode = 302;
|
|
1012
|
-
ctx.response.headers.set("location", redirectTo);
|
|
1013
|
-
} else {
|
|
1014
|
-
ctx.response.statusCode = 200;
|
|
1015
|
-
}
|
|
1016
|
-
ctx.response._res.end();
|
|
1190
|
+
ctx.response.statusCode = 200;
|
|
1191
|
+
await ctx.response.end();
|
|
1017
1192
|
} else {
|
|
1018
1193
|
await next();
|
|
1019
1194
|
}
|
|
1020
1195
|
},
|
|
1021
|
-
async (
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
logger5.debug(`${JSON.stringify(update)}`);
|
|
1031
|
-
}
|
|
1032
|
-
if ((config.file?.status ?? false) || update.status === void 0) {
|
|
1033
|
-
await appender.write(update);
|
|
1034
|
-
}
|
|
1035
|
-
} catch (e) {
|
|
1036
|
-
logger5.error(`error processing metrics`, e);
|
|
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)}`);
|
|
1037
1205
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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);
|
|
1041
1211
|
}
|
|
1042
1212
|
} else {
|
|
1043
1213
|
await next();
|
|
@@ -1267,7 +1437,7 @@ async function stop(m) {
|
|
|
1267
1437
|
return await run(m, "stop");
|
|
1268
1438
|
}
|
|
1269
1439
|
|
|
1270
|
-
// src/
|
|
1440
|
+
// src/app/ws-client-verify.ts
|
|
1271
1441
|
var import_gateway5 = require("@interopio/gateway");
|
|
1272
1442
|
var log3 = getLogger("gateway.ws.client-verify");
|
|
1273
1443
|
function acceptsMissing(originFilters) {
|
|
@@ -1339,272 +1509,56 @@ function regexifyOriginFilters(originFilters) {
|
|
|
1339
1509
|
}
|
|
1340
1510
|
}
|
|
1341
1511
|
|
|
1342
|
-
// src/server/
|
|
1343
|
-
var
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
}
|
|
1349
|
-
const url = request.URL;
|
|
1350
|
-
const actualProtocol = url.protocol;
|
|
1351
|
-
const actualHost = url.host;
|
|
1352
|
-
const originUrl = new URL(origin);
|
|
1353
|
-
const originHost = originUrl.host;
|
|
1354
|
-
const originProtocol = originUrl.protocol;
|
|
1355
|
-
return actualProtocol === originProtocol && actualHost === originHost;
|
|
1356
|
-
}
|
|
1357
|
-
function isCorsRequest(request) {
|
|
1358
|
-
return request.headers.has("origin") && !isSameOrigin(request);
|
|
1359
|
-
}
|
|
1360
|
-
function isPreFlightRequest(request) {
|
|
1361
|
-
return request.method === "OPTIONS" && request.headers.has("origin") && request.headers.has("access-control-request-method");
|
|
1362
|
-
}
|
|
1363
|
-
var VARY_HEADERS = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"];
|
|
1364
|
-
function processRequest(exchange, config) {
|
|
1365
|
-
const { request, response } = exchange;
|
|
1366
|
-
const responseHeaders = response.headers;
|
|
1367
|
-
if (!responseHeaders.has("Vary")) {
|
|
1368
|
-
responseHeaders.set("Vary", VARY_HEADERS.join(", "));
|
|
1369
|
-
} else {
|
|
1370
|
-
const varyHeaders = responseHeaders.list("Vary");
|
|
1371
|
-
for (const header of VARY_HEADERS) {
|
|
1372
|
-
if (!varyHeaders.find((h) => h === header)) {
|
|
1373
|
-
varyHeaders.push(header);
|
|
1374
|
-
}
|
|
1512
|
+
// src/server/server-header.ts
|
|
1513
|
+
var serverHeader = (server) => {
|
|
1514
|
+
const enabled = typeof server === "string";
|
|
1515
|
+
return async ({ response }, next) => {
|
|
1516
|
+
if (server != false && !response.headers.has("server")) {
|
|
1517
|
+
response.headers.set("Server", server);
|
|
1375
1518
|
}
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1519
|
+
await next();
|
|
1520
|
+
};
|
|
1521
|
+
};
|
|
1522
|
+
var server_header_default = (server = "gateway-server") => serverHeader(server);
|
|
1523
|
+
|
|
1524
|
+
// src/app/route.ts
|
|
1525
|
+
function findSocketRoute({ request }, { sockets: routes3 }) {
|
|
1526
|
+
const path = request.path ?? "/";
|
|
1527
|
+
const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
|
|
1528
|
+
if (path === "/" && route2.default === true) {
|
|
1380
1529
|
return true;
|
|
1381
1530
|
}
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
logger6.debug(`reject: origin is malformed`);
|
|
1385
|
-
}
|
|
1386
|
-
rejectRequest(response);
|
|
1387
|
-
return false;
|
|
1388
|
-
}
|
|
1389
|
-
if (responseHeaders.has("access-control-allow-origin")) {
|
|
1390
|
-
logger6.trace(`skip: already contains "Access-Control-Allow-Origin"`);
|
|
1391
|
-
return true;
|
|
1392
|
-
}
|
|
1393
|
-
const preFlightRequest = isPreFlightRequest(request);
|
|
1394
|
-
if (config) {
|
|
1395
|
-
return handleInternal(exchange, config, preFlightRequest);
|
|
1396
|
-
}
|
|
1397
|
-
if (preFlightRequest) {
|
|
1398
|
-
rejectRequest(response);
|
|
1399
|
-
return false;
|
|
1400
|
-
}
|
|
1401
|
-
return true;
|
|
1531
|
+
});
|
|
1532
|
+
return [route, path];
|
|
1402
1533
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
}
|
|
1409
|
-
const
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
return origin.toLowerCase();
|
|
1414
|
-
}
|
|
1415
|
-
return origin;
|
|
1416
|
-
});
|
|
1534
|
+
|
|
1535
|
+
// src/server/security/http-headers.ts
|
|
1536
|
+
var staticServerHttpHeadersWriter = (headers2) => {
|
|
1537
|
+
return async (exchange) => {
|
|
1538
|
+
let containsNoHeaders = true;
|
|
1539
|
+
const { response } = exchange;
|
|
1540
|
+
for (const name of headers2.keys()) {
|
|
1541
|
+
if (response.headers.has(name)) {
|
|
1542
|
+
containsNoHeaders = false;
|
|
1543
|
+
}
|
|
1417
1544
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
validateConfig(config);
|
|
1423
|
-
return async (ctx, next) => {
|
|
1424
|
-
const isValid = processRequest(ctx, config);
|
|
1425
|
-
if (!isValid || isPreFlightRequest(ctx.request)) {
|
|
1426
|
-
await ctx.response.end();
|
|
1427
|
-
} else {
|
|
1428
|
-
await next();
|
|
1545
|
+
if (containsNoHeaders) {
|
|
1546
|
+
for (const [name, value] of headers2) {
|
|
1547
|
+
response.headers.set(name, value);
|
|
1548
|
+
}
|
|
1429
1549
|
}
|
|
1430
1550
|
};
|
|
1431
1551
|
};
|
|
1432
|
-
var
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
if (allowOrigin === void 0) {
|
|
1443
|
-
if (logger6.enabledFor("debug")) {
|
|
1444
|
-
logger6.debug(`reject: '${requestOrigin}' origin is not allowed`);
|
|
1445
|
-
}
|
|
1446
|
-
rejectRequest(response);
|
|
1447
|
-
return false;
|
|
1448
|
-
}
|
|
1449
|
-
const requestMethod = getMethodToUse(request, preFlightRequest);
|
|
1450
|
-
const allowMethods = checkMethods(config, requestMethod);
|
|
1451
|
-
if (allowMethods === void 0) {
|
|
1452
|
-
if (logger6.enabledFor("debug")) {
|
|
1453
|
-
logger6.debug(`reject: HTTP '${requestMethod}' is not allowed`);
|
|
1454
|
-
}
|
|
1455
|
-
rejectRequest(response);
|
|
1456
|
-
return false;
|
|
1457
|
-
}
|
|
1458
|
-
const requestHeaders = getHeadersToUse(request, preFlightRequest);
|
|
1459
|
-
const allowHeaders = checkHeaders(config, requestHeaders);
|
|
1460
|
-
if (preFlightRequest && allowHeaders === void 0) {
|
|
1461
|
-
if (logger6.enabledFor("debug")) {
|
|
1462
|
-
logger6.debug(`reject: headers '${requestHeaders}' are not allowed`);
|
|
1463
|
-
}
|
|
1464
|
-
rejectRequest(response);
|
|
1465
|
-
return false;
|
|
1466
|
-
}
|
|
1467
|
-
responseHeaders.set("access-control-allow-origin", allowOrigin);
|
|
1468
|
-
if (preFlightRequest) {
|
|
1469
|
-
responseHeaders.set("access-control-allow-methods", allowMethods.join(","));
|
|
1470
|
-
}
|
|
1471
|
-
if (preFlightRequest && allowHeaders !== void 0 && allowHeaders.length > 0) {
|
|
1472
|
-
responseHeaders.set("access-control-allow-headers", allowHeaders.join(", "));
|
|
1473
|
-
}
|
|
1474
|
-
const exposeHeaders = config.headers?.expose;
|
|
1475
|
-
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
1476
|
-
responseHeaders.set("access-control-expose-headers", exposeHeaders.join(", "));
|
|
1477
|
-
}
|
|
1478
|
-
if (config.credentials?.allow) {
|
|
1479
|
-
responseHeaders.set("access-control-allow-credentials", "true");
|
|
1480
|
-
}
|
|
1481
|
-
if (config.privateNetwork?.allow && request.headers.one("access-control-request-private-network") === "true") {
|
|
1482
|
-
responseHeaders.set("access-control-allow-private-network", "true");
|
|
1483
|
-
}
|
|
1484
|
-
return true;
|
|
1485
|
-
}
|
|
1486
|
-
var ALL = "*";
|
|
1487
|
-
var DEFAULT_METHODS = ["GET", "HEAD"];
|
|
1488
|
-
function validateAllowCredentials(config) {
|
|
1489
|
-
if (config.credentials?.allow === true && config.origins?.allow === ALL) {
|
|
1490
|
-
throw new Error(`when credentials.allow is true origins.allow cannot be "*"`);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
function validateAllowPrivateNetwork(config) {
|
|
1494
|
-
if (config.privateNetwork?.allow === true && config.origins?.allow === ALL) {
|
|
1495
|
-
throw new Error(`when privateNetwork.allow is true origins.allow cannot be "*"`);
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
function checkOrigin(config, origin) {
|
|
1499
|
-
if (origin) {
|
|
1500
|
-
const allowedOrigins = config.origins?.allow;
|
|
1501
|
-
if (allowedOrigins) {
|
|
1502
|
-
if (allowedOrigins === ALL) {
|
|
1503
|
-
validateAllowCredentials(config);
|
|
1504
|
-
validateAllowPrivateNetwork(config);
|
|
1505
|
-
return ALL;
|
|
1506
|
-
}
|
|
1507
|
-
const originToCheck = trimTrailingSlash(origin.toLowerCase());
|
|
1508
|
-
for (const allowedOrigin of allowedOrigins) {
|
|
1509
|
-
if (allowedOrigin === ALL || import_gateway6.IOGateway.Filtering.valueMatches(allowedOrigin, originToCheck)) {
|
|
1510
|
-
return origin;
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
function checkMethods(config, requestMethod) {
|
|
1517
|
-
if (requestMethod) {
|
|
1518
|
-
const allowedMethods = config.methods?.allow ?? DEFAULT_METHODS;
|
|
1519
|
-
if (allowedMethods === ALL) {
|
|
1520
|
-
return [requestMethod];
|
|
1521
|
-
}
|
|
1522
|
-
if (import_gateway6.IOGateway.Filtering.valuesMatch(allowedMethods, requestMethod)) {
|
|
1523
|
-
return allowedMethods;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
function checkHeaders(config, requestHeaders) {
|
|
1528
|
-
if (requestHeaders === void 0) {
|
|
1529
|
-
return;
|
|
1530
|
-
}
|
|
1531
|
-
if (requestHeaders.length == 0) {
|
|
1532
|
-
return [];
|
|
1533
|
-
}
|
|
1534
|
-
const allowedHeaders = config.headers?.allow;
|
|
1535
|
-
if (allowedHeaders === void 0) {
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
const allowAnyHeader = allowedHeaders === ALL;
|
|
1539
|
-
const result = [];
|
|
1540
|
-
for (const requestHeader of requestHeaders) {
|
|
1541
|
-
const value = requestHeader?.trim();
|
|
1542
|
-
if (value) {
|
|
1543
|
-
if (allowAnyHeader) {
|
|
1544
|
-
result.push(value);
|
|
1545
|
-
} else {
|
|
1546
|
-
for (const allowedHeader of allowedHeaders) {
|
|
1547
|
-
if (value.toLowerCase() == allowedHeader) {
|
|
1548
|
-
result.push(value);
|
|
1549
|
-
break;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
if (result.length > 0) {
|
|
1556
|
-
return result;
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
function trimTrailingSlash(origin) {
|
|
1560
|
-
return origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
1561
|
-
}
|
|
1562
|
-
function getMethodToUse(request, isPreFlight) {
|
|
1563
|
-
return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
|
|
1564
|
-
}
|
|
1565
|
-
function getHeadersToUse(request, isPreFlight) {
|
|
1566
|
-
const headers2 = request.headers;
|
|
1567
|
-
return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
// src/server/server-header.ts
|
|
1571
|
-
var serverHeader = (server) => {
|
|
1572
|
-
return async ({ response }, next) => {
|
|
1573
|
-
if (!response.headers.has("server")) {
|
|
1574
|
-
response.headers.set("Server", server);
|
|
1575
|
-
}
|
|
1576
|
-
await next();
|
|
1577
|
-
};
|
|
1578
|
-
};
|
|
1579
|
-
var server_header_default = (server = "gateway-server") => serverHeader(server);
|
|
1580
|
-
|
|
1581
|
-
// src/server/security/http-headers.ts
|
|
1582
|
-
var staticServerHttpHeadersWriter = (headers2) => {
|
|
1583
|
-
return async (exchange) => {
|
|
1584
|
-
let containsNoHeaders = true;
|
|
1585
|
-
const { response } = exchange;
|
|
1586
|
-
for (const name of headers2.keys()) {
|
|
1587
|
-
if (response.headers.has(name)) {
|
|
1588
|
-
containsNoHeaders = false;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
if (containsNoHeaders) {
|
|
1592
|
-
for (const [name, value] of headers2) {
|
|
1593
|
-
response.headers.set(name, value);
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
};
|
|
1597
|
-
};
|
|
1598
|
-
var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1599
|
-
new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
|
|
1600
|
-
);
|
|
1601
|
-
var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1602
|
-
new MapHttpHeaders().add("x-content-type-options", "nosniff")
|
|
1603
|
-
);
|
|
1604
|
-
var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
|
|
1605
|
-
let headerValue = `max-age=${maxAgeInSeconds}`;
|
|
1606
|
-
if (includeSubDomains) {
|
|
1607
|
-
headerValue += " ; includeSubDomains";
|
|
1552
|
+
var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1553
|
+
new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
|
|
1554
|
+
);
|
|
1555
|
+
var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1556
|
+
new MapHttpHeaders().add("x-content-type-options", "nosniff")
|
|
1557
|
+
);
|
|
1558
|
+
var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
|
|
1559
|
+
let headerValue = `max-age=${maxAgeInSeconds}`;
|
|
1560
|
+
if (includeSubDomains) {
|
|
1561
|
+
headerValue += " ; includeSubDomains";
|
|
1608
1562
|
}
|
|
1609
1563
|
if (preload) {
|
|
1610
1564
|
headerValue += " ; preload";
|
|
@@ -1764,18 +1718,18 @@ var AuthorizationDecision = class {
|
|
|
1764
1718
|
granted;
|
|
1765
1719
|
};
|
|
1766
1720
|
var DefaultAuthorizationManager = class {
|
|
1767
|
-
check;
|
|
1721
|
+
#check;
|
|
1768
1722
|
constructor(check) {
|
|
1769
|
-
this
|
|
1723
|
+
this.#check = check;
|
|
1770
1724
|
}
|
|
1771
1725
|
async verify(authentication, object) {
|
|
1772
|
-
const decision = await this
|
|
1726
|
+
const decision = await this.#check(authentication, object);
|
|
1773
1727
|
if (!decision?.granted) {
|
|
1774
1728
|
throw new AccessDeniedError("Access denied");
|
|
1775
1729
|
}
|
|
1776
1730
|
}
|
|
1777
1731
|
async authorize(authentication, object) {
|
|
1778
|
-
return await this
|
|
1732
|
+
return await this.#check(authentication, object);
|
|
1779
1733
|
}
|
|
1780
1734
|
};
|
|
1781
1735
|
var AuthenticationServiceError = class extends AuthenticationError {
|
|
@@ -1829,71 +1783,6 @@ var httpBasicAuthenticationConverter = (opts) => {
|
|
|
1829
1783
|
};
|
|
1830
1784
|
};
|
|
1831
1785
|
|
|
1832
|
-
// src/server/util/matchers.ts
|
|
1833
|
-
var or = (matchers) => {
|
|
1834
|
-
return async (exchange) => {
|
|
1835
|
-
for (const matcher of matchers) {
|
|
1836
|
-
const result = await matcher(exchange);
|
|
1837
|
-
if (result.match) {
|
|
1838
|
-
return { match: true };
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
return { match: false };
|
|
1842
|
-
};
|
|
1843
|
-
};
|
|
1844
|
-
var and = (matchers) => {
|
|
1845
|
-
return async (exchange) => {
|
|
1846
|
-
for (const matcher of matchers) {
|
|
1847
|
-
const result = await matcher(exchange);
|
|
1848
|
-
if (!result.match) {
|
|
1849
|
-
return { match: false };
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
return { match: true };
|
|
1853
|
-
};
|
|
1854
|
-
};
|
|
1855
|
-
var not = (matcher) => {
|
|
1856
|
-
return async (exchange) => {
|
|
1857
|
-
const result = await matcher(exchange);
|
|
1858
|
-
return { match: !result.match };
|
|
1859
|
-
};
|
|
1860
|
-
};
|
|
1861
|
-
var anyExchange = async (_exchange) => {
|
|
1862
|
-
return { match: true };
|
|
1863
|
-
};
|
|
1864
|
-
var mediaType = (opts) => {
|
|
1865
|
-
const shouldIgnore = (requestedMediaType) => {
|
|
1866
|
-
if (opts.ignoredMediaTypes !== void 0) {
|
|
1867
|
-
for (const ignoredMediaType of opts.ignoredMediaTypes) {
|
|
1868
|
-
if (requestedMediaType === ignoredMediaType || ignoredMediaType === "*/*") {
|
|
1869
|
-
return true;
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
return false;
|
|
1874
|
-
};
|
|
1875
|
-
return async (exchange) => {
|
|
1876
|
-
const request = exchange.request;
|
|
1877
|
-
let requestMediaTypes;
|
|
1878
|
-
try {
|
|
1879
|
-
requestMediaTypes = request.headers.list("accept");
|
|
1880
|
-
} catch (e) {
|
|
1881
|
-
return { match: false };
|
|
1882
|
-
}
|
|
1883
|
-
for (const requestedMediaType of requestMediaTypes) {
|
|
1884
|
-
if (shouldIgnore(requestedMediaType)) {
|
|
1885
|
-
continue;
|
|
1886
|
-
}
|
|
1887
|
-
for (const mediaType2 of opts.mediaTypes) {
|
|
1888
|
-
if (requestedMediaType.startsWith(mediaType2)) {
|
|
1889
|
-
return { match: true };
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
return { match: false };
|
|
1894
|
-
};
|
|
1895
|
-
};
|
|
1896
|
-
|
|
1897
1786
|
// src/server/security/security-context.ts
|
|
1898
1787
|
var import_node_async_hooks = require("node:async_hooks");
|
|
1899
1788
|
var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
|
|
@@ -2207,6 +2096,7 @@ var httpStatusEntryPoint = (opts) => {
|
|
|
2207
2096
|
};
|
|
2208
2097
|
|
|
2209
2098
|
// src/server/security/delegating-entry-point.ts
|
|
2099
|
+
var logger6 = getLogger("auth.entry-point");
|
|
2210
2100
|
var delegatingEntryPoint = (opts) => {
|
|
2211
2101
|
const defaultEntryPoint = opts.defaultEntryPoint ?? (async ({ response }, _error) => {
|
|
2212
2102
|
response.statusCode = 401;
|
|
@@ -2214,11 +2104,20 @@ var delegatingEntryPoint = (opts) => {
|
|
|
2214
2104
|
});
|
|
2215
2105
|
return async (exchange, error) => {
|
|
2216
2106
|
for (const [matcher, entryPoint] of opts.entryPoints) {
|
|
2107
|
+
if (logger6.enabledFor("debug")) {
|
|
2108
|
+
logger6.debug(`trying to match using: ${matcher}`);
|
|
2109
|
+
}
|
|
2217
2110
|
const match = await matcher(exchange);
|
|
2218
2111
|
if (match.match) {
|
|
2112
|
+
if (logger6.enabledFor("debug")) {
|
|
2113
|
+
logger6.debug(`match found. using default entry point ${entryPoint}`);
|
|
2114
|
+
}
|
|
2219
2115
|
return entryPoint(exchange, error);
|
|
2220
2116
|
}
|
|
2221
2117
|
}
|
|
2118
|
+
if (logger6.enabledFor("debug")) {
|
|
2119
|
+
logger6.debug(`no match found. using default entry point ${defaultEntryPoint}`);
|
|
2120
|
+
}
|
|
2222
2121
|
return defaultEntryPoint(exchange, error);
|
|
2223
2122
|
};
|
|
2224
2123
|
};
|
|
@@ -2226,8 +2125,8 @@ var delegatingEntryPoint = (opts) => {
|
|
|
2226
2125
|
// src/server/security/delegating-success-handler.ts
|
|
2227
2126
|
var delegatingSuccessHandler = (handlers) => {
|
|
2228
2127
|
return async ({ exchange, next }, authentication) => {
|
|
2229
|
-
for (const
|
|
2230
|
-
await
|
|
2128
|
+
for (const handler of handlers) {
|
|
2129
|
+
await handler({ exchange, next }, authentication);
|
|
2231
2130
|
}
|
|
2232
2131
|
};
|
|
2233
2132
|
};
|
|
@@ -2321,14 +2220,21 @@ var errorFilter = (opts) => {
|
|
|
2321
2220
|
};
|
|
2322
2221
|
|
|
2323
2222
|
// src/server/security/authorization-filter.ts
|
|
2223
|
+
var logger7 = getLogger("security.auth");
|
|
2324
2224
|
function authorizationFilter(opts) {
|
|
2325
2225
|
const { manager, storage } = opts;
|
|
2326
2226
|
return async (exchange, next) => {
|
|
2327
2227
|
const promise = AsyncStorageSecurityContextHolder.getContext(storage).then((c) => c?.authentication);
|
|
2328
2228
|
try {
|
|
2329
2229
|
await manager.verify(promise, exchange);
|
|
2230
|
+
if (logger7.enabledFor("debug")) {
|
|
2231
|
+
logger7.debug("authorization successful");
|
|
2232
|
+
}
|
|
2330
2233
|
} catch (error) {
|
|
2331
2234
|
if (error instanceof AccessDeniedError) {
|
|
2235
|
+
if (logger7.enabledFor("debug")) {
|
|
2236
|
+
logger7.debug(`authorization failed: ${error.message}`);
|
|
2237
|
+
}
|
|
2332
2238
|
}
|
|
2333
2239
|
throw error;
|
|
2334
2240
|
}
|
|
@@ -2337,12 +2243,14 @@ function authorizationFilter(opts) {
|
|
|
2337
2243
|
}
|
|
2338
2244
|
|
|
2339
2245
|
// src/server/security/delegating-authorization-manager.ts
|
|
2246
|
+
var logger8 = getLogger("auth");
|
|
2340
2247
|
function delegatingAuthorizationManager(opts) {
|
|
2341
2248
|
const check = async (authentication, exchange) => {
|
|
2342
2249
|
let decision;
|
|
2343
2250
|
for (const [matcher, manager] of opts.mappings) {
|
|
2344
2251
|
if ((await matcher(exchange))?.match) {
|
|
2345
|
-
|
|
2252
|
+
logger8.debug(`checking authorization on '${exchange.path}' using [${matcher}, ${manager}]`);
|
|
2253
|
+
const checkResult = await manager.authorize(authentication, { exchange });
|
|
2346
2254
|
if (checkResult !== void 0) {
|
|
2347
2255
|
decision = checkResult;
|
|
2348
2256
|
break;
|
|
@@ -2355,6 +2263,308 @@ function delegatingAuthorizationManager(opts) {
|
|
|
2355
2263
|
return new DefaultAuthorizationManager(check);
|
|
2356
2264
|
}
|
|
2357
2265
|
|
|
2266
|
+
// src/server/cors.ts
|
|
2267
|
+
var import_gateway6 = require("@interopio/gateway");
|
|
2268
|
+
function isSameOrigin(request) {
|
|
2269
|
+
const origin = request.headers.one("origin");
|
|
2270
|
+
if (origin === void 0) {
|
|
2271
|
+
return true;
|
|
2272
|
+
}
|
|
2273
|
+
const url = request.URL;
|
|
2274
|
+
const actualProtocol = url.protocol;
|
|
2275
|
+
const actualHost = url.host;
|
|
2276
|
+
const originUrl = URL.parse(origin);
|
|
2277
|
+
const originHost = originUrl?.host;
|
|
2278
|
+
const originProtocol = originUrl?.protocol;
|
|
2279
|
+
return actualProtocol === originProtocol && actualHost === originHost;
|
|
2280
|
+
}
|
|
2281
|
+
function isCorsRequest(request) {
|
|
2282
|
+
return request.headers.has("origin") && !isSameOrigin(request);
|
|
2283
|
+
}
|
|
2284
|
+
function isPreFlightRequest(request) {
|
|
2285
|
+
return request.method === "OPTIONS" && request.headers.has("origin") && request.headers.has("access-control-request-method");
|
|
2286
|
+
}
|
|
2287
|
+
var VARY_HEADERS = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"];
|
|
2288
|
+
var processRequest = (exchange, config) => {
|
|
2289
|
+
const { request, response } = exchange;
|
|
2290
|
+
const responseHeaders = response.headers;
|
|
2291
|
+
if (!responseHeaders.has("Vary")) {
|
|
2292
|
+
responseHeaders.set("Vary", VARY_HEADERS.join(", "));
|
|
2293
|
+
} else {
|
|
2294
|
+
const varyHeaders = responseHeaders.list("Vary");
|
|
2295
|
+
for (const header of VARY_HEADERS) {
|
|
2296
|
+
if (!varyHeaders.find((h) => h === header)) {
|
|
2297
|
+
varyHeaders.push(header);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
responseHeaders.set("Vary", varyHeaders.join(", "));
|
|
2301
|
+
}
|
|
2302
|
+
try {
|
|
2303
|
+
if (!isCorsRequest(request)) {
|
|
2304
|
+
return true;
|
|
2305
|
+
}
|
|
2306
|
+
} catch (e) {
|
|
2307
|
+
if (logger9.enabledFor("debug")) {
|
|
2308
|
+
logger9.debug(`reject: origin is malformed`);
|
|
2309
|
+
}
|
|
2310
|
+
rejectRequest(response);
|
|
2311
|
+
return false;
|
|
2312
|
+
}
|
|
2313
|
+
if (responseHeaders.has("access-control-allow-origin")) {
|
|
2314
|
+
logger9.trace(`skip: already contains "Access-Control-Allow-Origin"`);
|
|
2315
|
+
return true;
|
|
2316
|
+
}
|
|
2317
|
+
const preFlightRequest = isPreFlightRequest(request);
|
|
2318
|
+
if (config) {
|
|
2319
|
+
return handleInternal(exchange, config, preFlightRequest);
|
|
2320
|
+
}
|
|
2321
|
+
if (preFlightRequest) {
|
|
2322
|
+
rejectRequest(response);
|
|
2323
|
+
return false;
|
|
2324
|
+
}
|
|
2325
|
+
return true;
|
|
2326
|
+
};
|
|
2327
|
+
var DEFAULT_PERMIT_ALL = ["*"];
|
|
2328
|
+
var DEFAULT_PERMIT_METHODS = ["GET", "HEAD", "POST"];
|
|
2329
|
+
var PERMIT_DEFAULT_CONFIG = {
|
|
2330
|
+
allowOrigins: DEFAULT_PERMIT_ALL,
|
|
2331
|
+
allowMethods: DEFAULT_PERMIT_METHODS,
|
|
2332
|
+
allowHeaders: DEFAULT_PERMIT_ALL,
|
|
2333
|
+
maxAge: 1800
|
|
2334
|
+
// 30 minutes
|
|
2335
|
+
};
|
|
2336
|
+
function validateCorsConfig(config) {
|
|
2337
|
+
if (config) {
|
|
2338
|
+
const allowHeaders = config.allowHeaders;
|
|
2339
|
+
if (allowHeaders && allowHeaders !== ALL) {
|
|
2340
|
+
config = {
|
|
2341
|
+
...config,
|
|
2342
|
+
allowHeaders: allowHeaders.map((header) => header.toLowerCase())
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
const allowOrigins = config.allowOrigins;
|
|
2346
|
+
if (allowOrigins) {
|
|
2347
|
+
if (allowOrigins === "*") {
|
|
2348
|
+
validateAllowCredentials(config);
|
|
2349
|
+
validateAllowPrivateNetwork(config);
|
|
2350
|
+
} else {
|
|
2351
|
+
config = {
|
|
2352
|
+
...config,
|
|
2353
|
+
allowOrigins: allowOrigins.map((origin) => {
|
|
2354
|
+
if (typeof origin === "string" && origin !== ALL) {
|
|
2355
|
+
origin = import_gateway6.IOGateway.Filtering.regexify(origin);
|
|
2356
|
+
if (typeof origin === "string") {
|
|
2357
|
+
return trimTrailingSlash(origin).toLowerCase();
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return origin;
|
|
2361
|
+
})
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
return config;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
function combine(source, other) {
|
|
2369
|
+
if (other === void 0) {
|
|
2370
|
+
return source !== void 0 ? source === ALL ? [ALL] : source : [];
|
|
2371
|
+
}
|
|
2372
|
+
if (source === void 0) {
|
|
2373
|
+
return other === ALL ? [ALL] : other;
|
|
2374
|
+
}
|
|
2375
|
+
if (source == DEFAULT_PERMIT_ALL || source === DEFAULT_PERMIT_METHODS) {
|
|
2376
|
+
return other === ALL ? [ALL] : other;
|
|
2377
|
+
}
|
|
2378
|
+
if (other == DEFAULT_PERMIT_ALL || other === DEFAULT_PERMIT_METHODS) {
|
|
2379
|
+
return source === ALL ? [ALL] : source;
|
|
2380
|
+
}
|
|
2381
|
+
if (source === ALL || source.includes(ALL) || other === ALL || other.includes(ALL)) {
|
|
2382
|
+
return [ALL];
|
|
2383
|
+
}
|
|
2384
|
+
const combined = /* @__PURE__ */ new Set();
|
|
2385
|
+
source.forEach((v) => combined.add(v));
|
|
2386
|
+
other.forEach((v) => combined.add(v));
|
|
2387
|
+
return Array.from(combined);
|
|
2388
|
+
}
|
|
2389
|
+
var combineCorsConfig = (source, other) => {
|
|
2390
|
+
if (other === void 0) {
|
|
2391
|
+
return source;
|
|
2392
|
+
}
|
|
2393
|
+
const config = {
|
|
2394
|
+
allowOrigins: combine(source.allowOrigins, other?.allowOrigins),
|
|
2395
|
+
allowMethods: combine(source.allowMethods, other?.allowMethods),
|
|
2396
|
+
allowHeaders: combine(source.allowHeaders, other?.allowHeaders),
|
|
2397
|
+
exposeHeaders: combine(source.exposeHeaders, other?.exposeHeaders),
|
|
2398
|
+
allowCredentials: other?.allowCredentials ?? source.allowCredentials,
|
|
2399
|
+
allowPrivateNetwork: other?.allowPrivateNetwork ?? source.allowPrivateNetwork,
|
|
2400
|
+
maxAge: other?.maxAge ?? source.maxAge
|
|
2401
|
+
};
|
|
2402
|
+
return config;
|
|
2403
|
+
};
|
|
2404
|
+
var corsFilter = (opts) => {
|
|
2405
|
+
const source = opts.corsConfigSource;
|
|
2406
|
+
const processor = opts.corsProcessor ?? processRequest;
|
|
2407
|
+
return async (ctx, next) => {
|
|
2408
|
+
const config = await source(ctx);
|
|
2409
|
+
const isValid = processor(ctx, config);
|
|
2410
|
+
if (!isValid || isPreFlightRequest(ctx.request)) {
|
|
2411
|
+
return;
|
|
2412
|
+
} else {
|
|
2413
|
+
await next();
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
};
|
|
2417
|
+
var cors_default = corsFilter;
|
|
2418
|
+
var logger9 = getLogger("cors");
|
|
2419
|
+
function rejectRequest(response) {
|
|
2420
|
+
response.statusCode = 403;
|
|
2421
|
+
}
|
|
2422
|
+
function handleInternal(exchange, config, preFlightRequest) {
|
|
2423
|
+
const { request, response } = exchange;
|
|
2424
|
+
const responseHeaders = response.headers;
|
|
2425
|
+
const requestOrigin = request.headers.one("origin");
|
|
2426
|
+
const allowOrigin = checkOrigin(config, requestOrigin);
|
|
2427
|
+
if (allowOrigin === void 0) {
|
|
2428
|
+
if (logger9.enabledFor("debug")) {
|
|
2429
|
+
logger9.debug(`reject: '${requestOrigin}' origin is not allowed`);
|
|
2430
|
+
}
|
|
2431
|
+
rejectRequest(response);
|
|
2432
|
+
return false;
|
|
2433
|
+
}
|
|
2434
|
+
const requestMethod = getMethodToUse(request, preFlightRequest);
|
|
2435
|
+
const allowMethods = checkMethods(config, requestMethod);
|
|
2436
|
+
if (allowMethods === void 0) {
|
|
2437
|
+
if (logger9.enabledFor("debug")) {
|
|
2438
|
+
logger9.debug(`reject: HTTP '${requestMethod}' is not allowed`);
|
|
2439
|
+
}
|
|
2440
|
+
rejectRequest(response);
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
const requestHeaders = getHeadersToUse(request, preFlightRequest);
|
|
2444
|
+
const allowHeaders = checkHeaders(config, requestHeaders);
|
|
2445
|
+
if (preFlightRequest && allowHeaders === void 0) {
|
|
2446
|
+
if (logger9.enabledFor("debug")) {
|
|
2447
|
+
logger9.debug(`reject: headers '${requestHeaders}' are not allowed`);
|
|
2448
|
+
}
|
|
2449
|
+
rejectRequest(response);
|
|
2450
|
+
return false;
|
|
2451
|
+
}
|
|
2452
|
+
responseHeaders.set("Access-Control-Allow-Origin", allowOrigin);
|
|
2453
|
+
if (preFlightRequest) {
|
|
2454
|
+
responseHeaders.set("Access-Control-Allow-Methods", allowMethods.join(","));
|
|
2455
|
+
}
|
|
2456
|
+
if (preFlightRequest && allowHeaders !== void 0 && allowHeaders.length > 0) {
|
|
2457
|
+
responseHeaders.set("Access-Control-Allow-Headers", allowHeaders.join(", "));
|
|
2458
|
+
}
|
|
2459
|
+
const exposeHeaders = config.exposeHeaders;
|
|
2460
|
+
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
2461
|
+
responseHeaders.set("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
2462
|
+
}
|
|
2463
|
+
if (config.allowCredentials) {
|
|
2464
|
+
responseHeaders.set("Access-Control-Allow-Credentials", "true");
|
|
2465
|
+
}
|
|
2466
|
+
if (config.allowPrivateNetwork && request.headers.one("access-control-request-private-network") === "true") {
|
|
2467
|
+
responseHeaders.set("Access-Control-Allow-Private-Network", "true");
|
|
2468
|
+
}
|
|
2469
|
+
if (preFlightRequest && config.maxAge !== void 0) {
|
|
2470
|
+
responseHeaders.set("Access-Control-Max-Age", config.maxAge.toString());
|
|
2471
|
+
}
|
|
2472
|
+
return true;
|
|
2473
|
+
}
|
|
2474
|
+
var ALL = "*";
|
|
2475
|
+
var DEFAULT_METHODS = ["GET", "HEAD"];
|
|
2476
|
+
function validateAllowCredentials(config) {
|
|
2477
|
+
if (config.allowCredentials === true && config.allowOrigins === ALL) {
|
|
2478
|
+
throw new Error(`when allowCredentials is true allowOrigins cannot be "*"`);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
function validateAllowPrivateNetwork(config) {
|
|
2482
|
+
if (config.allowPrivateNetwork === true && config.allowOrigins === ALL) {
|
|
2483
|
+
throw new Error(`when allowPrivateNetwork is true allowOrigins cannot be "*"`);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
function checkOrigin(config, origin) {
|
|
2487
|
+
if (origin) {
|
|
2488
|
+
const allowedOrigins = config.allowOrigins;
|
|
2489
|
+
if (allowedOrigins) {
|
|
2490
|
+
if (allowedOrigins === ALL) {
|
|
2491
|
+
validateAllowCredentials(config);
|
|
2492
|
+
validateAllowPrivateNetwork(config);
|
|
2493
|
+
return ALL;
|
|
2494
|
+
}
|
|
2495
|
+
const originToCheck = trimTrailingSlash(origin.toLowerCase());
|
|
2496
|
+
for (const allowedOrigin of allowedOrigins) {
|
|
2497
|
+
if (allowedOrigin === ALL || import_gateway6.IOGateway.Filtering.valueMatches(allowedOrigin, originToCheck)) {
|
|
2498
|
+
return origin;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
function checkMethods(config, requestMethod) {
|
|
2505
|
+
if (requestMethod) {
|
|
2506
|
+
const allowedMethods = config.allowMethods ?? DEFAULT_METHODS;
|
|
2507
|
+
if (allowedMethods === ALL) {
|
|
2508
|
+
return [requestMethod];
|
|
2509
|
+
}
|
|
2510
|
+
if (import_gateway6.IOGateway.Filtering.valuesMatch(allowedMethods, requestMethod)) {
|
|
2511
|
+
return allowedMethods;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
function checkHeaders(config, requestHeaders) {
|
|
2516
|
+
if (requestHeaders === void 0) {
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
if (requestHeaders.length == 0) {
|
|
2520
|
+
return [];
|
|
2521
|
+
}
|
|
2522
|
+
const allowedHeaders = config.allowHeaders;
|
|
2523
|
+
if (allowedHeaders === void 0) {
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
const allowAnyHeader = allowedHeaders === ALL || allowedHeaders.includes(ALL);
|
|
2527
|
+
const result = [];
|
|
2528
|
+
for (const requestHeader of requestHeaders) {
|
|
2529
|
+
const value = requestHeader?.trim();
|
|
2530
|
+
if (value) {
|
|
2531
|
+
if (allowAnyHeader) {
|
|
2532
|
+
result.push(value);
|
|
2533
|
+
} else {
|
|
2534
|
+
for (const allowedHeader of allowedHeaders) {
|
|
2535
|
+
if (value.toLowerCase() === allowedHeader) {
|
|
2536
|
+
result.push(value);
|
|
2537
|
+
break;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
if (result.length > 0) {
|
|
2544
|
+
return result;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
function trimTrailingSlash(origin) {
|
|
2548
|
+
return origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
2549
|
+
}
|
|
2550
|
+
function getMethodToUse(request, isPreFlight) {
|
|
2551
|
+
return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
|
|
2552
|
+
}
|
|
2553
|
+
function getHeadersToUse(request, isPreFlight) {
|
|
2554
|
+
const headers2 = request.headers;
|
|
2555
|
+
return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
|
|
2556
|
+
}
|
|
2557
|
+
var matchingCorsConfigSource = (opts) => {
|
|
2558
|
+
return async (exchange) => {
|
|
2559
|
+
for (const [matcher, config] of opts.mappings) {
|
|
2560
|
+
if ((await matcher(exchange)).match) {
|
|
2561
|
+
logger9.debug(`resolved cors config on '${exchange.path}' using ${matcher}: ${JSON.stringify(config)}`);
|
|
2562
|
+
return config;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
};
|
|
2567
|
+
|
|
2358
2568
|
// src/server/security/config.ts
|
|
2359
2569
|
var filterOrder = {
|
|
2360
2570
|
first: Number.MAX_SAFE_INTEGER,
|
|
@@ -2368,7 +2578,7 @@ var filterOrder = {
|
|
|
2368
2578
|
last: Number.MAX_SAFE_INTEGER
|
|
2369
2579
|
};
|
|
2370
2580
|
var filterOrderSymbol = Symbol.for("filterOrder");
|
|
2371
|
-
var config_default = (config,
|
|
2581
|
+
var config_default = (config, context) => {
|
|
2372
2582
|
const middleware = [];
|
|
2373
2583
|
class ServerHttpSecurity {
|
|
2374
2584
|
#authenticationEntryPoint;
|
|
@@ -2392,6 +2602,11 @@ var config_default = (config, storage) => {
|
|
|
2392
2602
|
writer[filterOrderSymbol] = filterOrder.http_headers;
|
|
2393
2603
|
middleware.push(writer);
|
|
2394
2604
|
}
|
|
2605
|
+
if (config.cors?.disabled !== true && context.corsConfigSource !== void 0) {
|
|
2606
|
+
const filter = cors_default({ corsConfigSource: context.corsConfigSource });
|
|
2607
|
+
filter[filterOrderSymbol] = filterOrder.cors;
|
|
2608
|
+
middleware.push(filter);
|
|
2609
|
+
}
|
|
2395
2610
|
if (config.basic !== void 0 && config.basic?.disabled !== true) {
|
|
2396
2611
|
const username = config.basic.user?.name.toLowerCase();
|
|
2397
2612
|
const password = config.basic.user?.password ?? "";
|
|
@@ -2410,7 +2625,7 @@ var config_default = (config, storage) => {
|
|
|
2410
2625
|
}
|
|
2411
2626
|
];
|
|
2412
2627
|
const filter = httpBasic({
|
|
2413
|
-
storage,
|
|
2628
|
+
storage: context.storage,
|
|
2414
2629
|
manager,
|
|
2415
2630
|
defaultEntryPoints: this.#defaultEntryPoints,
|
|
2416
2631
|
defaultSuccessHandlers
|
|
@@ -2420,20 +2635,43 @@ var config_default = (config, storage) => {
|
|
|
2420
2635
|
}
|
|
2421
2636
|
if (config.jwt !== void 0 && config.jwt.disabled !== true) {
|
|
2422
2637
|
const verifier = (0, import_jwt.jwtVerifier)({
|
|
2423
|
-
issuerBaseUri: config.jwt.
|
|
2638
|
+
issuerBaseUri: config.jwt.issuerUri,
|
|
2424
2639
|
issuer: config.jwt.issuer,
|
|
2425
2640
|
audience: config.jwt.audience
|
|
2426
2641
|
});
|
|
2427
2642
|
const decoder = async (token) => {
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2643
|
+
try {
|
|
2644
|
+
const { payload } = await verifier(token);
|
|
2645
|
+
return {
|
|
2646
|
+
subject: payload.sub,
|
|
2647
|
+
getClaimAsString(claim) {
|
|
2648
|
+
return payload[claim];
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
} catch (e) {
|
|
2652
|
+
if (e instanceof import_jwt.JwtVerifyError) {
|
|
2653
|
+
throw new BadJwtError(e.message, { cause: e });
|
|
2433
2654
|
}
|
|
2434
|
-
|
|
2655
|
+
throw new JwtError("error occurred while attempting to decoding jwt", { cause: e });
|
|
2656
|
+
}
|
|
2435
2657
|
};
|
|
2436
|
-
const
|
|
2658
|
+
const authenticationConverter = token_converter_default({ uriQueryParameter: true });
|
|
2659
|
+
const authenticationConverterMatcher = async (exchange) => {
|
|
2660
|
+
try {
|
|
2661
|
+
const a = await authenticationConverter(exchange);
|
|
2662
|
+
return { match: a !== void 0 };
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
return { match: false };
|
|
2665
|
+
}
|
|
2666
|
+
};
|
|
2667
|
+
const entryPoint = token_entry_point_default({});
|
|
2668
|
+
this.#defaultEntryPoints.push([authenticationConverterMatcher, entryPoint]);
|
|
2669
|
+
const filter = resourceServer({
|
|
2670
|
+
storage: context.storage,
|
|
2671
|
+
entryPoint,
|
|
2672
|
+
converter: authenticationConverter,
|
|
2673
|
+
jwt: { decoder }
|
|
2674
|
+
});
|
|
2437
2675
|
filter[filterOrderSymbol] = filterOrder.authentication;
|
|
2438
2676
|
middleware.push(filter);
|
|
2439
2677
|
}
|
|
@@ -2455,10 +2693,12 @@ var config_default = (config, storage) => {
|
|
|
2455
2693
|
serverMatcher = matcher;
|
|
2456
2694
|
}
|
|
2457
2695
|
let manager2;
|
|
2458
|
-
if (access2.access === "
|
|
2696
|
+
if (access2.access === "permitted") {
|
|
2459
2697
|
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(true));
|
|
2460
|
-
|
|
2698
|
+
manager2.toString = () => "AuthorizationManager[permitted]";
|
|
2699
|
+
} else if (access2.access === "denied") {
|
|
2461
2700
|
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(false));
|
|
2701
|
+
manager2.toString = () => "AuthorizationManager[denied]";
|
|
2462
2702
|
} else if (access2.access === "authenticated") {
|
|
2463
2703
|
manager2 = new DefaultAuthorizationManager(async (p) => {
|
|
2464
2704
|
const authentication = await p;
|
|
@@ -2467,6 +2707,7 @@ var config_default = (config, storage) => {
|
|
|
2467
2707
|
}
|
|
2468
2708
|
return new AuthorizationDecision(false);
|
|
2469
2709
|
});
|
|
2710
|
+
manager2.toString = () => "AuthorizationManager[authenticated]";
|
|
2470
2711
|
} else {
|
|
2471
2712
|
throw new Error(`Unknown access type: ${JSON.stringify(access2)}`);
|
|
2472
2713
|
}
|
|
@@ -2475,7 +2716,7 @@ var config_default = (config, storage) => {
|
|
|
2475
2716
|
return delegatingAuthorizationManager({ mappings });
|
|
2476
2717
|
};
|
|
2477
2718
|
const manager = buildAuthorizationManager(config.authorize);
|
|
2478
|
-
const filter = authorizationFilter({ manager, storage });
|
|
2719
|
+
const filter = authorizationFilter({ manager, storage: context.storage });
|
|
2479
2720
|
filter[filterOrderSymbol] = filterOrder.authorization;
|
|
2480
2721
|
middleware.push(filter);
|
|
2481
2722
|
}
|
|
@@ -2491,8 +2732,79 @@ var config_default = (config, storage) => {
|
|
|
2491
2732
|
return middleware;
|
|
2492
2733
|
};
|
|
2493
2734
|
|
|
2735
|
+
// src/app/cors.ts
|
|
2736
|
+
function mockUpgradeExchange(path) {
|
|
2737
|
+
const request = new MockHttpRequest(path, "GET");
|
|
2738
|
+
request.headers.set("upgrade", "websocket");
|
|
2739
|
+
request["_req"] = { upgrade: true };
|
|
2740
|
+
return new DefaultWebExchange(request, new MockHttpResponse());
|
|
2741
|
+
}
|
|
2742
|
+
async function createCorsConfigSource(context) {
|
|
2743
|
+
const { sockets: routes3, cors } = context;
|
|
2744
|
+
const defaultCorsConfig = combineCorsConfig(PERMIT_DEFAULT_CONFIG, context.corsConfig);
|
|
2745
|
+
const validatedConfigs = [];
|
|
2746
|
+
for (const [path, route] of routes3) {
|
|
2747
|
+
let routeCorsConfig = defaultCorsConfig;
|
|
2748
|
+
const upgradeExchange = mockUpgradeExchange(path);
|
|
2749
|
+
for (const [matcher, config] of cors) {
|
|
2750
|
+
if ((await matcher(upgradeExchange)).match) {
|
|
2751
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, config);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, {
|
|
2755
|
+
allowOrigins: route.originFilters?.allow,
|
|
2756
|
+
allowMethods: ["GET", "CONNECT"],
|
|
2757
|
+
allowHeaders: ["upgrade", "connection", "origin", "sec-websocket-key", "sec-websocket-version", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2758
|
+
exposeHeaders: ["sec-websocket-accept", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2759
|
+
allowCredentials: route.authorize?.access !== "permitted"
|
|
2760
|
+
});
|
|
2761
|
+
validatedConfigs.push([and([upgradeMatcher, pattern(path)]), validateCorsConfig(routeCorsConfig)]);
|
|
2762
|
+
}
|
|
2763
|
+
for (const [matcher, config] of cors) {
|
|
2764
|
+
const routeCorsConfig = combineCorsConfig(defaultCorsConfig, config);
|
|
2765
|
+
validatedConfigs.push([matcher, validateCorsConfig(routeCorsConfig)]);
|
|
2766
|
+
}
|
|
2767
|
+
validatedConfigs.push([pattern(/\/api\/.*/), validateCorsConfig(defaultCorsConfig)]);
|
|
2768
|
+
return matchingCorsConfigSource({ mappings: validatedConfigs });
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
// src/app/auth.ts
|
|
2772
|
+
function createSecurityConfig(context) {
|
|
2773
|
+
const authorize = [];
|
|
2774
|
+
for (const [path, route] of context.sockets) {
|
|
2775
|
+
const rule = route.authorize ?? { access: "authenticated" };
|
|
2776
|
+
let matcher = pattern(path, { method: "GET" });
|
|
2777
|
+
matcher = and([upgradeMatcher, matcher]);
|
|
2778
|
+
authorize.push([matcher, rule]);
|
|
2779
|
+
}
|
|
2780
|
+
authorize.push([pattern("/", { method: "GET" }), { access: "permitted" }]);
|
|
2781
|
+
authorize.push([pattern("/favicon.ico", { method: "GET" }), { access: "permitted" }]);
|
|
2782
|
+
authorize.push([pattern("/health", { method: "GET" }), { access: "permitted" }]);
|
|
2783
|
+
if (context.authorize.length > 0) {
|
|
2784
|
+
authorize.push(...context.authorize);
|
|
2785
|
+
}
|
|
2786
|
+
authorize.push(["any-exchange", { access: "authenticated" }]);
|
|
2787
|
+
return {
|
|
2788
|
+
authorize,
|
|
2789
|
+
basic: {
|
|
2790
|
+
disabled: context.authConfig?.type !== "basic",
|
|
2791
|
+
...context.authConfig?.basic
|
|
2792
|
+
},
|
|
2793
|
+
jwt: {
|
|
2794
|
+
disabled: context.authConfig?.type !== "oauth2",
|
|
2795
|
+
...context.authConfig?.oauth2?.jwt
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
async function httpSecurity(context) {
|
|
2800
|
+
const corsConfigSource = await createCorsConfigSource(context);
|
|
2801
|
+
const config = createSecurityConfig(context);
|
|
2802
|
+
const { storage } = context;
|
|
2803
|
+
return config_default(config, { storage, corsConfigSource });
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2494
2806
|
// src/server.ts
|
|
2495
|
-
var
|
|
2807
|
+
var logger10 = getLogger("app");
|
|
2496
2808
|
function secureContextOptions(ssl) {
|
|
2497
2809
|
const options = {};
|
|
2498
2810
|
if (ssl.key) options.key = (0, import_node_fs.readFileSync)(ssl.key);
|
|
@@ -2500,60 +2812,38 @@ function secureContextOptions(ssl) {
|
|
|
2500
2812
|
if (ssl.ca) options.ca = (0, import_node_fs.readFileSync)(ssl.ca);
|
|
2501
2813
|
return options;
|
|
2502
2814
|
}
|
|
2503
|
-
function createListener(
|
|
2815
|
+
async function createListener(middleware, context, onSocketError) {
|
|
2816
|
+
const storage = context.storage;
|
|
2817
|
+
const security = await httpSecurity(context);
|
|
2504
2818
|
const listener = compose(
|
|
2505
|
-
server_header_default(),
|
|
2506
|
-
...
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
basic: {
|
|
2515
|
-
disabled: true,
|
|
2516
|
-
realm: "Gateway Server"
|
|
2517
|
-
},
|
|
2518
|
-
jwt: {
|
|
2519
|
-
disabled: true
|
|
2520
|
-
}
|
|
2521
|
-
}, storage),
|
|
2522
|
-
...cors_default({
|
|
2523
|
-
origins: { allow: [/http:\/\/localhost(:\d+)?/, /file:\//] },
|
|
2524
|
-
methods: { allow: ["GET", "HEAD", "POST", "DELETE"] },
|
|
2525
|
-
headers: { allow: "*" },
|
|
2526
|
-
credentials: { allow: true }
|
|
2527
|
-
}),
|
|
2528
|
-
...middleware,
|
|
2529
|
-
async ({ request, response }, next) => {
|
|
2530
|
-
const path = request.path ?? "/";
|
|
2531
|
-
const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
|
|
2532
|
-
if (path === "/" && route2.default === true) {
|
|
2533
|
-
return true;
|
|
2534
|
-
}
|
|
2535
|
-
});
|
|
2536
|
-
if (route) {
|
|
2537
|
-
if (request.method === "GET" && request._req["upgrade"] && request.headers.one("upgrade")?.toLowerCase() === "websocket") {
|
|
2819
|
+
server_header_default(context.serverHeader),
|
|
2820
|
+
...security,
|
|
2821
|
+
// websocket upgrade handler
|
|
2822
|
+
async (exchange, next) => {
|
|
2823
|
+
const [route, path] = findSocketRoute(exchange, context);
|
|
2824
|
+
if (route !== void 0) {
|
|
2825
|
+
const { request, response } = exchange;
|
|
2826
|
+
const upgradeMatchResult = await upgradeMatcher(exchange);
|
|
2827
|
+
if ((request.method === "GET" || request.method === "CONNECT") && upgradeMatchResult.match) {
|
|
2538
2828
|
const socket = request.socket;
|
|
2539
2829
|
const host = request.host;
|
|
2540
2830
|
const info = socketKey(request._req.socket);
|
|
2541
|
-
if (route
|
|
2831
|
+
if (route.wss) {
|
|
2542
2832
|
socket.removeListener("error", onSocketError);
|
|
2543
2833
|
const wss = route.wss;
|
|
2544
2834
|
if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
|
|
2545
|
-
|
|
2835
|
+
logger10.warn(`${info} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
|
|
2546
2836
|
socket.destroy();
|
|
2547
2837
|
return;
|
|
2548
2838
|
}
|
|
2549
|
-
const origin = request.headers
|
|
2839
|
+
const origin = request.headers.one("origin");
|
|
2550
2840
|
if (!acceptsOrigin(origin, route.originFilters)) {
|
|
2551
|
-
|
|
2841
|
+
logger10.info(`${info} dropping ws connection request from ${host} on ${path}. origin ${origin ?? "<missing>"}`);
|
|
2552
2842
|
socket.destroy();
|
|
2553
2843
|
return;
|
|
2554
2844
|
}
|
|
2555
|
-
if (
|
|
2556
|
-
|
|
2845
|
+
if (logger10.enabledFor("debug")) {
|
|
2846
|
+
logger10.debug(`${info} accepted new ws connection request from ${host} on ${path}`);
|
|
2557
2847
|
}
|
|
2558
2848
|
wss.handleUpgrade(request._req, socket, request._req["_upgradeHead"], (ws) => {
|
|
2559
2849
|
response._res["_header"] = true;
|
|
@@ -2563,29 +2853,36 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2563
2853
|
wss.emit("connection", ws, request._req);
|
|
2564
2854
|
});
|
|
2565
2855
|
} else {
|
|
2566
|
-
|
|
2856
|
+
logger10.warn(`${info} rejected upgrade request from ${host} on ${path}`);
|
|
2567
2857
|
socket.destroy();
|
|
2568
2858
|
}
|
|
2569
2859
|
} else {
|
|
2570
|
-
if (
|
|
2571
|
-
|
|
2860
|
+
if (route.default) {
|
|
2861
|
+
await next();
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
if (logger10.enabledFor("debug")) {
|
|
2865
|
+
logger10.debug(`rejecting request for ${path} with method ${request.method} from ${request.socket.remoteAddress}:${request.socket.remotePort} with headers: ${JSON.stringify(request._req.rawHeaders)}`);
|
|
2572
2866
|
}
|
|
2573
2867
|
response.statusCode = 426;
|
|
2574
|
-
response.
|
|
2868
|
+
response.headers.set("Upgrade", "websocket").set("Connection", "Upgrade").set("Content-Type", "text/plain");
|
|
2575
2869
|
await response.end(`This service [${request.path}] requires use of the websocket protocol.`);
|
|
2576
2870
|
}
|
|
2577
2871
|
} else {
|
|
2578
2872
|
await next();
|
|
2579
2873
|
}
|
|
2580
2874
|
},
|
|
2875
|
+
...middleware,
|
|
2876
|
+
// helth check
|
|
2581
2877
|
async ({ request, response }, next) => {
|
|
2582
2878
|
if (request.method === "GET" && request.path === "/health") {
|
|
2583
2879
|
response.statusCode = 200;
|
|
2584
|
-
response.
|
|
2880
|
+
await response.end(import_node_http.default.STATUS_CODES[200]);
|
|
2585
2881
|
} else {
|
|
2586
2882
|
await next();
|
|
2587
2883
|
}
|
|
2588
2884
|
},
|
|
2885
|
+
// home page
|
|
2589
2886
|
async ({ request, response }, next) => {
|
|
2590
2887
|
if (request.method === "GET" && request.path === "/") {
|
|
2591
2888
|
await response.end(`io.Gateway Server`);
|
|
@@ -2593,7 +2890,8 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2593
2890
|
await next();
|
|
2594
2891
|
}
|
|
2595
2892
|
},
|
|
2596
|
-
|
|
2893
|
+
// not found
|
|
2894
|
+
async ({ response }, _next) => {
|
|
2597
2895
|
response.statusCode = 404;
|
|
2598
2896
|
await response.end(import_node_http.default.STATUS_CODES[404]);
|
|
2599
2897
|
}
|
|
@@ -2602,17 +2900,17 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2602
2900
|
request.socket.addListener("error", onSocketError);
|
|
2603
2901
|
const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
|
|
2604
2902
|
return storage.run({ exchange }, async () => {
|
|
2605
|
-
if (
|
|
2903
|
+
if (logger10.enabledFor("debug")) {
|
|
2606
2904
|
const socket = exchange.request._req.socket;
|
|
2607
|
-
if (
|
|
2608
|
-
|
|
2905
|
+
if (logger10.enabledFor("debug")) {
|
|
2906
|
+
logger10.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
|
|
2609
2907
|
}
|
|
2610
2908
|
}
|
|
2611
2909
|
try {
|
|
2612
2910
|
return await listener(exchange);
|
|
2613
2911
|
} catch (e) {
|
|
2614
|
-
if (
|
|
2615
|
-
|
|
2912
|
+
if (logger10.enabledFor("warn")) {
|
|
2913
|
+
logger10.warn(`error processing request for ${exchange.path}`, e);
|
|
2616
2914
|
}
|
|
2617
2915
|
} finally {
|
|
2618
2916
|
await exchange.response.end();
|
|
@@ -2647,65 +2945,75 @@ function regexAwareReplacer(_key, value) {
|
|
|
2647
2945
|
}
|
|
2648
2946
|
var Factory = async (options) => {
|
|
2649
2947
|
const ssl = options.ssl;
|
|
2650
|
-
const createServer = ssl ? (options2,
|
|
2948
|
+
const createServer = ssl ? (options2, handler) => import_node_https.default.createServer({ ...options2, ...secureContextOptions(ssl) }, handler) : (options2, handler) => import_node_http.default.createServer(options2, handler);
|
|
2651
2949
|
const monitor = memoryMonitor(options.memory);
|
|
2652
2950
|
const middleware = [];
|
|
2653
|
-
const
|
|
2951
|
+
const context = {
|
|
2952
|
+
corsConfig: options.cors,
|
|
2953
|
+
cors: [],
|
|
2954
|
+
authConfig: options.auth,
|
|
2955
|
+
authorize: [],
|
|
2956
|
+
storage: new import_node_async_hooks2.AsyncLocalStorage(),
|
|
2957
|
+
sockets: /* @__PURE__ */ new Map()
|
|
2958
|
+
};
|
|
2654
2959
|
const gw = import_gateway7.IOGateway.Factory({ ...options.gateway });
|
|
2655
2960
|
if (options.gateway) {
|
|
2656
2961
|
const config = options.gateway;
|
|
2657
|
-
|
|
2962
|
+
const route = config.route ?? "/";
|
|
2963
|
+
context.sockets.set(route, {
|
|
2658
2964
|
default: config.route === void 0,
|
|
2659
2965
|
ping: options.gateway.ping,
|
|
2660
2966
|
factory: core_default.bind(gw),
|
|
2661
2967
|
maxConnections: config.limits?.max_connections,
|
|
2968
|
+
authorize: config.authorize,
|
|
2662
2969
|
originFilters: regexifyOriginFilters(config.origins)
|
|
2663
2970
|
});
|
|
2664
2971
|
}
|
|
2665
2972
|
if (options.mesh) {
|
|
2666
2973
|
const connections = new InMemoryNodeConnections(options.mesh.timeout ?? 6e4);
|
|
2667
|
-
|
|
2974
|
+
const authorize = options.mesh.authorize;
|
|
2975
|
+
middleware.push(...routes_default(connections, context, authorize));
|
|
2668
2976
|
const ping = options.mesh.ping ?? 3e4;
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2977
|
+
const originFilters = regexifyOriginFilters(options.mesh.origins);
|
|
2978
|
+
context.sockets.set("/broker", { factory: core_default2, ping, originFilters, authorize });
|
|
2979
|
+
context.sockets.set("/cluster", { factory: core_default4, ping, originFilters, authorize });
|
|
2980
|
+
context.sockets.set("/relays", { factory: core_default3, ping, originFilters, authorize });
|
|
2672
2981
|
}
|
|
2673
2982
|
if (options.metrics) {
|
|
2674
|
-
middleware.push(...await routes_default2(options.metrics));
|
|
2983
|
+
middleware.push(...await routes_default2(options.metrics, context));
|
|
2675
2984
|
}
|
|
2676
2985
|
const ports = portRange(options.port ?? 0);
|
|
2677
2986
|
const host = options.host;
|
|
2678
|
-
const
|
|
2987
|
+
const onSocketError = (err) => logger10.error(`socket error: ${err}`, err);
|
|
2988
|
+
const listener = await createListener(middleware, context, onSocketError);
|
|
2679
2989
|
const serverP = new Promise((resolve, reject) => {
|
|
2680
|
-
const onSocketError = (err) => logger7.error(`socket error: ${err}`, err);
|
|
2681
|
-
const listener = createListener(storage, middleware, routes3, onSocketError);
|
|
2682
2990
|
const server2 = createServer({}, listener);
|
|
2683
2991
|
server2.on("error", (e) => {
|
|
2684
2992
|
if (e["code"] === "EADDRINUSE") {
|
|
2685
|
-
|
|
2993
|
+
logger10.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
|
|
2686
2994
|
const { value: port } = ports.next();
|
|
2687
2995
|
if (port) {
|
|
2688
|
-
|
|
2996
|
+
logger10.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
|
|
2689
2997
|
server2.close();
|
|
2690
2998
|
server2.listen(port, host);
|
|
2691
2999
|
} else {
|
|
2692
|
-
|
|
3000
|
+
logger10.warn(`all configured port(s) ${options.port} are in use. closing...`);
|
|
2693
3001
|
server2.close();
|
|
2694
3002
|
reject(e);
|
|
2695
3003
|
}
|
|
2696
3004
|
} else {
|
|
2697
|
-
|
|
3005
|
+
logger10.error(`server error: ${e.message}`, e);
|
|
2698
3006
|
reject(e);
|
|
2699
3007
|
}
|
|
2700
3008
|
});
|
|
2701
3009
|
server2.on("listening", async () => {
|
|
2702
3010
|
const info = server2.address();
|
|
2703
|
-
for (const [path, route] of
|
|
3011
|
+
for (const [path, route] of context.sockets) {
|
|
2704
3012
|
try {
|
|
2705
|
-
|
|
3013
|
+
logger10.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
|
|
2706
3014
|
const wss = new import_ws.WebSocketServer({ noServer: true });
|
|
2707
3015
|
const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info.port}${path}`;
|
|
2708
|
-
const
|
|
3016
|
+
const handler = await route.factory({ endpoint, wss, storage: context.storage });
|
|
2709
3017
|
const pingInterval = route.ping;
|
|
2710
3018
|
if (pingInterval) {
|
|
2711
3019
|
const pingIntervalId = setInterval(() => {
|
|
@@ -2722,12 +3030,12 @@ var Factory = async (options) => {
|
|
|
2722
3030
|
});
|
|
2723
3031
|
}
|
|
2724
3032
|
route.wss = wss;
|
|
2725
|
-
route.close =
|
|
3033
|
+
route.close = handler.close?.bind(handler);
|
|
2726
3034
|
} catch (e) {
|
|
2727
|
-
|
|
3035
|
+
logger10.warn(`failed to init route ${path}`, e);
|
|
2728
3036
|
}
|
|
2729
3037
|
}
|
|
2730
|
-
|
|
3038
|
+
logger10.info(`http server listening on ${info.address}:${info.port}`);
|
|
2731
3039
|
resolve(server2);
|
|
2732
3040
|
});
|
|
2733
3041
|
server2.on("upgrade", (req, socket, head) => {
|
|
@@ -2738,16 +3046,16 @@ var Factory = async (options) => {
|
|
|
2738
3046
|
res.assignSocket(socket);
|
|
2739
3047
|
listener(req, res);
|
|
2740
3048
|
} catch (err) {
|
|
2741
|
-
|
|
3049
|
+
logger10.error(`upgrade error: ${err}`, err);
|
|
2742
3050
|
}
|
|
2743
3051
|
}).on("close", async () => {
|
|
2744
|
-
|
|
3052
|
+
logger10.info(`http server closed.`);
|
|
2745
3053
|
});
|
|
2746
3054
|
try {
|
|
2747
3055
|
const { value: port } = ports.next();
|
|
2748
3056
|
server2.listen(port, host);
|
|
2749
3057
|
} catch (e) {
|
|
2750
|
-
|
|
3058
|
+
logger10.error(`error starting web socket server`, e);
|
|
2751
3059
|
reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
|
|
2752
3060
|
}
|
|
2753
3061
|
});
|
|
@@ -2755,18 +3063,18 @@ var Factory = async (options) => {
|
|
|
2755
3063
|
return new class {
|
|
2756
3064
|
gateway = gw;
|
|
2757
3065
|
async close() {
|
|
2758
|
-
for (const [path, route] of
|
|
3066
|
+
for (const [path, route] of context.sockets) {
|
|
2759
3067
|
try {
|
|
2760
3068
|
if (route.close) {
|
|
2761
3069
|
await route.close();
|
|
2762
3070
|
}
|
|
2763
|
-
|
|
3071
|
+
logger10.info(`stopping ws server for [${path}]. clients: ${route.wss?.clients?.size ?? 0}`);
|
|
2764
3072
|
route.wss?.clients?.forEach((client) => {
|
|
2765
3073
|
client.terminate();
|
|
2766
3074
|
});
|
|
2767
3075
|
route.wss?.close();
|
|
2768
3076
|
} catch (e) {
|
|
2769
|
-
|
|
3077
|
+
logger10.warn(`error closing route ${path}`, e);
|
|
2770
3078
|
}
|
|
2771
3079
|
}
|
|
2772
3080
|
await promisify((cb) => {
|