@interopio/gateway-server 0.5.1-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 +15 -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.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();
|
|
@@ -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,56 @@ function regexifyOriginFilters(originFilters) {
|
|
|
1308
1478
|
}
|
|
1309
1479
|
}
|
|
1310
1480
|
|
|
1311
|
-
// src/server/
|
|
1312
|
-
|
|
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
|
+
var serverHeader = (server) => {
|
|
1483
|
+
const enabled = typeof server === "string";
|
|
1484
|
+
return async ({ response }, next) => {
|
|
1485
|
+
if (server != false && !response.headers.has("server")) {
|
|
1486
|
+
response.headers.set("Server", server);
|
|
1344
1487
|
}
|
|
1345
|
-
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1488
|
+
await next();
|
|
1489
|
+
};
|
|
1490
|
+
};
|
|
1491
|
+
var server_header_default = (server = "gateway-server") => serverHeader(server);
|
|
1492
|
+
|
|
1493
|
+
// src/app/route.ts
|
|
1494
|
+
function findSocketRoute({ request }, { sockets: routes3 }) {
|
|
1495
|
+
const path = request.path ?? "/";
|
|
1496
|
+
const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
|
|
1497
|
+
if (path === "/" && route2.default === true) {
|
|
1349
1498
|
return true;
|
|
1350
1499
|
}
|
|
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;
|
|
1500
|
+
});
|
|
1501
|
+
return [route, path];
|
|
1371
1502
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
return origin.toLowerCase();
|
|
1383
|
-
}
|
|
1384
|
-
return origin;
|
|
1385
|
-
});
|
|
1503
|
+
|
|
1504
|
+
// src/server/security/http-headers.ts
|
|
1505
|
+
var staticServerHttpHeadersWriter = (headers2) => {
|
|
1506
|
+
return async (exchange) => {
|
|
1507
|
+
let containsNoHeaders = true;
|
|
1508
|
+
const { response } = exchange;
|
|
1509
|
+
for (const name of headers2.keys()) {
|
|
1510
|
+
if (response.headers.has(name)) {
|
|
1511
|
+
containsNoHeaders = false;
|
|
1512
|
+
}
|
|
1386
1513
|
}
|
|
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();
|
|
1514
|
+
if (containsNoHeaders) {
|
|
1515
|
+
for (const [name, value] of headers2) {
|
|
1516
|
+
response.headers.set(name, value);
|
|
1517
|
+
}
|
|
1398
1518
|
}
|
|
1399
1519
|
};
|
|
1400
1520
|
};
|
|
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";
|
|
1521
|
+
var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1522
|
+
new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
|
|
1523
|
+
);
|
|
1524
|
+
var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
|
|
1525
|
+
new MapHttpHeaders().add("x-content-type-options", "nosniff")
|
|
1526
|
+
);
|
|
1527
|
+
var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
|
|
1528
|
+
let headerValue = `max-age=${maxAgeInSeconds}`;
|
|
1529
|
+
if (includeSubDomains) {
|
|
1530
|
+
headerValue += " ; includeSubDomains";
|
|
1577
1531
|
}
|
|
1578
1532
|
if (preload) {
|
|
1579
1533
|
headerValue += " ; preload";
|
|
@@ -1733,18 +1687,18 @@ var AuthorizationDecision = class {
|
|
|
1733
1687
|
granted;
|
|
1734
1688
|
};
|
|
1735
1689
|
var DefaultAuthorizationManager = class {
|
|
1736
|
-
check;
|
|
1690
|
+
#check;
|
|
1737
1691
|
constructor(check) {
|
|
1738
|
-
this
|
|
1692
|
+
this.#check = check;
|
|
1739
1693
|
}
|
|
1740
1694
|
async verify(authentication, object) {
|
|
1741
|
-
const decision = await this
|
|
1695
|
+
const decision = await this.#check(authentication, object);
|
|
1742
1696
|
if (!decision?.granted) {
|
|
1743
1697
|
throw new AccessDeniedError("Access denied");
|
|
1744
1698
|
}
|
|
1745
1699
|
}
|
|
1746
1700
|
async authorize(authentication, object) {
|
|
1747
|
-
return await this
|
|
1701
|
+
return await this.#check(authentication, object);
|
|
1748
1702
|
}
|
|
1749
1703
|
};
|
|
1750
1704
|
var AuthenticationServiceError = class extends AuthenticationError {
|
|
@@ -1798,71 +1752,6 @@ var httpBasicAuthenticationConverter = (opts) => {
|
|
|
1798
1752
|
};
|
|
1799
1753
|
};
|
|
1800
1754
|
|
|
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
1755
|
// src/server/security/security-context.ts
|
|
1867
1756
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1868
1757
|
var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
|
|
@@ -2176,6 +2065,7 @@ var httpStatusEntryPoint = (opts) => {
|
|
|
2176
2065
|
};
|
|
2177
2066
|
|
|
2178
2067
|
// src/server/security/delegating-entry-point.ts
|
|
2068
|
+
var logger6 = getLogger("auth.entry-point");
|
|
2179
2069
|
var delegatingEntryPoint = (opts) => {
|
|
2180
2070
|
const defaultEntryPoint = opts.defaultEntryPoint ?? (async ({ response }, _error) => {
|
|
2181
2071
|
response.statusCode = 401;
|
|
@@ -2183,11 +2073,20 @@ var delegatingEntryPoint = (opts) => {
|
|
|
2183
2073
|
});
|
|
2184
2074
|
return async (exchange, error) => {
|
|
2185
2075
|
for (const [matcher, entryPoint] of opts.entryPoints) {
|
|
2076
|
+
if (logger6.enabledFor("debug")) {
|
|
2077
|
+
logger6.debug(`trying to match using: ${matcher}`);
|
|
2078
|
+
}
|
|
2186
2079
|
const match = await matcher(exchange);
|
|
2187
2080
|
if (match.match) {
|
|
2081
|
+
if (logger6.enabledFor("debug")) {
|
|
2082
|
+
logger6.debug(`match found. using default entry point ${entryPoint}`);
|
|
2083
|
+
}
|
|
2188
2084
|
return entryPoint(exchange, error);
|
|
2189
2085
|
}
|
|
2190
2086
|
}
|
|
2087
|
+
if (logger6.enabledFor("debug")) {
|
|
2088
|
+
logger6.debug(`no match found. using default entry point ${defaultEntryPoint}`);
|
|
2089
|
+
}
|
|
2191
2090
|
return defaultEntryPoint(exchange, error);
|
|
2192
2091
|
};
|
|
2193
2092
|
};
|
|
@@ -2195,8 +2094,8 @@ var delegatingEntryPoint = (opts) => {
|
|
|
2195
2094
|
// src/server/security/delegating-success-handler.ts
|
|
2196
2095
|
var delegatingSuccessHandler = (handlers) => {
|
|
2197
2096
|
return async ({ exchange, next }, authentication) => {
|
|
2198
|
-
for (const
|
|
2199
|
-
await
|
|
2097
|
+
for (const handler of handlers) {
|
|
2098
|
+
await handler({ exchange, next }, authentication);
|
|
2200
2099
|
}
|
|
2201
2100
|
};
|
|
2202
2101
|
};
|
|
@@ -2246,7 +2145,7 @@ function httpBasic(opts) {
|
|
|
2246
2145
|
}
|
|
2247
2146
|
|
|
2248
2147
|
// src/server/security/config.ts
|
|
2249
|
-
import { jwtVerifier } from "@interopio/gateway/jose/jwt";
|
|
2148
|
+
import { jwtVerifier, JwtVerifyError } from "@interopio/gateway/jose/jwt";
|
|
2250
2149
|
|
|
2251
2150
|
// src/server/security/error-filter.ts
|
|
2252
2151
|
async function commenceAuthentication(exchange, authentication, entryPoint) {
|
|
@@ -2290,14 +2189,21 @@ var errorFilter = (opts) => {
|
|
|
2290
2189
|
};
|
|
2291
2190
|
|
|
2292
2191
|
// src/server/security/authorization-filter.ts
|
|
2192
|
+
var logger7 = getLogger("security.auth");
|
|
2293
2193
|
function authorizationFilter(opts) {
|
|
2294
2194
|
const { manager, storage } = opts;
|
|
2295
2195
|
return async (exchange, next) => {
|
|
2296
2196
|
const promise = AsyncStorageSecurityContextHolder.getContext(storage).then((c) => c?.authentication);
|
|
2297
2197
|
try {
|
|
2298
2198
|
await manager.verify(promise, exchange);
|
|
2199
|
+
if (logger7.enabledFor("debug")) {
|
|
2200
|
+
logger7.debug("authorization successful");
|
|
2201
|
+
}
|
|
2299
2202
|
} catch (error) {
|
|
2300
2203
|
if (error instanceof AccessDeniedError) {
|
|
2204
|
+
if (logger7.enabledFor("debug")) {
|
|
2205
|
+
logger7.debug(`authorization failed: ${error.message}`);
|
|
2206
|
+
}
|
|
2301
2207
|
}
|
|
2302
2208
|
throw error;
|
|
2303
2209
|
}
|
|
@@ -2306,12 +2212,14 @@ function authorizationFilter(opts) {
|
|
|
2306
2212
|
}
|
|
2307
2213
|
|
|
2308
2214
|
// src/server/security/delegating-authorization-manager.ts
|
|
2215
|
+
var logger8 = getLogger("auth");
|
|
2309
2216
|
function delegatingAuthorizationManager(opts) {
|
|
2310
2217
|
const check = async (authentication, exchange) => {
|
|
2311
2218
|
let decision;
|
|
2312
2219
|
for (const [matcher, manager] of opts.mappings) {
|
|
2313
2220
|
if ((await matcher(exchange))?.match) {
|
|
2314
|
-
|
|
2221
|
+
logger8.debug(`checking authorization on '${exchange.path}' using [${matcher}, ${manager}]`);
|
|
2222
|
+
const checkResult = await manager.authorize(authentication, { exchange });
|
|
2315
2223
|
if (checkResult !== void 0) {
|
|
2316
2224
|
decision = checkResult;
|
|
2317
2225
|
break;
|
|
@@ -2324,6 +2232,308 @@ function delegatingAuthorizationManager(opts) {
|
|
|
2324
2232
|
return new DefaultAuthorizationManager(check);
|
|
2325
2233
|
}
|
|
2326
2234
|
|
|
2235
|
+
// src/server/cors.ts
|
|
2236
|
+
import { IOGateway as IOGateway6 } from "@interopio/gateway";
|
|
2237
|
+
function isSameOrigin(request) {
|
|
2238
|
+
const origin = request.headers.one("origin");
|
|
2239
|
+
if (origin === void 0) {
|
|
2240
|
+
return true;
|
|
2241
|
+
}
|
|
2242
|
+
const url = request.URL;
|
|
2243
|
+
const actualProtocol = url.protocol;
|
|
2244
|
+
const actualHost = url.host;
|
|
2245
|
+
const originUrl = URL.parse(origin);
|
|
2246
|
+
const originHost = originUrl?.host;
|
|
2247
|
+
const originProtocol = originUrl?.protocol;
|
|
2248
|
+
return actualProtocol === originProtocol && actualHost === originHost;
|
|
2249
|
+
}
|
|
2250
|
+
function isCorsRequest(request) {
|
|
2251
|
+
return request.headers.has("origin") && !isSameOrigin(request);
|
|
2252
|
+
}
|
|
2253
|
+
function isPreFlightRequest(request) {
|
|
2254
|
+
return request.method === "OPTIONS" && request.headers.has("origin") && request.headers.has("access-control-request-method");
|
|
2255
|
+
}
|
|
2256
|
+
var VARY_HEADERS = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"];
|
|
2257
|
+
var processRequest = (exchange, config) => {
|
|
2258
|
+
const { request, response } = exchange;
|
|
2259
|
+
const responseHeaders = response.headers;
|
|
2260
|
+
if (!responseHeaders.has("Vary")) {
|
|
2261
|
+
responseHeaders.set("Vary", VARY_HEADERS.join(", "));
|
|
2262
|
+
} else {
|
|
2263
|
+
const varyHeaders = responseHeaders.list("Vary");
|
|
2264
|
+
for (const header of VARY_HEADERS) {
|
|
2265
|
+
if (!varyHeaders.find((h) => h === header)) {
|
|
2266
|
+
varyHeaders.push(header);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
responseHeaders.set("Vary", varyHeaders.join(", "));
|
|
2270
|
+
}
|
|
2271
|
+
try {
|
|
2272
|
+
if (!isCorsRequest(request)) {
|
|
2273
|
+
return true;
|
|
2274
|
+
}
|
|
2275
|
+
} catch (e) {
|
|
2276
|
+
if (logger9.enabledFor("debug")) {
|
|
2277
|
+
logger9.debug(`reject: origin is malformed`);
|
|
2278
|
+
}
|
|
2279
|
+
rejectRequest(response);
|
|
2280
|
+
return false;
|
|
2281
|
+
}
|
|
2282
|
+
if (responseHeaders.has("access-control-allow-origin")) {
|
|
2283
|
+
logger9.trace(`skip: already contains "Access-Control-Allow-Origin"`);
|
|
2284
|
+
return true;
|
|
2285
|
+
}
|
|
2286
|
+
const preFlightRequest = isPreFlightRequest(request);
|
|
2287
|
+
if (config) {
|
|
2288
|
+
return handleInternal(exchange, config, preFlightRequest);
|
|
2289
|
+
}
|
|
2290
|
+
if (preFlightRequest) {
|
|
2291
|
+
rejectRequest(response);
|
|
2292
|
+
return false;
|
|
2293
|
+
}
|
|
2294
|
+
return true;
|
|
2295
|
+
};
|
|
2296
|
+
var DEFAULT_PERMIT_ALL = ["*"];
|
|
2297
|
+
var DEFAULT_PERMIT_METHODS = ["GET", "HEAD", "POST"];
|
|
2298
|
+
var PERMIT_DEFAULT_CONFIG = {
|
|
2299
|
+
allowOrigins: DEFAULT_PERMIT_ALL,
|
|
2300
|
+
allowMethods: DEFAULT_PERMIT_METHODS,
|
|
2301
|
+
allowHeaders: DEFAULT_PERMIT_ALL,
|
|
2302
|
+
maxAge: 1800
|
|
2303
|
+
// 30 minutes
|
|
2304
|
+
};
|
|
2305
|
+
function validateCorsConfig(config) {
|
|
2306
|
+
if (config) {
|
|
2307
|
+
const allowHeaders = config.allowHeaders;
|
|
2308
|
+
if (allowHeaders && allowHeaders !== ALL) {
|
|
2309
|
+
config = {
|
|
2310
|
+
...config,
|
|
2311
|
+
allowHeaders: allowHeaders.map((header) => header.toLowerCase())
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
const allowOrigins = config.allowOrigins;
|
|
2315
|
+
if (allowOrigins) {
|
|
2316
|
+
if (allowOrigins === "*") {
|
|
2317
|
+
validateAllowCredentials(config);
|
|
2318
|
+
validateAllowPrivateNetwork(config);
|
|
2319
|
+
} else {
|
|
2320
|
+
config = {
|
|
2321
|
+
...config,
|
|
2322
|
+
allowOrigins: allowOrigins.map((origin) => {
|
|
2323
|
+
if (typeof origin === "string" && origin !== ALL) {
|
|
2324
|
+
origin = IOGateway6.Filtering.regexify(origin);
|
|
2325
|
+
if (typeof origin === "string") {
|
|
2326
|
+
return trimTrailingSlash(origin).toLowerCase();
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
return origin;
|
|
2330
|
+
})
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return config;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
function combine(source, other) {
|
|
2338
|
+
if (other === void 0) {
|
|
2339
|
+
return source !== void 0 ? source === ALL ? [ALL] : source : [];
|
|
2340
|
+
}
|
|
2341
|
+
if (source === void 0) {
|
|
2342
|
+
return other === ALL ? [ALL] : other;
|
|
2343
|
+
}
|
|
2344
|
+
if (source == DEFAULT_PERMIT_ALL || source === DEFAULT_PERMIT_METHODS) {
|
|
2345
|
+
return other === ALL ? [ALL] : other;
|
|
2346
|
+
}
|
|
2347
|
+
if (other == DEFAULT_PERMIT_ALL || other === DEFAULT_PERMIT_METHODS) {
|
|
2348
|
+
return source === ALL ? [ALL] : source;
|
|
2349
|
+
}
|
|
2350
|
+
if (source === ALL || source.includes(ALL) || other === ALL || other.includes(ALL)) {
|
|
2351
|
+
return [ALL];
|
|
2352
|
+
}
|
|
2353
|
+
const combined = /* @__PURE__ */ new Set();
|
|
2354
|
+
source.forEach((v) => combined.add(v));
|
|
2355
|
+
other.forEach((v) => combined.add(v));
|
|
2356
|
+
return Array.from(combined);
|
|
2357
|
+
}
|
|
2358
|
+
var combineCorsConfig = (source, other) => {
|
|
2359
|
+
if (other === void 0) {
|
|
2360
|
+
return source;
|
|
2361
|
+
}
|
|
2362
|
+
const config = {
|
|
2363
|
+
allowOrigins: combine(source.allowOrigins, other?.allowOrigins),
|
|
2364
|
+
allowMethods: combine(source.allowMethods, other?.allowMethods),
|
|
2365
|
+
allowHeaders: combine(source.allowHeaders, other?.allowHeaders),
|
|
2366
|
+
exposeHeaders: combine(source.exposeHeaders, other?.exposeHeaders),
|
|
2367
|
+
allowCredentials: other?.allowCredentials ?? source.allowCredentials,
|
|
2368
|
+
allowPrivateNetwork: other?.allowPrivateNetwork ?? source.allowPrivateNetwork,
|
|
2369
|
+
maxAge: other?.maxAge ?? source.maxAge
|
|
2370
|
+
};
|
|
2371
|
+
return config;
|
|
2372
|
+
};
|
|
2373
|
+
var corsFilter = (opts) => {
|
|
2374
|
+
const source = opts.corsConfigSource;
|
|
2375
|
+
const processor = opts.corsProcessor ?? processRequest;
|
|
2376
|
+
return async (ctx, next) => {
|
|
2377
|
+
const config = await source(ctx);
|
|
2378
|
+
const isValid = processor(ctx, config);
|
|
2379
|
+
if (!isValid || isPreFlightRequest(ctx.request)) {
|
|
2380
|
+
return;
|
|
2381
|
+
} else {
|
|
2382
|
+
await next();
|
|
2383
|
+
}
|
|
2384
|
+
};
|
|
2385
|
+
};
|
|
2386
|
+
var cors_default = corsFilter;
|
|
2387
|
+
var logger9 = getLogger("cors");
|
|
2388
|
+
function rejectRequest(response) {
|
|
2389
|
+
response.statusCode = 403;
|
|
2390
|
+
}
|
|
2391
|
+
function handleInternal(exchange, config, preFlightRequest) {
|
|
2392
|
+
const { request, response } = exchange;
|
|
2393
|
+
const responseHeaders = response.headers;
|
|
2394
|
+
const requestOrigin = request.headers.one("origin");
|
|
2395
|
+
const allowOrigin = checkOrigin(config, requestOrigin);
|
|
2396
|
+
if (allowOrigin === void 0) {
|
|
2397
|
+
if (logger9.enabledFor("debug")) {
|
|
2398
|
+
logger9.debug(`reject: '${requestOrigin}' origin is not allowed`);
|
|
2399
|
+
}
|
|
2400
|
+
rejectRequest(response);
|
|
2401
|
+
return false;
|
|
2402
|
+
}
|
|
2403
|
+
const requestMethod = getMethodToUse(request, preFlightRequest);
|
|
2404
|
+
const allowMethods = checkMethods(config, requestMethod);
|
|
2405
|
+
if (allowMethods === void 0) {
|
|
2406
|
+
if (logger9.enabledFor("debug")) {
|
|
2407
|
+
logger9.debug(`reject: HTTP '${requestMethod}' is not allowed`);
|
|
2408
|
+
}
|
|
2409
|
+
rejectRequest(response);
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
const requestHeaders = getHeadersToUse(request, preFlightRequest);
|
|
2413
|
+
const allowHeaders = checkHeaders(config, requestHeaders);
|
|
2414
|
+
if (preFlightRequest && allowHeaders === void 0) {
|
|
2415
|
+
if (logger9.enabledFor("debug")) {
|
|
2416
|
+
logger9.debug(`reject: headers '${requestHeaders}' are not allowed`);
|
|
2417
|
+
}
|
|
2418
|
+
rejectRequest(response);
|
|
2419
|
+
return false;
|
|
2420
|
+
}
|
|
2421
|
+
responseHeaders.set("Access-Control-Allow-Origin", allowOrigin);
|
|
2422
|
+
if (preFlightRequest) {
|
|
2423
|
+
responseHeaders.set("Access-Control-Allow-Methods", allowMethods.join(","));
|
|
2424
|
+
}
|
|
2425
|
+
if (preFlightRequest && allowHeaders !== void 0 && allowHeaders.length > 0) {
|
|
2426
|
+
responseHeaders.set("Access-Control-Allow-Headers", allowHeaders.join(", "));
|
|
2427
|
+
}
|
|
2428
|
+
const exposeHeaders = config.exposeHeaders;
|
|
2429
|
+
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
2430
|
+
responseHeaders.set("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
2431
|
+
}
|
|
2432
|
+
if (config.allowCredentials) {
|
|
2433
|
+
responseHeaders.set("Access-Control-Allow-Credentials", "true");
|
|
2434
|
+
}
|
|
2435
|
+
if (config.allowPrivateNetwork && request.headers.one("access-control-request-private-network") === "true") {
|
|
2436
|
+
responseHeaders.set("Access-Control-Allow-Private-Network", "true");
|
|
2437
|
+
}
|
|
2438
|
+
if (preFlightRequest && config.maxAge !== void 0) {
|
|
2439
|
+
responseHeaders.set("Access-Control-Max-Age", config.maxAge.toString());
|
|
2440
|
+
}
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
var ALL = "*";
|
|
2444
|
+
var DEFAULT_METHODS = ["GET", "HEAD"];
|
|
2445
|
+
function validateAllowCredentials(config) {
|
|
2446
|
+
if (config.allowCredentials === true && config.allowOrigins === ALL) {
|
|
2447
|
+
throw new Error(`when allowCredentials is true allowOrigins cannot be "*"`);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
function validateAllowPrivateNetwork(config) {
|
|
2451
|
+
if (config.allowPrivateNetwork === true && config.allowOrigins === ALL) {
|
|
2452
|
+
throw new Error(`when allowPrivateNetwork is true allowOrigins cannot be "*"`);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
function checkOrigin(config, origin) {
|
|
2456
|
+
if (origin) {
|
|
2457
|
+
const allowedOrigins = config.allowOrigins;
|
|
2458
|
+
if (allowedOrigins) {
|
|
2459
|
+
if (allowedOrigins === ALL) {
|
|
2460
|
+
validateAllowCredentials(config);
|
|
2461
|
+
validateAllowPrivateNetwork(config);
|
|
2462
|
+
return ALL;
|
|
2463
|
+
}
|
|
2464
|
+
const originToCheck = trimTrailingSlash(origin.toLowerCase());
|
|
2465
|
+
for (const allowedOrigin of allowedOrigins) {
|
|
2466
|
+
if (allowedOrigin === ALL || IOGateway6.Filtering.valueMatches(allowedOrigin, originToCheck)) {
|
|
2467
|
+
return origin;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
function checkMethods(config, requestMethod) {
|
|
2474
|
+
if (requestMethod) {
|
|
2475
|
+
const allowedMethods = config.allowMethods ?? DEFAULT_METHODS;
|
|
2476
|
+
if (allowedMethods === ALL) {
|
|
2477
|
+
return [requestMethod];
|
|
2478
|
+
}
|
|
2479
|
+
if (IOGateway6.Filtering.valuesMatch(allowedMethods, requestMethod)) {
|
|
2480
|
+
return allowedMethods;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
function checkHeaders(config, requestHeaders) {
|
|
2485
|
+
if (requestHeaders === void 0) {
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
if (requestHeaders.length == 0) {
|
|
2489
|
+
return [];
|
|
2490
|
+
}
|
|
2491
|
+
const allowedHeaders = config.allowHeaders;
|
|
2492
|
+
if (allowedHeaders === void 0) {
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
const allowAnyHeader = allowedHeaders === ALL || allowedHeaders.includes(ALL);
|
|
2496
|
+
const result = [];
|
|
2497
|
+
for (const requestHeader of requestHeaders) {
|
|
2498
|
+
const value = requestHeader?.trim();
|
|
2499
|
+
if (value) {
|
|
2500
|
+
if (allowAnyHeader) {
|
|
2501
|
+
result.push(value);
|
|
2502
|
+
} else {
|
|
2503
|
+
for (const allowedHeader of allowedHeaders) {
|
|
2504
|
+
if (value.toLowerCase() === allowedHeader) {
|
|
2505
|
+
result.push(value);
|
|
2506
|
+
break;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
if (result.length > 0) {
|
|
2513
|
+
return result;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
function trimTrailingSlash(origin) {
|
|
2517
|
+
return origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
2518
|
+
}
|
|
2519
|
+
function getMethodToUse(request, isPreFlight) {
|
|
2520
|
+
return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
|
|
2521
|
+
}
|
|
2522
|
+
function getHeadersToUse(request, isPreFlight) {
|
|
2523
|
+
const headers2 = request.headers;
|
|
2524
|
+
return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
|
|
2525
|
+
}
|
|
2526
|
+
var matchingCorsConfigSource = (opts) => {
|
|
2527
|
+
return async (exchange) => {
|
|
2528
|
+
for (const [matcher, config] of opts.mappings) {
|
|
2529
|
+
if ((await matcher(exchange)).match) {
|
|
2530
|
+
logger9.debug(`resolved cors config on '${exchange.path}' using ${matcher}: ${JSON.stringify(config)}`);
|
|
2531
|
+
return config;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
};
|
|
2535
|
+
};
|
|
2536
|
+
|
|
2327
2537
|
// src/server/security/config.ts
|
|
2328
2538
|
var filterOrder = {
|
|
2329
2539
|
first: Number.MAX_SAFE_INTEGER,
|
|
@@ -2337,7 +2547,7 @@ var filterOrder = {
|
|
|
2337
2547
|
last: Number.MAX_SAFE_INTEGER
|
|
2338
2548
|
};
|
|
2339
2549
|
var filterOrderSymbol = Symbol.for("filterOrder");
|
|
2340
|
-
var config_default = (config,
|
|
2550
|
+
var config_default = (config, context) => {
|
|
2341
2551
|
const middleware = [];
|
|
2342
2552
|
class ServerHttpSecurity {
|
|
2343
2553
|
#authenticationEntryPoint;
|
|
@@ -2361,6 +2571,11 @@ var config_default = (config, storage) => {
|
|
|
2361
2571
|
writer[filterOrderSymbol] = filterOrder.http_headers;
|
|
2362
2572
|
middleware.push(writer);
|
|
2363
2573
|
}
|
|
2574
|
+
if (config.cors?.disabled !== true && context.corsConfigSource !== void 0) {
|
|
2575
|
+
const filter = cors_default({ corsConfigSource: context.corsConfigSource });
|
|
2576
|
+
filter[filterOrderSymbol] = filterOrder.cors;
|
|
2577
|
+
middleware.push(filter);
|
|
2578
|
+
}
|
|
2364
2579
|
if (config.basic !== void 0 && config.basic?.disabled !== true) {
|
|
2365
2580
|
const username = config.basic.user?.name.toLowerCase();
|
|
2366
2581
|
const password = config.basic.user?.password ?? "";
|
|
@@ -2379,7 +2594,7 @@ var config_default = (config, storage) => {
|
|
|
2379
2594
|
}
|
|
2380
2595
|
];
|
|
2381
2596
|
const filter = httpBasic({
|
|
2382
|
-
storage,
|
|
2597
|
+
storage: context.storage,
|
|
2383
2598
|
manager,
|
|
2384
2599
|
defaultEntryPoints: this.#defaultEntryPoints,
|
|
2385
2600
|
defaultSuccessHandlers
|
|
@@ -2389,20 +2604,43 @@ var config_default = (config, storage) => {
|
|
|
2389
2604
|
}
|
|
2390
2605
|
if (config.jwt !== void 0 && config.jwt.disabled !== true) {
|
|
2391
2606
|
const verifier = jwtVerifier({
|
|
2392
|
-
issuerBaseUri: config.jwt.
|
|
2607
|
+
issuerBaseUri: config.jwt.issuerUri,
|
|
2393
2608
|
issuer: config.jwt.issuer,
|
|
2394
2609
|
audience: config.jwt.audience
|
|
2395
2610
|
});
|
|
2396
2611
|
const decoder = async (token) => {
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2612
|
+
try {
|
|
2613
|
+
const { payload } = await verifier(token);
|
|
2614
|
+
return {
|
|
2615
|
+
subject: payload.sub,
|
|
2616
|
+
getClaimAsString(claim) {
|
|
2617
|
+
return payload[claim];
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2620
|
+
} catch (e) {
|
|
2621
|
+
if (e instanceof JwtVerifyError) {
|
|
2622
|
+
throw new BadJwtError(e.message, { cause: e });
|
|
2402
2623
|
}
|
|
2403
|
-
|
|
2624
|
+
throw new JwtError("error occurred while attempting to decoding jwt", { cause: e });
|
|
2625
|
+
}
|
|
2404
2626
|
};
|
|
2405
|
-
const
|
|
2627
|
+
const authenticationConverter = token_converter_default({ uriQueryParameter: true });
|
|
2628
|
+
const authenticationConverterMatcher = async (exchange) => {
|
|
2629
|
+
try {
|
|
2630
|
+
const a = await authenticationConverter(exchange);
|
|
2631
|
+
return { match: a !== void 0 };
|
|
2632
|
+
} catch (e) {
|
|
2633
|
+
return { match: false };
|
|
2634
|
+
}
|
|
2635
|
+
};
|
|
2636
|
+
const entryPoint = token_entry_point_default({});
|
|
2637
|
+
this.#defaultEntryPoints.push([authenticationConverterMatcher, entryPoint]);
|
|
2638
|
+
const filter = resourceServer({
|
|
2639
|
+
storage: context.storage,
|
|
2640
|
+
entryPoint,
|
|
2641
|
+
converter: authenticationConverter,
|
|
2642
|
+
jwt: { decoder }
|
|
2643
|
+
});
|
|
2406
2644
|
filter[filterOrderSymbol] = filterOrder.authentication;
|
|
2407
2645
|
middleware.push(filter);
|
|
2408
2646
|
}
|
|
@@ -2424,10 +2662,12 @@ var config_default = (config, storage) => {
|
|
|
2424
2662
|
serverMatcher = matcher;
|
|
2425
2663
|
}
|
|
2426
2664
|
let manager2;
|
|
2427
|
-
if (access2.access === "
|
|
2665
|
+
if (access2.access === "permitted") {
|
|
2428
2666
|
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(true));
|
|
2429
|
-
|
|
2667
|
+
manager2.toString = () => "AuthorizationManager[permitted]";
|
|
2668
|
+
} else if (access2.access === "denied") {
|
|
2430
2669
|
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(false));
|
|
2670
|
+
manager2.toString = () => "AuthorizationManager[denied]";
|
|
2431
2671
|
} else if (access2.access === "authenticated") {
|
|
2432
2672
|
manager2 = new DefaultAuthorizationManager(async (p) => {
|
|
2433
2673
|
const authentication = await p;
|
|
@@ -2436,6 +2676,7 @@ var config_default = (config, storage) => {
|
|
|
2436
2676
|
}
|
|
2437
2677
|
return new AuthorizationDecision(false);
|
|
2438
2678
|
});
|
|
2679
|
+
manager2.toString = () => "AuthorizationManager[authenticated]";
|
|
2439
2680
|
} else {
|
|
2440
2681
|
throw new Error(`Unknown access type: ${JSON.stringify(access2)}`);
|
|
2441
2682
|
}
|
|
@@ -2444,7 +2685,7 @@ var config_default = (config, storage) => {
|
|
|
2444
2685
|
return delegatingAuthorizationManager({ mappings });
|
|
2445
2686
|
};
|
|
2446
2687
|
const manager = buildAuthorizationManager(config.authorize);
|
|
2447
|
-
const filter = authorizationFilter({ manager, storage });
|
|
2688
|
+
const filter = authorizationFilter({ manager, storage: context.storage });
|
|
2448
2689
|
filter[filterOrderSymbol] = filterOrder.authorization;
|
|
2449
2690
|
middleware.push(filter);
|
|
2450
2691
|
}
|
|
@@ -2460,8 +2701,79 @@ var config_default = (config, storage) => {
|
|
|
2460
2701
|
return middleware;
|
|
2461
2702
|
};
|
|
2462
2703
|
|
|
2704
|
+
// src/app/cors.ts
|
|
2705
|
+
function mockUpgradeExchange(path) {
|
|
2706
|
+
const request = new MockHttpRequest(path, "GET");
|
|
2707
|
+
request.headers.set("upgrade", "websocket");
|
|
2708
|
+
request["_req"] = { upgrade: true };
|
|
2709
|
+
return new DefaultWebExchange(request, new MockHttpResponse());
|
|
2710
|
+
}
|
|
2711
|
+
async function createCorsConfigSource(context) {
|
|
2712
|
+
const { sockets: routes3, cors } = context;
|
|
2713
|
+
const defaultCorsConfig = combineCorsConfig(PERMIT_DEFAULT_CONFIG, context.corsConfig);
|
|
2714
|
+
const validatedConfigs = [];
|
|
2715
|
+
for (const [path, route] of routes3) {
|
|
2716
|
+
let routeCorsConfig = defaultCorsConfig;
|
|
2717
|
+
const upgradeExchange = mockUpgradeExchange(path);
|
|
2718
|
+
for (const [matcher, config] of cors) {
|
|
2719
|
+
if ((await matcher(upgradeExchange)).match) {
|
|
2720
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, config);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
routeCorsConfig = combineCorsConfig(routeCorsConfig, {
|
|
2724
|
+
allowOrigins: route.originFilters?.allow,
|
|
2725
|
+
allowMethods: ["GET", "CONNECT"],
|
|
2726
|
+
allowHeaders: ["upgrade", "connection", "origin", "sec-websocket-key", "sec-websocket-version", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2727
|
+
exposeHeaders: ["sec-websocket-accept", "sec-websocket-protocol", "sec-websocket-extensions"],
|
|
2728
|
+
allowCredentials: route.authorize?.access !== "permitted"
|
|
2729
|
+
});
|
|
2730
|
+
validatedConfigs.push([and([upgradeMatcher, pattern(path)]), validateCorsConfig(routeCorsConfig)]);
|
|
2731
|
+
}
|
|
2732
|
+
for (const [matcher, config] of cors) {
|
|
2733
|
+
const routeCorsConfig = combineCorsConfig(defaultCorsConfig, config);
|
|
2734
|
+
validatedConfigs.push([matcher, validateCorsConfig(routeCorsConfig)]);
|
|
2735
|
+
}
|
|
2736
|
+
validatedConfigs.push([pattern(/\/api\/.*/), validateCorsConfig(defaultCorsConfig)]);
|
|
2737
|
+
return matchingCorsConfigSource({ mappings: validatedConfigs });
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// src/app/auth.ts
|
|
2741
|
+
function createSecurityConfig(context) {
|
|
2742
|
+
const authorize = [];
|
|
2743
|
+
for (const [path, route] of context.sockets) {
|
|
2744
|
+
const rule = route.authorize ?? { access: "authenticated" };
|
|
2745
|
+
let matcher = pattern(path, { method: "GET" });
|
|
2746
|
+
matcher = and([upgradeMatcher, matcher]);
|
|
2747
|
+
authorize.push([matcher, rule]);
|
|
2748
|
+
}
|
|
2749
|
+
authorize.push([pattern("/", { method: "GET" }), { access: "permitted" }]);
|
|
2750
|
+
authorize.push([pattern("/favicon.ico", { method: "GET" }), { access: "permitted" }]);
|
|
2751
|
+
authorize.push([pattern("/health", { method: "GET" }), { access: "permitted" }]);
|
|
2752
|
+
if (context.authorize.length > 0) {
|
|
2753
|
+
authorize.push(...context.authorize);
|
|
2754
|
+
}
|
|
2755
|
+
authorize.push(["any-exchange", { access: "authenticated" }]);
|
|
2756
|
+
return {
|
|
2757
|
+
authorize,
|
|
2758
|
+
basic: {
|
|
2759
|
+
disabled: context.authConfig?.type !== "basic",
|
|
2760
|
+
...context.authConfig?.basic
|
|
2761
|
+
},
|
|
2762
|
+
jwt: {
|
|
2763
|
+
disabled: context.authConfig?.type !== "oauth2",
|
|
2764
|
+
...context.authConfig?.oauth2?.jwt
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
async function httpSecurity(context) {
|
|
2769
|
+
const corsConfigSource = await createCorsConfigSource(context);
|
|
2770
|
+
const config = createSecurityConfig(context);
|
|
2771
|
+
const { storage } = context;
|
|
2772
|
+
return config_default(config, { storage, corsConfigSource });
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2463
2775
|
// src/server.ts
|
|
2464
|
-
var
|
|
2776
|
+
var logger10 = getLogger("app");
|
|
2465
2777
|
function secureContextOptions(ssl) {
|
|
2466
2778
|
const options = {};
|
|
2467
2779
|
if (ssl.key) options.key = readFileSync(ssl.key);
|
|
@@ -2469,60 +2781,38 @@ function secureContextOptions(ssl) {
|
|
|
2469
2781
|
if (ssl.ca) options.ca = readFileSync(ssl.ca);
|
|
2470
2782
|
return options;
|
|
2471
2783
|
}
|
|
2472
|
-
function createListener(
|
|
2784
|
+
async function createListener(middleware, context, onSocketError) {
|
|
2785
|
+
const storage = context.storage;
|
|
2786
|
+
const security = await httpSecurity(context);
|
|
2473
2787
|
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.headers.one("connection") === "Upgrade" && request.headers.one("upgrade")?.toLowerCase() === "websocket") {
|
|
2788
|
+
server_header_default(context.serverHeader),
|
|
2789
|
+
...security,
|
|
2790
|
+
// websocket upgrade handler
|
|
2791
|
+
async (exchange, next) => {
|
|
2792
|
+
const [route, path] = findSocketRoute(exchange, context);
|
|
2793
|
+
if (route !== void 0) {
|
|
2794
|
+
const { request, response } = exchange;
|
|
2795
|
+
const upgradeMatchResult = await upgradeMatcher(exchange);
|
|
2796
|
+
if ((request.method === "GET" || request.method === "CONNECT") && upgradeMatchResult.match) {
|
|
2507
2797
|
const socket = request.socket;
|
|
2508
2798
|
const host = request.host;
|
|
2509
2799
|
const info = socketKey(request._req.socket);
|
|
2510
|
-
if (route
|
|
2800
|
+
if (route.wss) {
|
|
2511
2801
|
socket.removeListener("error", onSocketError);
|
|
2512
2802
|
const wss = route.wss;
|
|
2513
2803
|
if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
|
|
2514
|
-
|
|
2804
|
+
logger10.warn(`${info} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
|
|
2515
2805
|
socket.destroy();
|
|
2516
2806
|
return;
|
|
2517
2807
|
}
|
|
2518
|
-
const origin = request.headers
|
|
2808
|
+
const origin = request.headers.one("origin");
|
|
2519
2809
|
if (!acceptsOrigin(origin, route.originFilters)) {
|
|
2520
|
-
|
|
2810
|
+
logger10.info(`${info} dropping ws connection request from ${host} on ${path}. origin ${origin ?? "<missing>"}`);
|
|
2521
2811
|
socket.destroy();
|
|
2522
2812
|
return;
|
|
2523
2813
|
}
|
|
2524
|
-
if (
|
|
2525
|
-
|
|
2814
|
+
if (logger10.enabledFor("debug")) {
|
|
2815
|
+
logger10.debug(`${info} accepted new ws connection request from ${host} on ${path}`);
|
|
2526
2816
|
}
|
|
2527
2817
|
wss.handleUpgrade(request._req, socket, request._req["_upgradeHead"], (ws) => {
|
|
2528
2818
|
response._res["_header"] = true;
|
|
@@ -2532,29 +2822,36 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2532
2822
|
wss.emit("connection", ws, request._req);
|
|
2533
2823
|
});
|
|
2534
2824
|
} else {
|
|
2535
|
-
|
|
2825
|
+
logger10.warn(`${info} rejected upgrade request from ${host} on ${path}`);
|
|
2536
2826
|
socket.destroy();
|
|
2537
2827
|
}
|
|
2538
2828
|
} else {
|
|
2539
|
-
if (
|
|
2540
|
-
|
|
2829
|
+
if (route.default) {
|
|
2830
|
+
await next();
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
if (logger10.enabledFor("debug")) {
|
|
2834
|
+
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
2835
|
}
|
|
2542
2836
|
response.statusCode = 426;
|
|
2543
|
-
response.
|
|
2837
|
+
response.headers.set("Upgrade", "websocket").set("Connection", "Upgrade").set("Content-Type", "text/plain");
|
|
2544
2838
|
await response.end(`This service [${request.path}] requires use of the websocket protocol.`);
|
|
2545
2839
|
}
|
|
2546
2840
|
} else {
|
|
2547
2841
|
await next();
|
|
2548
2842
|
}
|
|
2549
2843
|
},
|
|
2844
|
+
...middleware,
|
|
2845
|
+
// helth check
|
|
2550
2846
|
async ({ request, response }, next) => {
|
|
2551
2847
|
if (request.method === "GET" && request.path === "/health") {
|
|
2552
2848
|
response.statusCode = 200;
|
|
2553
|
-
response.
|
|
2849
|
+
await response.end(http.STATUS_CODES[200]);
|
|
2554
2850
|
} else {
|
|
2555
2851
|
await next();
|
|
2556
2852
|
}
|
|
2557
2853
|
},
|
|
2854
|
+
// home page
|
|
2558
2855
|
async ({ request, response }, next) => {
|
|
2559
2856
|
if (request.method === "GET" && request.path === "/") {
|
|
2560
2857
|
await response.end(`io.Gateway Server`);
|
|
@@ -2562,7 +2859,8 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2562
2859
|
await next();
|
|
2563
2860
|
}
|
|
2564
2861
|
},
|
|
2565
|
-
|
|
2862
|
+
// not found
|
|
2863
|
+
async ({ response }, _next) => {
|
|
2566
2864
|
response.statusCode = 404;
|
|
2567
2865
|
await response.end(http.STATUS_CODES[404]);
|
|
2568
2866
|
}
|
|
@@ -2571,17 +2869,17 @@ function createListener(storage, middleware, routes3, onSocketError) {
|
|
|
2571
2869
|
request.socket.addListener("error", onSocketError);
|
|
2572
2870
|
const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
|
|
2573
2871
|
return storage.run({ exchange }, async () => {
|
|
2574
|
-
if (
|
|
2872
|
+
if (logger10.enabledFor("debug")) {
|
|
2575
2873
|
const socket = exchange.request._req.socket;
|
|
2576
|
-
if (
|
|
2577
|
-
|
|
2874
|
+
if (logger10.enabledFor("debug")) {
|
|
2875
|
+
logger10.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
|
|
2578
2876
|
}
|
|
2579
2877
|
}
|
|
2580
2878
|
try {
|
|
2581
2879
|
return await listener(exchange);
|
|
2582
2880
|
} catch (e) {
|
|
2583
|
-
if (
|
|
2584
|
-
|
|
2881
|
+
if (logger10.enabledFor("warn")) {
|
|
2882
|
+
logger10.warn(`error processing request for ${exchange.path}`, e);
|
|
2585
2883
|
}
|
|
2586
2884
|
} finally {
|
|
2587
2885
|
await exchange.response.end();
|
|
@@ -2616,65 +2914,75 @@ function regexAwareReplacer(_key, value) {
|
|
|
2616
2914
|
}
|
|
2617
2915
|
var Factory = async (options) => {
|
|
2618
2916
|
const ssl = options.ssl;
|
|
2619
|
-
const createServer = ssl ? (options2,
|
|
2917
|
+
const createServer = ssl ? (options2, handler) => https.createServer({ ...options2, ...secureContextOptions(ssl) }, handler) : (options2, handler) => http.createServer(options2, handler);
|
|
2620
2918
|
const monitor = memoryMonitor(options.memory);
|
|
2621
2919
|
const middleware = [];
|
|
2622
|
-
const
|
|
2920
|
+
const context = {
|
|
2921
|
+
corsConfig: options.cors,
|
|
2922
|
+
cors: [],
|
|
2923
|
+
authConfig: options.auth,
|
|
2924
|
+
authorize: [],
|
|
2925
|
+
storage: new AsyncLocalStorage2(),
|
|
2926
|
+
sockets: /* @__PURE__ */ new Map()
|
|
2927
|
+
};
|
|
2623
2928
|
const gw = IOGateway7.Factory({ ...options.gateway });
|
|
2624
2929
|
if (options.gateway) {
|
|
2625
2930
|
const config = options.gateway;
|
|
2626
|
-
|
|
2931
|
+
const route = config.route ?? "/";
|
|
2932
|
+
context.sockets.set(route, {
|
|
2627
2933
|
default: config.route === void 0,
|
|
2628
2934
|
ping: options.gateway.ping,
|
|
2629
2935
|
factory: core_default.bind(gw),
|
|
2630
2936
|
maxConnections: config.limits?.max_connections,
|
|
2937
|
+
authorize: config.authorize,
|
|
2631
2938
|
originFilters: regexifyOriginFilters(config.origins)
|
|
2632
2939
|
});
|
|
2633
2940
|
}
|
|
2634
2941
|
if (options.mesh) {
|
|
2635
2942
|
const connections = new InMemoryNodeConnections(options.mesh.timeout ?? 6e4);
|
|
2636
|
-
|
|
2943
|
+
const authorize = options.mesh.authorize;
|
|
2944
|
+
middleware.push(...routes_default(connections, context, authorize));
|
|
2637
2945
|
const ping = options.mesh.ping ?? 3e4;
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2946
|
+
const originFilters = regexifyOriginFilters(options.mesh.origins);
|
|
2947
|
+
context.sockets.set("/broker", { factory: core_default2, ping, originFilters, authorize });
|
|
2948
|
+
context.sockets.set("/cluster", { factory: core_default4, ping, originFilters, authorize });
|
|
2949
|
+
context.sockets.set("/relays", { factory: core_default3, ping, originFilters, authorize });
|
|
2641
2950
|
}
|
|
2642
2951
|
if (options.metrics) {
|
|
2643
|
-
middleware.push(...await routes_default2(options.metrics));
|
|
2952
|
+
middleware.push(...await routes_default2(options.metrics, context));
|
|
2644
2953
|
}
|
|
2645
2954
|
const ports = portRange(options.port ?? 0);
|
|
2646
2955
|
const host = options.host;
|
|
2647
|
-
const
|
|
2956
|
+
const onSocketError = (err) => logger10.error(`socket error: ${err}`, err);
|
|
2957
|
+
const listener = await createListener(middleware, context, onSocketError);
|
|
2648
2958
|
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
2959
|
const server2 = createServer({}, listener);
|
|
2652
2960
|
server2.on("error", (e) => {
|
|
2653
2961
|
if (e["code"] === "EADDRINUSE") {
|
|
2654
|
-
|
|
2962
|
+
logger10.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
|
|
2655
2963
|
const { value: port } = ports.next();
|
|
2656
2964
|
if (port) {
|
|
2657
|
-
|
|
2965
|
+
logger10.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
|
|
2658
2966
|
server2.close();
|
|
2659
2967
|
server2.listen(port, host);
|
|
2660
2968
|
} else {
|
|
2661
|
-
|
|
2969
|
+
logger10.warn(`all configured port(s) ${options.port} are in use. closing...`);
|
|
2662
2970
|
server2.close();
|
|
2663
2971
|
reject(e);
|
|
2664
2972
|
}
|
|
2665
2973
|
} else {
|
|
2666
|
-
|
|
2974
|
+
logger10.error(`server error: ${e.message}`, e);
|
|
2667
2975
|
reject(e);
|
|
2668
2976
|
}
|
|
2669
2977
|
});
|
|
2670
2978
|
server2.on("listening", async () => {
|
|
2671
2979
|
const info = server2.address();
|
|
2672
|
-
for (const [path, route] of
|
|
2980
|
+
for (const [path, route] of context.sockets) {
|
|
2673
2981
|
try {
|
|
2674
|
-
|
|
2982
|
+
logger10.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
|
|
2675
2983
|
const wss = new WebSocketServer({ noServer: true });
|
|
2676
2984
|
const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info.port}${path}`;
|
|
2677
|
-
const
|
|
2985
|
+
const handler = await route.factory({ endpoint, wss, storage: context.storage });
|
|
2678
2986
|
const pingInterval = route.ping;
|
|
2679
2987
|
if (pingInterval) {
|
|
2680
2988
|
const pingIntervalId = setInterval(() => {
|
|
@@ -2691,12 +2999,12 @@ var Factory = async (options) => {
|
|
|
2691
2999
|
});
|
|
2692
3000
|
}
|
|
2693
3001
|
route.wss = wss;
|
|
2694
|
-
route.close =
|
|
3002
|
+
route.close = handler.close?.bind(handler);
|
|
2695
3003
|
} catch (e) {
|
|
2696
|
-
|
|
3004
|
+
logger10.warn(`failed to init route ${path}`, e);
|
|
2697
3005
|
}
|
|
2698
3006
|
}
|
|
2699
|
-
|
|
3007
|
+
logger10.info(`http server listening on ${info.address}:${info.port}`);
|
|
2700
3008
|
resolve(server2);
|
|
2701
3009
|
});
|
|
2702
3010
|
server2.on("upgrade", (req, socket, head) => {
|
|
@@ -2707,16 +3015,16 @@ var Factory = async (options) => {
|
|
|
2707
3015
|
res.assignSocket(socket);
|
|
2708
3016
|
listener(req, res);
|
|
2709
3017
|
} catch (err) {
|
|
2710
|
-
|
|
3018
|
+
logger10.error(`upgrade error: ${err}`, err);
|
|
2711
3019
|
}
|
|
2712
3020
|
}).on("close", async () => {
|
|
2713
|
-
|
|
3021
|
+
logger10.info(`http server closed.`);
|
|
2714
3022
|
});
|
|
2715
3023
|
try {
|
|
2716
3024
|
const { value: port } = ports.next();
|
|
2717
3025
|
server2.listen(port, host);
|
|
2718
3026
|
} catch (e) {
|
|
2719
|
-
|
|
3027
|
+
logger10.error(`error starting web socket server`, e);
|
|
2720
3028
|
reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
|
|
2721
3029
|
}
|
|
2722
3030
|
});
|
|
@@ -2724,18 +3032,18 @@ var Factory = async (options) => {
|
|
|
2724
3032
|
return new class {
|
|
2725
3033
|
gateway = gw;
|
|
2726
3034
|
async close() {
|
|
2727
|
-
for (const [path, route] of
|
|
3035
|
+
for (const [path, route] of context.sockets) {
|
|
2728
3036
|
try {
|
|
2729
3037
|
if (route.close) {
|
|
2730
3038
|
await route.close();
|
|
2731
3039
|
}
|
|
2732
|
-
|
|
3040
|
+
logger10.info(`stopping ws server for [${path}]. clients: ${route.wss?.clients?.size ?? 0}`);
|
|
2733
3041
|
route.wss?.clients?.forEach((client) => {
|
|
2734
3042
|
client.terminate();
|
|
2735
3043
|
});
|
|
2736
3044
|
route.wss?.close();
|
|
2737
3045
|
} catch (e) {
|
|
2738
|
-
|
|
3046
|
+
logger10.warn(`error closing route ${path}`, e);
|
|
2739
3047
|
}
|
|
2740
3048
|
}
|
|
2741
3049
|
await promisify((cb) => {
|