@interopio/gateway-server 0.4.0-beta → 0.5.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 +18 -0
- package/dist/gateway-ent.cjs +25 -2
- package/dist/gateway-ent.cjs.map +2 -2
- package/dist/gateway-ent.js +25 -2
- package/dist/gateway-ent.js.map +2 -2
- package/dist/index.cjs +1182 -109
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +1182 -109
- package/dist/index.js.map +4 -4
- package/dist/metrics/publisher/rest.cjs +60 -0
- package/dist/metrics/publisher/rest.cjs.map +7 -0
- package/dist/metrics/publisher/rest.js +23 -0
- package/dist/metrics/publisher/rest.js.map +7 -0
- package/package.json +12 -12
- package/types/gateway-ent.d.ts +2 -1
- package/dist/metrics-rest.cjs +0 -21440
- package/dist/metrics-rest.cjs.map +0 -7
- package/dist/metrics-rest.js +0 -21430
- package/dist/metrics-rest.js.map +0 -7
- package/src/common/compose.ts +0 -40
- package/src/gateway/ent/config.ts +0 -174
- package/src/gateway/ent/index.ts +0 -18
- package/src/gateway/ent/logging.ts +0 -89
- package/src/gateway/ent/server.ts +0 -34
- package/src/gateway/metrics/rest.ts +0 -20
- package/src/gateway/ws/core.ts +0 -90
- package/src/index.ts +0 -3
- package/src/logger.ts +0 -6
- package/src/mesh/connections.ts +0 -101
- package/src/mesh/rest-directory/routes.ts +0 -38
- package/src/mesh/ws/broker/core.ts +0 -163
- package/src/mesh/ws/cluster/core.ts +0 -107
- package/src/mesh/ws/relays/core.ts +0 -159
- package/src/metrics/routes.ts +0 -86
- package/src/server/address.ts +0 -47
- package/src/server/cors.ts +0 -311
- package/src/server/exchange.ts +0 -379
- package/src/server/monitoring.ts +0 -167
- package/src/server/types.ts +0 -69
- package/src/server/ws-client-verify.ts +0 -79
- package/src/server.ts +0 -316
- package/src/utils.ts +0 -10
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { WebSocketServer } from "ws";
|
|
|
13
13
|
import http from "node:http";
|
|
14
14
|
import https from "node:https";
|
|
15
15
|
import { readFileSync } from "node:fs";
|
|
16
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
16
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
17
17
|
import { IOGateway as IOGateway7 } from "@interopio/gateway";
|
|
18
18
|
|
|
19
19
|
// src/logger.ts
|
|
@@ -42,6 +42,7 @@ var WebExchange = class {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// src/server/exchange.ts
|
|
45
|
+
import { Cookie } from "tough-cookie";
|
|
45
46
|
function requestToProtocol(request, defaultProtocol) {
|
|
46
47
|
let proto = request.headers.get("x-forwarded-proto");
|
|
47
48
|
if (Array.isArray(proto)) {
|
|
@@ -65,9 +66,7 @@ function requestToHost(request, defaultHost) {
|
|
|
65
66
|
host = `${host}:${port}`;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
host = request.headers.one("host");
|
|
70
|
-
}
|
|
69
|
+
host ??= request.headers.one("host");
|
|
71
70
|
}
|
|
72
71
|
if (Array.isArray(host)) {
|
|
73
72
|
host = host[0];
|
|
@@ -77,14 +76,22 @@ function requestToHost(request, defaultHost) {
|
|
|
77
76
|
}
|
|
78
77
|
return defaultHost;
|
|
79
78
|
}
|
|
79
|
+
function parseCookies(request) {
|
|
80
|
+
return request.headers.list("cookie").map((s) => s.split(";").map((cs) => Cookie.parse(cs))).flat(1).filter((tc) => tc !== void 0).map((tc) => {
|
|
81
|
+
const result = { name: tc.key, value: tc.value };
|
|
82
|
+
return result;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
80
85
|
var HttpServerRequest = class {
|
|
81
|
-
constructor(_req) {
|
|
82
|
-
this._req = _req;
|
|
83
|
-
this._headers = new IncomingMessageHeaders(_req);
|
|
84
|
-
}
|
|
85
86
|
_body;
|
|
86
87
|
_url;
|
|
88
|
+
_cookies;
|
|
87
89
|
_headers;
|
|
90
|
+
_req;
|
|
91
|
+
constructor(req) {
|
|
92
|
+
this._req = req;
|
|
93
|
+
this._headers = new IncomingMessageHeaders(this._req);
|
|
94
|
+
}
|
|
88
95
|
get http2() {
|
|
89
96
|
return this._req.httpVersionMajor >= 2;
|
|
90
97
|
}
|
|
@@ -109,9 +116,7 @@ var HttpServerRequest = class {
|
|
|
109
116
|
if (this._req.httpVersionMajor >= 2) {
|
|
110
117
|
dh = (this._req?.headers)[":authority"];
|
|
111
118
|
}
|
|
112
|
-
|
|
113
|
-
dh = this._req?.socket.remoteAddress;
|
|
114
|
-
}
|
|
119
|
+
dh ??= this._req?.socket.remoteAddress;
|
|
115
120
|
return requestToHost(this, dh);
|
|
116
121
|
}
|
|
117
122
|
get protocol() {
|
|
@@ -119,14 +124,16 @@ var HttpServerRequest = class {
|
|
|
119
124
|
if (this._req.httpVersionMajor > 2) {
|
|
120
125
|
dp = this._req.headers[":scheme"];
|
|
121
126
|
}
|
|
122
|
-
|
|
123
|
-
dp = this._req?.socket["encrypted"] ? "https" : "http";
|
|
124
|
-
}
|
|
127
|
+
dp ??= this._req?.socket["encrypted"] ? "https" : "http";
|
|
125
128
|
return requestToProtocol(this, dp);
|
|
126
129
|
}
|
|
127
130
|
get socket() {
|
|
128
131
|
return this._req.socket;
|
|
129
132
|
}
|
|
133
|
+
get cookies() {
|
|
134
|
+
this._cookies ??= parseCookies(this);
|
|
135
|
+
return this._cookies;
|
|
136
|
+
}
|
|
130
137
|
get body() {
|
|
131
138
|
this._body ??= new Promise((resolve, reject) => {
|
|
132
139
|
const chunks = [];
|
|
@@ -139,6 +146,13 @@ var HttpServerRequest = class {
|
|
|
139
146
|
get text() {
|
|
140
147
|
return this.body.then(async (blob) => await blob.text());
|
|
141
148
|
}
|
|
149
|
+
get formData() {
|
|
150
|
+
return this.body.then(async (blob) => {
|
|
151
|
+
const text = await blob.text();
|
|
152
|
+
const formData = new URLSearchParams(text);
|
|
153
|
+
return formData;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
142
156
|
get json() {
|
|
143
157
|
return this.body.then(async (blob) => {
|
|
144
158
|
const json = JSON.parse(await blob.text());
|
|
@@ -171,8 +185,9 @@ var IncomingMessageHeaders = class {
|
|
|
171
185
|
}
|
|
172
186
|
};
|
|
173
187
|
var OutgoingMessageHeaders = class {
|
|
174
|
-
|
|
175
|
-
|
|
188
|
+
_msg;
|
|
189
|
+
constructor(msg) {
|
|
190
|
+
this._msg = msg;
|
|
176
191
|
}
|
|
177
192
|
has(name) {
|
|
178
193
|
return this._msg.hasHeader(name);
|
|
@@ -217,11 +232,12 @@ var OutgoingMessageHeaders = class {
|
|
|
217
232
|
}
|
|
218
233
|
};
|
|
219
234
|
var HttpServerResponse = class {
|
|
220
|
-
constructor(_res) {
|
|
221
|
-
this._res = _res;
|
|
222
|
-
this._headers = new OutgoingMessageHeaders(_res);
|
|
223
|
-
}
|
|
224
235
|
_headers;
|
|
236
|
+
_res;
|
|
237
|
+
constructor(res) {
|
|
238
|
+
this._res = res;
|
|
239
|
+
this._headers = new OutgoingMessageHeaders(res);
|
|
240
|
+
}
|
|
225
241
|
get statusCode() {
|
|
226
242
|
return this._res.statusCode;
|
|
227
243
|
}
|
|
@@ -231,9 +247,57 @@ var HttpServerResponse = class {
|
|
|
231
247
|
}
|
|
232
248
|
this._res.statusCode = value;
|
|
233
249
|
}
|
|
250
|
+
set statusMessage(value) {
|
|
251
|
+
this._res.statusMessage = value;
|
|
252
|
+
}
|
|
234
253
|
get headers() {
|
|
235
254
|
return this._headers;
|
|
236
255
|
}
|
|
256
|
+
get cookies() {
|
|
257
|
+
return this.headers.list("set-cookie").map((cookie) => {
|
|
258
|
+
const parsed = Cookie.parse(cookie);
|
|
259
|
+
if (parsed) {
|
|
260
|
+
const result = { name: parsed.key, value: parsed.value, maxAge: Number(parsed.maxAge ?? -1) };
|
|
261
|
+
if (parsed.httpOnly) result.httpOnly = true;
|
|
262
|
+
if (parsed.domain) result.domain = parsed.domain;
|
|
263
|
+
if (parsed.path) result.path = parsed.path;
|
|
264
|
+
if (parsed.secure) result.secure = true;
|
|
265
|
+
if (parsed.httpOnly) result.httpOnly = true;
|
|
266
|
+
if (parsed.sameSite) result.sameSite = parsed.sameSite;
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
}).filter((cookie) => cookie !== void 0);
|
|
270
|
+
}
|
|
271
|
+
end(chunk) {
|
|
272
|
+
if (!this._res.headersSent) {
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
if (chunk === void 0) {
|
|
275
|
+
this._res.end(() => {
|
|
276
|
+
resolve(true);
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
this._res.end(chunk, () => {
|
|
280
|
+
resolve(true);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
} else {
|
|
285
|
+
return Promise.resolve(false);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
addCookie(cookie) {
|
|
289
|
+
this.headers.add("set-cookie", new Cookie({
|
|
290
|
+
key: cookie.name,
|
|
291
|
+
value: cookie.value,
|
|
292
|
+
maxAge: cookie.maxAge,
|
|
293
|
+
domain: cookie.domain,
|
|
294
|
+
path: cookie.path,
|
|
295
|
+
secure: cookie.secure,
|
|
296
|
+
httpOnly: cookie.httpOnly,
|
|
297
|
+
sameSite: cookie.sameSite
|
|
298
|
+
}).toString());
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
237
301
|
};
|
|
238
302
|
var DefaultWebExchange = class extends WebExchange {
|
|
239
303
|
constructor(request, response) {
|
|
@@ -241,6 +305,9 @@ var DefaultWebExchange = class extends WebExchange {
|
|
|
241
305
|
this.request = request;
|
|
242
306
|
this.response = response;
|
|
243
307
|
}
|
|
308
|
+
get principal() {
|
|
309
|
+
return Promise.resolve(void 0);
|
|
310
|
+
}
|
|
244
311
|
};
|
|
245
312
|
function toList(values) {
|
|
246
313
|
if (typeof values === "string") {
|
|
@@ -284,17 +351,61 @@ function parseHeader(value) {
|
|
|
284
351
|
}
|
|
285
352
|
return list;
|
|
286
353
|
}
|
|
354
|
+
var MapHttpHeaders = class extends Map {
|
|
355
|
+
get(name) {
|
|
356
|
+
return super.get(name.toLowerCase());
|
|
357
|
+
}
|
|
358
|
+
one(name) {
|
|
359
|
+
return this.get(name)?.[0];
|
|
360
|
+
}
|
|
361
|
+
list(name) {
|
|
362
|
+
const values = super.get(name.toLowerCase());
|
|
363
|
+
return toList(values);
|
|
364
|
+
}
|
|
365
|
+
set(name, value) {
|
|
366
|
+
if (typeof value === "number") {
|
|
367
|
+
value = String(value);
|
|
368
|
+
}
|
|
369
|
+
if (typeof value === "string") {
|
|
370
|
+
value = [value];
|
|
371
|
+
}
|
|
372
|
+
if (value) {
|
|
373
|
+
return super.set(name.toLowerCase(), value);
|
|
374
|
+
} else {
|
|
375
|
+
super.delete(name.toLowerCase());
|
|
376
|
+
return this;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
add(name, value) {
|
|
380
|
+
const prev = super.get(name.toLowerCase());
|
|
381
|
+
if (typeof value === "string") {
|
|
382
|
+
value = [value];
|
|
383
|
+
}
|
|
384
|
+
if (prev) {
|
|
385
|
+
value = prev.concat(value);
|
|
386
|
+
}
|
|
387
|
+
this.set(name, value);
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
287
391
|
|
|
288
392
|
// src/gateway/ws/core.ts
|
|
289
393
|
import { IOGateway as IOGateway2 } from "@interopio/gateway";
|
|
290
394
|
var GatewayEncoders = IOGateway2.Encoding;
|
|
291
395
|
var log = getLogger("ws");
|
|
292
396
|
var codec = GatewayEncoders.json();
|
|
293
|
-
function initClient(key, socket, host) {
|
|
397
|
+
function initClient(key, socket, host, securityContextPromise) {
|
|
294
398
|
const opts = {
|
|
295
399
|
key,
|
|
296
400
|
host,
|
|
297
401
|
codec,
|
|
402
|
+
onAuthenticate: async () => {
|
|
403
|
+
const authentication = (await securityContextPromise)?.authentication;
|
|
404
|
+
if (authentication?.authenticated) {
|
|
405
|
+
return { type: "success", user: authentication.name };
|
|
406
|
+
}
|
|
407
|
+
throw new Error(`no valid client authentication ${key}`);
|
|
408
|
+
},
|
|
298
409
|
onPing: () => {
|
|
299
410
|
socket.ping((err) => {
|
|
300
411
|
if (err) {
|
|
@@ -331,10 +442,11 @@ async function create(server) {
|
|
|
331
442
|
log.error("error starting the gateway websocket server", err);
|
|
332
443
|
}).on("connection", (socket, req) => {
|
|
333
444
|
const request = new HttpServerRequest(req);
|
|
445
|
+
const securityContextPromise = server.storage?.getStore()?.securityContext;
|
|
334
446
|
const key = socketKey(request.socket);
|
|
335
447
|
const host = request.host;
|
|
336
448
|
log.info(`${key} connected on gw from ${host}`);
|
|
337
|
-
const client = initClient.call(this, key, socket);
|
|
449
|
+
const client = initClient.call(this, key, socket, host, securityContextPromise);
|
|
338
450
|
if (!client) {
|
|
339
451
|
log.error(`${key} gw client init failed`);
|
|
340
452
|
socket.terminate();
|
|
@@ -836,9 +948,8 @@ var logger5 = getLogger("metrics");
|
|
|
836
948
|
var COOKIE_NAME = "GW_LOGIN";
|
|
837
949
|
function loggedIn(auth, ctx) {
|
|
838
950
|
if (auth) {
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
return cookie && parseInt(cookie?.substring(COOKIE_NAME.length + 1)) > Date.now();
|
|
951
|
+
const value = ctx.request.cookies.find((cookie) => cookie.name === COOKIE_NAME)?.value;
|
|
952
|
+
return value && parseInt(value) > Date.now();
|
|
842
953
|
}
|
|
843
954
|
return true;
|
|
844
955
|
}
|
|
@@ -864,7 +975,7 @@ async function routes2(config) {
|
|
|
864
975
|
if (ctx.method === "GET" && ctx.path === "/api/login") {
|
|
865
976
|
const redirectTo = new URLSearchParams(ctx.request.query ?? void 0).get("redirectTo");
|
|
866
977
|
const expires = Date.now() + 180 * 1e3;
|
|
867
|
-
ctx.response.
|
|
978
|
+
ctx.response.addCookie({ name: COOKIE_NAME, value: `${expires}`, maxAge: Infinity, path: "/api", sameSite: "strict" });
|
|
868
979
|
if (redirectTo) {
|
|
869
980
|
ctx.response.statusCode = 302;
|
|
870
981
|
ctx.response.headers.set("location", redirectTo);
|
|
@@ -910,30 +1021,38 @@ function compose(...middleware) {
|
|
|
910
1021
|
if (!Array.isArray(middleware)) {
|
|
911
1022
|
throw new Error("middleware must be array!");
|
|
912
1023
|
}
|
|
913
|
-
|
|
1024
|
+
const fns = middleware.flat();
|
|
1025
|
+
for (const fn of fns) {
|
|
914
1026
|
if (typeof fn !== "function") {
|
|
915
1027
|
throw new Error("middleware must be compose of functions!");
|
|
916
1028
|
}
|
|
917
1029
|
}
|
|
918
1030
|
return async function(ctx, next) {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
if (i < index) {
|
|
923
|
-
throw new Error("next() called multiple times");
|
|
924
|
-
}
|
|
925
|
-
index = i;
|
|
926
|
-
let fn;
|
|
927
|
-
if (i === middleware.length) {
|
|
928
|
-
fn = next;
|
|
929
|
-
} else {
|
|
930
|
-
fn = middleware[i];
|
|
931
|
-
}
|
|
932
|
-
if (!fn) {
|
|
1031
|
+
const dispatch = async (i) => {
|
|
1032
|
+
const fn = i === fns.length ? next : fns[i];
|
|
1033
|
+
if (fn === void 0) {
|
|
933
1034
|
return;
|
|
934
1035
|
}
|
|
935
|
-
|
|
936
|
-
|
|
1036
|
+
let nextCalled = false;
|
|
1037
|
+
let nextResolved = false;
|
|
1038
|
+
const nextFn = async () => {
|
|
1039
|
+
if (nextCalled) {
|
|
1040
|
+
throw new Error("next() called multiple times");
|
|
1041
|
+
}
|
|
1042
|
+
nextCalled = true;
|
|
1043
|
+
try {
|
|
1044
|
+
return await dispatch(i + 1);
|
|
1045
|
+
} finally {
|
|
1046
|
+
nextResolved = true;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
const result = await fn(ctx, nextFn);
|
|
1050
|
+
if (nextCalled && !nextResolved) {
|
|
1051
|
+
throw new Error("middleware resolved before downstream.\n You are probably missing an await or return statement in your middleware function.");
|
|
1052
|
+
}
|
|
1053
|
+
return result;
|
|
1054
|
+
};
|
|
1055
|
+
return dispatch(0);
|
|
937
1056
|
};
|
|
938
1057
|
}
|
|
939
1058
|
|
|
@@ -1252,9 +1371,9 @@ function processRequest(exchange, config) {
|
|
|
1252
1371
|
}
|
|
1253
1372
|
function validateConfig(config) {
|
|
1254
1373
|
if (config) {
|
|
1255
|
-
const
|
|
1256
|
-
if (
|
|
1257
|
-
|
|
1374
|
+
const headers2 = config.headers;
|
|
1375
|
+
if (headers2?.allow && headers2.allow !== ALL) {
|
|
1376
|
+
headers2.allow = headers2.allow.map((header) => header.toLowerCase());
|
|
1258
1377
|
}
|
|
1259
1378
|
const origins = config.origins;
|
|
1260
1379
|
if (origins?.allow && origins.allow !== ALL) {
|
|
@@ -1273,7 +1392,7 @@ var handler = (config) => {
|
|
|
1273
1392
|
return async (ctx, next) => {
|
|
1274
1393
|
const isValid = processRequest(ctx, config);
|
|
1275
1394
|
if (!isValid || isPreFlightRequest(ctx.request)) {
|
|
1276
|
-
ctx.response.
|
|
1395
|
+
await ctx.response.end();
|
|
1277
1396
|
} else {
|
|
1278
1397
|
await next();
|
|
1279
1398
|
}
|
|
@@ -1413,10 +1532,934 @@ function getMethodToUse(request, isPreFlight) {
|
|
|
1413
1532
|
return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
|
|
1414
1533
|
}
|
|
1415
1534
|
function getHeadersToUse(request, isPreFlight) {
|
|
1416
|
-
const
|
|
1417
|
-
return 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";
|
|
1577
|
+
}
|
|
1578
|
+
if (preload) {
|
|
1579
|
+
headerValue += " ; preload";
|
|
1580
|
+
}
|
|
1581
|
+
const delegate = staticServerHttpHeadersWriter(
|
|
1582
|
+
new MapHttpHeaders().add("strict-transport-security", headerValue)
|
|
1583
|
+
);
|
|
1584
|
+
const isSecure = (exchange) => {
|
|
1585
|
+
const protocol = exchange.request.URL.protocol;
|
|
1586
|
+
return protocol === "https:";
|
|
1587
|
+
};
|
|
1588
|
+
return async (exchange) => {
|
|
1589
|
+
if (isSecure(exchange)) {
|
|
1590
|
+
await delegate(exchange);
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
};
|
|
1594
|
+
var frameOptionsServerHttpHeadersWriter = (mode) => {
|
|
1595
|
+
return staticServerHttpHeadersWriter(
|
|
1596
|
+
new MapHttpHeaders().add("x-frame-options", mode)
|
|
1597
|
+
);
|
|
1598
|
+
};
|
|
1599
|
+
var xssProtectionServerHttpHeadersWriter = (headerValue) => staticServerHttpHeadersWriter(
|
|
1600
|
+
new MapHttpHeaders().add("x-xss-protection", headerValue)
|
|
1601
|
+
);
|
|
1602
|
+
var permissionsPolicyServerHttpHeadersWriter = (policyDirectives) => {
|
|
1603
|
+
const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1604
|
+
new MapHttpHeaders().add("permissions-policy", policyDirectives)
|
|
1605
|
+
);
|
|
1606
|
+
return async (exchange) => {
|
|
1607
|
+
if (delegate !== void 0) {
|
|
1608
|
+
await delegate(exchange);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
};
|
|
1612
|
+
var contentSecurityPolicyServerHttpHeadersWriter = (policyDirectives, reportOnly) => {
|
|
1613
|
+
const headerName = reportOnly ? "content-security-policy-report-only" : "content-security-policy";
|
|
1614
|
+
const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1615
|
+
new MapHttpHeaders().add(headerName, policyDirectives)
|
|
1616
|
+
);
|
|
1617
|
+
return async (exchange) => {
|
|
1618
|
+
if (delegate !== void 0) {
|
|
1619
|
+
await delegate(exchange);
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
};
|
|
1623
|
+
var refererPolicyServerHttpHeadersWriter = (policy = "no-referrer") => {
|
|
1624
|
+
return staticServerHttpHeadersWriter(
|
|
1625
|
+
new MapHttpHeaders().add("referer-policy", policy)
|
|
1626
|
+
);
|
|
1627
|
+
};
|
|
1628
|
+
var crossOriginOpenerPolicyServerHttpHeadersWriter = (policy) => {
|
|
1629
|
+
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1630
|
+
new MapHttpHeaders().add("cross-origin-opener-policy", policy)
|
|
1631
|
+
);
|
|
1632
|
+
return async (exchange) => {
|
|
1633
|
+
if (delegate !== void 0) {
|
|
1634
|
+
await delegate(exchange);
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
};
|
|
1638
|
+
var crossOriginEmbedderPolicyServerHttpHeadersWriter = (policy) => {
|
|
1639
|
+
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1640
|
+
new MapHttpHeaders().add("cross-origin-embedder-policy", policy)
|
|
1641
|
+
);
|
|
1642
|
+
return async (exchange) => {
|
|
1643
|
+
if (delegate !== void 0) {
|
|
1644
|
+
await delegate(exchange);
|
|
1645
|
+
}
|
|
1646
|
+
};
|
|
1647
|
+
};
|
|
1648
|
+
var crossOriginResourcePolicyServerHttpHeadersWriter = (policy) => {
|
|
1649
|
+
const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
|
|
1650
|
+
new MapHttpHeaders().add("cross-origin-resource-policy", policy)
|
|
1651
|
+
);
|
|
1652
|
+
return async (exchange) => {
|
|
1653
|
+
if (delegate !== void 0) {
|
|
1654
|
+
await delegate(exchange);
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
};
|
|
1658
|
+
var compositeServerHttpHeadersWriter = (...writers) => {
|
|
1659
|
+
return async (exchange) => {
|
|
1660
|
+
for (const writer of writers) {
|
|
1661
|
+
await writer(exchange);
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
};
|
|
1665
|
+
function headers(opts) {
|
|
1666
|
+
const writers = [];
|
|
1667
|
+
if (!opts?.cache?.disabled) {
|
|
1668
|
+
writers.push(cacheControlServerHttpHeadersWriter());
|
|
1669
|
+
}
|
|
1670
|
+
if (!opts?.contentType?.disabled) {
|
|
1671
|
+
writers.push(contentTypeServerHttpHeadersWriter());
|
|
1672
|
+
}
|
|
1673
|
+
if (!opts?.hsts?.disabled) {
|
|
1674
|
+
writers.push(strictTransportSecurityServerHttpHeadersWriter(opts?.hsts?.maxAge ?? 365 * 24 * 60 * 60, opts?.hsts?.includeSubDomains ?? true, opts?.hsts?.preload ?? false));
|
|
1675
|
+
}
|
|
1676
|
+
if (!opts?.frameOptions?.disabled) {
|
|
1677
|
+
writers.push(frameOptionsServerHttpHeadersWriter(opts?.frameOptions?.mode ?? "DENY"));
|
|
1678
|
+
}
|
|
1679
|
+
if (!opts?.xss?.disabled) {
|
|
1680
|
+
writers.push(xssProtectionServerHttpHeadersWriter(opts?.xss?.headerValue ?? "0"));
|
|
1681
|
+
}
|
|
1682
|
+
if (!opts?.permissionsPolicy?.disabled) {
|
|
1683
|
+
writers.push(permissionsPolicyServerHttpHeadersWriter(opts?.permissionsPolicy?.policyDirectives));
|
|
1684
|
+
}
|
|
1685
|
+
if (!opts?.contentSecurityPolicy?.disabled) {
|
|
1686
|
+
writers.push(contentSecurityPolicyServerHttpHeadersWriter(opts?.contentSecurityPolicy?.policyDirectives ?? "default-src 'self'", opts?.contentSecurityPolicy?.reportOnly));
|
|
1687
|
+
}
|
|
1688
|
+
if (!opts?.refererPolicy?.disabled) {
|
|
1689
|
+
writers.push(refererPolicyServerHttpHeadersWriter(opts?.refererPolicy?.policy ?? "no-referrer"));
|
|
1690
|
+
}
|
|
1691
|
+
if (!opts?.crossOriginOpenerPolicy?.disabled) {
|
|
1692
|
+
writers.push(crossOriginOpenerPolicyServerHttpHeadersWriter(opts?.crossOriginOpenerPolicy?.policy));
|
|
1693
|
+
}
|
|
1694
|
+
if (!opts?.crossOriginEmbedderPolicy?.disabled) {
|
|
1695
|
+
writers.push(crossOriginEmbedderPolicyServerHttpHeadersWriter(opts?.crossOriginEmbedderPolicy?.policy));
|
|
1696
|
+
}
|
|
1697
|
+
if (!opts?.crossOriginResourcePolicy?.disabled) {
|
|
1698
|
+
writers.push(crossOriginResourcePolicyServerHttpHeadersWriter(opts?.crossOriginResourcePolicy?.policy));
|
|
1699
|
+
}
|
|
1700
|
+
if (opts?.writers) {
|
|
1701
|
+
writers.push(...opts.writers);
|
|
1702
|
+
}
|
|
1703
|
+
const writer = compositeServerHttpHeadersWriter(...writers);
|
|
1704
|
+
return async (exchange, next) => {
|
|
1705
|
+
await writer(exchange);
|
|
1706
|
+
await next();
|
|
1707
|
+
};
|
|
1418
1708
|
}
|
|
1419
1709
|
|
|
1710
|
+
// src/server/security/types.ts
|
|
1711
|
+
var AuthenticationError = class extends Error {
|
|
1712
|
+
_authentication;
|
|
1713
|
+
get authentication() {
|
|
1714
|
+
return this._authentication;
|
|
1715
|
+
}
|
|
1716
|
+
set authentication(value) {
|
|
1717
|
+
if (value === void 0) {
|
|
1718
|
+
throw new TypeError("Authentication cannot be undefined");
|
|
1719
|
+
}
|
|
1720
|
+
this._authentication = value;
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
var InsufficientAuthenticationError = class extends AuthenticationError {
|
|
1724
|
+
};
|
|
1725
|
+
var BadCredentialsError = class extends AuthenticationError {
|
|
1726
|
+
};
|
|
1727
|
+
var AccessDeniedError = class extends Error {
|
|
1728
|
+
};
|
|
1729
|
+
var AuthorizationDecision = class {
|
|
1730
|
+
constructor(granted) {
|
|
1731
|
+
this.granted = granted;
|
|
1732
|
+
}
|
|
1733
|
+
granted;
|
|
1734
|
+
};
|
|
1735
|
+
var DefaultAuthorizationManager = class {
|
|
1736
|
+
check;
|
|
1737
|
+
constructor(check) {
|
|
1738
|
+
this.check = check;
|
|
1739
|
+
}
|
|
1740
|
+
async verify(authentication, object) {
|
|
1741
|
+
const decision = await this.check(authentication, object);
|
|
1742
|
+
if (!decision?.granted) {
|
|
1743
|
+
throw new AccessDeniedError("Access denied");
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
async authorize(authentication, object) {
|
|
1747
|
+
return await this.check(authentication, object);
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
var AuthenticationServiceError = class extends AuthenticationError {
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
// src/server/security/entry-point-failure-handler.ts
|
|
1754
|
+
var serverAuthenticationEntryPointFailureHandler = (opts) => {
|
|
1755
|
+
const entryPoint = opts.entryPoint;
|
|
1756
|
+
const rethrowAuthenticationServiceError = opts?.rethrowAuthenticationServiceError ?? true;
|
|
1757
|
+
return async ({ exchange }, error) => {
|
|
1758
|
+
if (!rethrowAuthenticationServiceError) {
|
|
1759
|
+
return entryPoint(exchange, error);
|
|
1760
|
+
}
|
|
1761
|
+
if (!(error instanceof AuthenticationServiceError)) {
|
|
1762
|
+
return entryPoint(exchange, error);
|
|
1763
|
+
}
|
|
1764
|
+
throw error;
|
|
1765
|
+
};
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
// src/server/security/http-basic-entry-point.ts
|
|
1769
|
+
var DEFAULT_REALM = "Realm";
|
|
1770
|
+
var createHeaderValue = (realm) => {
|
|
1771
|
+
return `Basic realm="${realm}"`;
|
|
1772
|
+
};
|
|
1773
|
+
var httpBasicEntryPoint = (opts) => {
|
|
1774
|
+
const headerValue = createHeaderValue(opts?.realm ?? DEFAULT_REALM);
|
|
1775
|
+
return async (exchange, _error) => {
|
|
1776
|
+
const { response } = exchange;
|
|
1777
|
+
response.statusCode = 401;
|
|
1778
|
+
response.headers.set("WWW-Authenticate", headerValue);
|
|
1779
|
+
};
|
|
1780
|
+
};
|
|
1781
|
+
|
|
1782
|
+
// src/server/security/http-basic-converter.ts
|
|
1783
|
+
var BASIC = "Basic ";
|
|
1784
|
+
var httpBasicAuthenticationConverter = (opts) => {
|
|
1785
|
+
return async (exchange) => {
|
|
1786
|
+
const { request } = exchange;
|
|
1787
|
+
const authorization = request.headers.one("authorization");
|
|
1788
|
+
if (!authorization || !/basic/i.test(authorization.substring(0))) {
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
const credentials = authorization.length <= BASIC.length ? "" : authorization.substring(BASIC.length);
|
|
1792
|
+
const decoded = Buffer.from(credentials, "base64").toString(opts?.credentialsEncoding ?? "utf-8");
|
|
1793
|
+
const parts = decoded.split(":", 2);
|
|
1794
|
+
if (parts.length !== 2) {
|
|
1795
|
+
return void 0;
|
|
1796
|
+
}
|
|
1797
|
+
return { type: "UsernamePassword", authenticated: false, principal: parts[0], credentials: parts[1] };
|
|
1798
|
+
};
|
|
1799
|
+
};
|
|
1800
|
+
|
|
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
|
+
// src/server/security/security-context.ts
|
|
1867
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1868
|
+
var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
|
|
1869
|
+
static hasSecurityContext(storage) {
|
|
1870
|
+
return storage.getStore()?.securityContext !== void 0;
|
|
1871
|
+
}
|
|
1872
|
+
static async getSecurityContext(storage) {
|
|
1873
|
+
return await storage.getStore()?.securityContext;
|
|
1874
|
+
}
|
|
1875
|
+
static clearSecurityContext(storage) {
|
|
1876
|
+
delete storage.getStore()?.securityContext;
|
|
1877
|
+
}
|
|
1878
|
+
static withSecurityContext(securityContext) {
|
|
1879
|
+
return (storage = new AsyncLocalStorage()) => {
|
|
1880
|
+
storage.getStore().securityContext = securityContext;
|
|
1881
|
+
return storage;
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
static withAuthentication(authentication) {
|
|
1885
|
+
return _AsyncStorageSecurityContextHolder.withSecurityContext(Promise.resolve({ authentication }));
|
|
1886
|
+
}
|
|
1887
|
+
static async getContext(storage) {
|
|
1888
|
+
if (_AsyncStorageSecurityContextHolder.hasSecurityContext(storage)) {
|
|
1889
|
+
return _AsyncStorageSecurityContextHolder.getSecurityContext(storage);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
// src/server/security/authentication-filter.ts
|
|
1895
|
+
async function authenticate(exchange, next, token, managerResolver, successHandler, storage) {
|
|
1896
|
+
const authManager = await managerResolver(exchange);
|
|
1897
|
+
const authentication = await authManager?.(token);
|
|
1898
|
+
if (authentication === void 0) {
|
|
1899
|
+
throw new Error("No authentication manager found for the exchange");
|
|
1900
|
+
}
|
|
1901
|
+
try {
|
|
1902
|
+
await onAuthenticationSuccess(authentication, { exchange, next }, successHandler, storage);
|
|
1903
|
+
} catch (e) {
|
|
1904
|
+
if (e instanceof AuthenticationError) {
|
|
1905
|
+
}
|
|
1906
|
+
throw e;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
async function onAuthenticationSuccess(authentication, filterExchange, successHandler, storage) {
|
|
1910
|
+
AsyncStorageSecurityContextHolder.withAuthentication(authentication)(storage);
|
|
1911
|
+
await successHandler(filterExchange, authentication);
|
|
1912
|
+
}
|
|
1913
|
+
function authenticationFilter(opts) {
|
|
1914
|
+
const auth = {
|
|
1915
|
+
matcher: anyExchange,
|
|
1916
|
+
successHandler: async ({ next }) => {
|
|
1917
|
+
await next();
|
|
1918
|
+
},
|
|
1919
|
+
converter: httpBasicAuthenticationConverter({}),
|
|
1920
|
+
failureHandler: serverAuthenticationEntryPointFailureHandler({ entryPoint: httpBasicEntryPoint({}) }),
|
|
1921
|
+
...opts
|
|
1922
|
+
};
|
|
1923
|
+
let managerResolver = auth.managerResolver;
|
|
1924
|
+
if (managerResolver === void 0 && auth.manager !== void 0) {
|
|
1925
|
+
const manager = auth.manager;
|
|
1926
|
+
managerResolver = async (_exchange) => {
|
|
1927
|
+
return manager;
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
if (managerResolver === void 0) {
|
|
1931
|
+
throw new Error("Authentication filter requires a managerResolver or a manager");
|
|
1932
|
+
}
|
|
1933
|
+
return async (exchange, next) => {
|
|
1934
|
+
const matchResult = await auth.matcher(exchange);
|
|
1935
|
+
const token = matchResult.match ? await auth.converter(exchange) : void 0;
|
|
1936
|
+
if (token === void 0) {
|
|
1937
|
+
await next();
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
try {
|
|
1941
|
+
await authenticate(exchange, next, token, managerResolver, auth.successHandler, auth.storage);
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
if (error instanceof AuthenticationError) {
|
|
1944
|
+
await auth.failureHandler({ exchange, next }, error);
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
throw error;
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// src/server/security/oauth2/token-error.ts
|
|
1953
|
+
var BearerTokenErrorCodes = {
|
|
1954
|
+
invalid_request: "invalid_request",
|
|
1955
|
+
invalid_token: "invalid_token"
|
|
1956
|
+
};
|
|
1957
|
+
var DEFAULT_URI = "https://tools.ietf.org/html/rfc6750#section-3.1";
|
|
1958
|
+
function invalidToken(message) {
|
|
1959
|
+
return { errorCode: BearerTokenErrorCodes.invalid_token, httpStatus: 401, description: message, uri: DEFAULT_URI };
|
|
1960
|
+
}
|
|
1961
|
+
function invalidRequest(message) {
|
|
1962
|
+
return { errorCode: BearerTokenErrorCodes.invalid_request, httpStatus: 400, description: message, uri: DEFAULT_URI };
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// src/server/security/oauth2/token-converter.ts
|
|
1966
|
+
var ACCESS_TOKEN_PARAMETER_NAME = "access_token";
|
|
1967
|
+
var authorizationPattern = /^Bearer\s+(?<token>[a-zA-Z0-9-._~+/]+=*)$/i;
|
|
1968
|
+
var Oauth2AuthenticationError = class extends AuthenticationError {
|
|
1969
|
+
error;
|
|
1970
|
+
constructor(error, message, options) {
|
|
1971
|
+
super(message ?? (typeof error === "string" ? void 0 : error.description), options);
|
|
1972
|
+
this.error = typeof error === "string" ? { errorCode: error } : error;
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
var isBearerTokenAuthenticationToken = (authentication) => {
|
|
1976
|
+
return authentication.type === "BearerToken";
|
|
1977
|
+
};
|
|
1978
|
+
var serverBearerTokenAuthenticationConverter = (opts) => {
|
|
1979
|
+
return async (exchange) => {
|
|
1980
|
+
const { request } = exchange;
|
|
1981
|
+
return Promise.all([
|
|
1982
|
+
resolveFromAuthorizationHeader(request.headers, opts?.headerName).then((token) => token !== void 0 ? [token] : void 0),
|
|
1983
|
+
resolveFromQueryString(request, opts?.uriQueryParameter),
|
|
1984
|
+
resolveFromBody(exchange, opts?.formEncodedBodyParameter)
|
|
1985
|
+
]).then((rs) => rs.filter((r) => r !== void 0).flat(1)).then(resolveToken).then((token) => {
|
|
1986
|
+
if (token) return { authenticated: false, type: "BearerToken", token };
|
|
1987
|
+
});
|
|
1988
|
+
};
|
|
1989
|
+
};
|
|
1990
|
+
async function resolveToken(accessTokens) {
|
|
1991
|
+
if (accessTokens.length === 0) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
if (accessTokens.length > 1) {
|
|
1995
|
+
const error = invalidRequest("Found multiple access tokens in the request");
|
|
1996
|
+
throw new Oauth2AuthenticationError(error);
|
|
1997
|
+
}
|
|
1998
|
+
const accessToken = accessTokens[0];
|
|
1999
|
+
if (!accessToken || accessToken.length === 0) {
|
|
2000
|
+
const error = invalidRequest("The requested access token parameter is an empty string");
|
|
2001
|
+
throw new Oauth2AuthenticationError(error);
|
|
2002
|
+
}
|
|
2003
|
+
return accessToken;
|
|
2004
|
+
}
|
|
2005
|
+
async function resolveFromAuthorizationHeader(headers2, headerName = "authorization") {
|
|
2006
|
+
const authorization = headers2.one(headerName);
|
|
2007
|
+
if (!authorization || !/bearer/i.test(authorization.substring(0))) {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
const match = authorizationPattern.exec(authorization);
|
|
2011
|
+
if (match === null) {
|
|
2012
|
+
const error = invalidToken("Bearer token is malformed");
|
|
2013
|
+
throw new Oauth2AuthenticationError(error);
|
|
2014
|
+
}
|
|
2015
|
+
return match.groups?.token;
|
|
2016
|
+
}
|
|
2017
|
+
async function resolveTokens(parameters) {
|
|
2018
|
+
const accessTokens = parameters.getAll(ACCESS_TOKEN_PARAMETER_NAME);
|
|
2019
|
+
if (accessTokens.length === 0) {
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
return accessTokens;
|
|
2023
|
+
}
|
|
2024
|
+
async function resolveFromQueryString(request, allow = false) {
|
|
2025
|
+
if (!allow || request.method !== "GET") {
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
return resolveTokens(request.URL.searchParams);
|
|
2029
|
+
}
|
|
2030
|
+
async function resolveFromBody(exchange, allow = false) {
|
|
2031
|
+
const { request } = exchange;
|
|
2032
|
+
if (!allow || "application/x-www-form-urlencoded" !== request.headers.one("content-type") || request.method !== "POST") {
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
return resolveTokens(await exchange.request.formData);
|
|
2036
|
+
}
|
|
2037
|
+
var token_converter_default = serverBearerTokenAuthenticationConverter;
|
|
2038
|
+
|
|
2039
|
+
// src/server/security/oauth2/token-entry-point.ts
|
|
2040
|
+
function computeWWWAuthenticate(parameters) {
|
|
2041
|
+
let wwwAuthenticate = "Bearer";
|
|
2042
|
+
if (parameters.size !== 0) {
|
|
2043
|
+
wwwAuthenticate += " ";
|
|
2044
|
+
let i = 0;
|
|
2045
|
+
for (const [key, value] of parameters) {
|
|
2046
|
+
wwwAuthenticate += `${key}="${value}"`;
|
|
2047
|
+
if (i !== parameters.size - 1) {
|
|
2048
|
+
wwwAuthenticate += ", ";
|
|
2049
|
+
}
|
|
2050
|
+
i++;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return wwwAuthenticate;
|
|
2054
|
+
}
|
|
2055
|
+
var isBearerTokenError = (error) => {
|
|
2056
|
+
return error.httpStatus !== void 0;
|
|
2057
|
+
};
|
|
2058
|
+
function getStatus(authError) {
|
|
2059
|
+
if (authError instanceof Oauth2AuthenticationError) {
|
|
2060
|
+
const { error } = authError;
|
|
2061
|
+
if (isBearerTokenError(error)) {
|
|
2062
|
+
return error.httpStatus;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return 401;
|
|
2066
|
+
}
|
|
2067
|
+
function createParameters(authError, realm) {
|
|
2068
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
2069
|
+
if (realm) {
|
|
2070
|
+
parameters.set("realm", realm);
|
|
2071
|
+
}
|
|
2072
|
+
if (authError instanceof Oauth2AuthenticationError) {
|
|
2073
|
+
const { error } = authError;
|
|
2074
|
+
parameters.set("error", error.errorCode);
|
|
2075
|
+
if (error.description) {
|
|
2076
|
+
parameters.set("error_description", error.description);
|
|
2077
|
+
}
|
|
2078
|
+
if (error.uri) {
|
|
2079
|
+
parameters.set("error_uri", error.uri);
|
|
2080
|
+
}
|
|
2081
|
+
if (isBearerTokenError(error) && error.scope) {
|
|
2082
|
+
parameters.set("scope", error.scope);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return parameters;
|
|
2086
|
+
}
|
|
2087
|
+
var bearerTokenServerAuthenticationEntryPoint = (opts) => {
|
|
2088
|
+
return async (exchange, error) => {
|
|
2089
|
+
const status = getStatus(error);
|
|
2090
|
+
const parameters = createParameters(error, opts?.realmName);
|
|
2091
|
+
const wwwAuthenticate = computeWWWAuthenticate(parameters);
|
|
2092
|
+
const { response } = exchange;
|
|
2093
|
+
response.headers.set("WWW-Authenticate", wwwAuthenticate);
|
|
2094
|
+
response.statusCode = status;
|
|
2095
|
+
await response.end();
|
|
2096
|
+
};
|
|
2097
|
+
};
|
|
2098
|
+
var token_entry_point_default = bearerTokenServerAuthenticationEntryPoint;
|
|
2099
|
+
|
|
2100
|
+
// src/server/security/oauth2/jwt-auth-manager.ts
|
|
2101
|
+
var jwtAuthConverter = (opts) => {
|
|
2102
|
+
const principalClaimName = opts?.principalClaimName ?? "sub";
|
|
2103
|
+
return (jwt) => {
|
|
2104
|
+
const name = jwt.getClaimAsString(principalClaimName);
|
|
2105
|
+
return { type: "JwtToken", authenticated: true, name };
|
|
2106
|
+
};
|
|
2107
|
+
};
|
|
2108
|
+
var asyncJwtConverter = (converter) => {
|
|
2109
|
+
return async (jwt) => {
|
|
2110
|
+
return converter(jwt);
|
|
2111
|
+
};
|
|
2112
|
+
};
|
|
2113
|
+
var JwtError = class extends Error {
|
|
2114
|
+
};
|
|
2115
|
+
var BadJwtError = class extends JwtError {
|
|
2116
|
+
};
|
|
2117
|
+
function onError(error) {
|
|
2118
|
+
if (error instanceof BadJwtError) {
|
|
2119
|
+
return new Oauth2AuthenticationError(invalidToken(error.message), error.message, { cause: error });
|
|
2120
|
+
}
|
|
2121
|
+
throw new AuthenticationServiceError(error.message, { cause: error });
|
|
2122
|
+
}
|
|
2123
|
+
function jwtAuthManager(opts) {
|
|
2124
|
+
const decoder = opts.decoder;
|
|
2125
|
+
const authConverter = opts.authConverter ?? asyncJwtConverter(jwtAuthConverter({}));
|
|
2126
|
+
return async (authentication) => {
|
|
2127
|
+
if (isBearerTokenAuthenticationToken(authentication)) {
|
|
2128
|
+
const token = authentication.token;
|
|
2129
|
+
try {
|
|
2130
|
+
const jwt = await decoder(token);
|
|
2131
|
+
return await authConverter(jwt);
|
|
2132
|
+
} catch (e) {
|
|
2133
|
+
if (e instanceof JwtError) {
|
|
2134
|
+
throw onError(e);
|
|
2135
|
+
}
|
|
2136
|
+
throw e;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// src/server/security/oauth2-resource-server.ts
|
|
2143
|
+
function resourceServer(opts) {
|
|
2144
|
+
const entryPoint = opts.entryPoint ?? token_entry_point_default({});
|
|
2145
|
+
const converter = opts?.converter ?? token_converter_default({});
|
|
2146
|
+
const failureHandler = opts.failureHandler ?? serverAuthenticationEntryPointFailureHandler({ entryPoint });
|
|
2147
|
+
if (opts.managerResolver !== void 0) {
|
|
2148
|
+
return authenticationFilter({
|
|
2149
|
+
storage: opts.storage,
|
|
2150
|
+
converter,
|
|
2151
|
+
failureHandler,
|
|
2152
|
+
managerResolver: opts.managerResolver
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
if (opts.jwt !== void 0) {
|
|
2156
|
+
const manager = opts.jwt.manager ?? jwtAuthManager(opts.jwt);
|
|
2157
|
+
return authenticationFilter({
|
|
2158
|
+
storage: opts.storage,
|
|
2159
|
+
converter,
|
|
2160
|
+
failureHandler,
|
|
2161
|
+
managerResolver: async (_exchange) => {
|
|
2162
|
+
return manager;
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
throw new Error("Invalid resource server configuration: either managerResolver or jwt must be provided");
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// src/server/security/http-status-entry-point.ts
|
|
2170
|
+
var httpStatusEntryPoint = (opts) => {
|
|
2171
|
+
return async (exchange, _error) => {
|
|
2172
|
+
const response = exchange.response;
|
|
2173
|
+
response.statusCode = opts.httpStatus.code;
|
|
2174
|
+
response.statusMessage = opts.httpStatus.message;
|
|
2175
|
+
};
|
|
2176
|
+
};
|
|
2177
|
+
|
|
2178
|
+
// src/server/security/delegating-entry-point.ts
|
|
2179
|
+
var delegatingEntryPoint = (opts) => {
|
|
2180
|
+
const defaultEntryPoint = opts.defaultEntryPoint ?? (async ({ response }, _error) => {
|
|
2181
|
+
response.statusCode = 401;
|
|
2182
|
+
await response.end();
|
|
2183
|
+
});
|
|
2184
|
+
return async (exchange, error) => {
|
|
2185
|
+
for (const [matcher, entryPoint] of opts.entryPoints) {
|
|
2186
|
+
const match = await matcher(exchange);
|
|
2187
|
+
if (match.match) {
|
|
2188
|
+
return entryPoint(exchange, error);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
return defaultEntryPoint(exchange, error);
|
|
2192
|
+
};
|
|
2193
|
+
};
|
|
2194
|
+
|
|
2195
|
+
// src/server/security/delegating-success-handler.ts
|
|
2196
|
+
var delegatingSuccessHandler = (handlers) => {
|
|
2197
|
+
return async ({ exchange, next }, authentication) => {
|
|
2198
|
+
for (const handler2 of handlers) {
|
|
2199
|
+
await handler2({ exchange, next }, authentication);
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2204
|
+
// src/server/security/http-basic.ts
|
|
2205
|
+
function httpBasic(opts) {
|
|
2206
|
+
const xhrMatcher = async (exchange) => {
|
|
2207
|
+
const headers2 = exchange.request.headers;
|
|
2208
|
+
const h = headers2.list("X-Requested-With");
|
|
2209
|
+
if (h.includes("XMLHttpRequest")) {
|
|
2210
|
+
return { match: true };
|
|
2211
|
+
}
|
|
2212
|
+
return { match: false };
|
|
2213
|
+
};
|
|
2214
|
+
const defaultEntryPoint = delegatingEntryPoint({
|
|
2215
|
+
entryPoints: [[xhrMatcher, httpStatusEntryPoint({ httpStatus: { code: 401 } })]],
|
|
2216
|
+
defaultEntryPoint: httpBasicEntryPoint({})
|
|
2217
|
+
});
|
|
2218
|
+
const entryPoint = opts.entryPoint ?? defaultEntryPoint;
|
|
2219
|
+
const manager = opts.manager;
|
|
2220
|
+
const restMatcher = mediaType({
|
|
2221
|
+
mediaTypes: [
|
|
2222
|
+
"application/atom+xml",
|
|
2223
|
+
"application/x-www-form-urlencoded",
|
|
2224
|
+
"application/json",
|
|
2225
|
+
"application/octet-stream",
|
|
2226
|
+
"application/xml",
|
|
2227
|
+
"multipart/form-data",
|
|
2228
|
+
"text/xml"
|
|
2229
|
+
],
|
|
2230
|
+
ignoredMediaTypes: ["*/*"]
|
|
2231
|
+
});
|
|
2232
|
+
const notHtmlMatcher = not(mediaType({ mediaTypes: ["text/html"] }));
|
|
2233
|
+
const restNoHtmlMatcher = and([notHtmlMatcher, restMatcher]);
|
|
2234
|
+
const preferredMatcher = or([xhrMatcher, restNoHtmlMatcher]);
|
|
2235
|
+
opts.defaultEntryPoints.push([preferredMatcher, entryPoint]);
|
|
2236
|
+
const failureHandler = opts.failureHandler ?? serverAuthenticationEntryPointFailureHandler({ entryPoint });
|
|
2237
|
+
const successHandler = delegatingSuccessHandler(opts.successHandlers === void 0 ? opts.defaultSuccessHandlers : opts.successHandlers);
|
|
2238
|
+
const converter = httpBasicAuthenticationConverter({});
|
|
2239
|
+
return authenticationFilter({
|
|
2240
|
+
storage: opts.storage,
|
|
2241
|
+
manager,
|
|
2242
|
+
failureHandler,
|
|
2243
|
+
successHandler,
|
|
2244
|
+
converter
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// src/server/security/config.ts
|
|
2249
|
+
import { jwtVerifier } from "@interopio/gateway/jose/jwt";
|
|
2250
|
+
|
|
2251
|
+
// src/server/security/error-filter.ts
|
|
2252
|
+
async function commenceAuthentication(exchange, authentication, entryPoint) {
|
|
2253
|
+
const cause = new InsufficientAuthenticationError(`Full authentication is required to access this resource.`);
|
|
2254
|
+
const e = new AuthenticationError("Access Denied", { cause });
|
|
2255
|
+
if (authentication) {
|
|
2256
|
+
e.authentication = authentication;
|
|
2257
|
+
}
|
|
2258
|
+
await entryPoint(exchange, e);
|
|
2259
|
+
}
|
|
2260
|
+
function httpStatusAccessDeniedHandler(status, message) {
|
|
2261
|
+
return async (exchange, _error) => {
|
|
2262
|
+
exchange.response.statusCode = status;
|
|
2263
|
+
exchange.response.statusMessage = message;
|
|
2264
|
+
exchange.response.headers.set("Content-Type", "text/plain");
|
|
2265
|
+
const bytes = Buffer.from("Access Denied", "utf-8");
|
|
2266
|
+
exchange.response.headers.set("Content-Length", bytes.length);
|
|
2267
|
+
await exchange.response.end(bytes);
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
var errorFilter = (opts) => {
|
|
2271
|
+
const accessDeniedHandler = httpStatusAccessDeniedHandler(403, "Forbidden");
|
|
2272
|
+
const authenticationEntryPoint = opts.authenticationEntryPoint ?? httpBasicEntryPoint();
|
|
2273
|
+
return async (exchange, next) => {
|
|
2274
|
+
try {
|
|
2275
|
+
await next();
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
if (error instanceof AccessDeniedError) {
|
|
2278
|
+
const principal = await exchange.principal;
|
|
2279
|
+
if (principal === void 0) {
|
|
2280
|
+
await commenceAuthentication(exchange, void 0, authenticationEntryPoint);
|
|
2281
|
+
} else {
|
|
2282
|
+
if (!principal.authenticated) {
|
|
2283
|
+
return accessDeniedHandler(exchange, error);
|
|
2284
|
+
}
|
|
2285
|
+
await commenceAuthentication(exchange, principal, authenticationEntryPoint);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2292
|
+
// src/server/security/authorization-filter.ts
|
|
2293
|
+
function authorizationFilter(opts) {
|
|
2294
|
+
const { manager, storage } = opts;
|
|
2295
|
+
return async (exchange, next) => {
|
|
2296
|
+
const promise = AsyncStorageSecurityContextHolder.getContext(storage).then((c) => c?.authentication);
|
|
2297
|
+
try {
|
|
2298
|
+
await manager.verify(promise, exchange);
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
if (error instanceof AccessDeniedError) {
|
|
2301
|
+
}
|
|
2302
|
+
throw error;
|
|
2303
|
+
}
|
|
2304
|
+
await next();
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// src/server/security/delegating-authorization-manager.ts
|
|
2309
|
+
function delegatingAuthorizationManager(opts) {
|
|
2310
|
+
const check = async (authentication, exchange) => {
|
|
2311
|
+
let decision;
|
|
2312
|
+
for (const [matcher, manager] of opts.mappings) {
|
|
2313
|
+
if ((await matcher(exchange))?.match) {
|
|
2314
|
+
const checkResult = await manager.check(authentication, { exchange });
|
|
2315
|
+
if (checkResult !== void 0) {
|
|
2316
|
+
decision = checkResult;
|
|
2317
|
+
break;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
decision ??= new AuthorizationDecision(false);
|
|
2322
|
+
return decision;
|
|
2323
|
+
};
|
|
2324
|
+
return new DefaultAuthorizationManager(check);
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// src/server/security/config.ts
|
|
2328
|
+
var filterOrder = {
|
|
2329
|
+
first: Number.MAX_SAFE_INTEGER,
|
|
2330
|
+
http_headers: 1 * 100,
|
|
2331
|
+
https_redirect: 2 * 100,
|
|
2332
|
+
cors: 3 * 100,
|
|
2333
|
+
http_basic: 6 * 100,
|
|
2334
|
+
authentication: 8 * 100,
|
|
2335
|
+
error_translation: 18 * 100,
|
|
2336
|
+
authorization: 19 * 100,
|
|
2337
|
+
last: Number.MAX_SAFE_INTEGER
|
|
2338
|
+
};
|
|
2339
|
+
var filterOrderSymbol = Symbol.for("filterOrder");
|
|
2340
|
+
var config_default = (config, storage) => {
|
|
2341
|
+
const middleware = [];
|
|
2342
|
+
class ServerHttpSecurity {
|
|
2343
|
+
#authenticationEntryPoint;
|
|
2344
|
+
#defaultEntryPoints = [];
|
|
2345
|
+
manager;
|
|
2346
|
+
get authenticationEntryPoint() {
|
|
2347
|
+
if (this.#authenticationEntryPoint !== void 0 || this.#defaultEntryPoints.length === 0) {
|
|
2348
|
+
return this.#authenticationEntryPoint;
|
|
2349
|
+
}
|
|
2350
|
+
if (this.#defaultEntryPoints.length === 1) {
|
|
2351
|
+
return this.#defaultEntryPoints[0][1];
|
|
2352
|
+
}
|
|
2353
|
+
return delegatingEntryPoint({
|
|
2354
|
+
entryPoints: this.#defaultEntryPoints,
|
|
2355
|
+
defaultEntryPoint: this.#defaultEntryPoints[this.#defaultEntryPoints.length - 1][1]
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
build() {
|
|
2359
|
+
if (config.headers !== void 0 && config.headers.disabled !== true) {
|
|
2360
|
+
const writer = headers(config.headers);
|
|
2361
|
+
writer[filterOrderSymbol] = filterOrder.http_headers;
|
|
2362
|
+
middleware.push(writer);
|
|
2363
|
+
}
|
|
2364
|
+
if (config.basic !== void 0 && config.basic?.disabled !== true) {
|
|
2365
|
+
const username = config.basic.user?.name.toLowerCase();
|
|
2366
|
+
const password = config.basic.user?.password ?? "";
|
|
2367
|
+
const authorities = config.basic.user?.authorities ?? [];
|
|
2368
|
+
const manager = async (auth) => {
|
|
2369
|
+
const principal = auth["principal"];
|
|
2370
|
+
const credentials = auth["credentials"];
|
|
2371
|
+
if (principal.toLowerCase() !== username || credentials !== password) {
|
|
2372
|
+
throw new BadCredentialsError("Invalid username or password");
|
|
2373
|
+
}
|
|
2374
|
+
return { type: "UsernamePassword", authenticated: true, principal, credentials, authorities: [...authorities] };
|
|
2375
|
+
};
|
|
2376
|
+
const defaultSuccessHandlers = [
|
|
2377
|
+
async ({ exchange: _, next }, _authentication) => {
|
|
2378
|
+
return next();
|
|
2379
|
+
}
|
|
2380
|
+
];
|
|
2381
|
+
const filter = httpBasic({
|
|
2382
|
+
storage,
|
|
2383
|
+
manager,
|
|
2384
|
+
defaultEntryPoints: this.#defaultEntryPoints,
|
|
2385
|
+
defaultSuccessHandlers
|
|
2386
|
+
});
|
|
2387
|
+
filter[filterOrderSymbol] = filterOrder.http_basic;
|
|
2388
|
+
middleware.push(filter);
|
|
2389
|
+
}
|
|
2390
|
+
if (config.jwt !== void 0 && config.jwt.disabled !== true) {
|
|
2391
|
+
const verifier = jwtVerifier({
|
|
2392
|
+
issuerBaseUri: config.jwt.issuerBaseUri,
|
|
2393
|
+
issuer: config.jwt.issuer,
|
|
2394
|
+
audience: config.jwt.audience
|
|
2395
|
+
});
|
|
2396
|
+
const decoder = async (token) => {
|
|
2397
|
+
const { payload } = await verifier(token);
|
|
2398
|
+
return {
|
|
2399
|
+
subject: payload.sub,
|
|
2400
|
+
getClaimAsString(claim) {
|
|
2401
|
+
return payload[claim];
|
|
2402
|
+
}
|
|
2403
|
+
};
|
|
2404
|
+
};
|
|
2405
|
+
const filter = resourceServer({ storage, jwt: { decoder } });
|
|
2406
|
+
filter[filterOrderSymbol] = filterOrder.authentication;
|
|
2407
|
+
middleware.push(filter);
|
|
2408
|
+
}
|
|
2409
|
+
if (config.authorize !== void 0) {
|
|
2410
|
+
const errorFf = errorFilter({ authenticationEntryPoint: this.authenticationEntryPoint });
|
|
2411
|
+
errorFf[filterOrderSymbol] = filterOrder.error_translation;
|
|
2412
|
+
middleware.push(errorFf);
|
|
2413
|
+
const buildAuthorizationManager = (authorize) => {
|
|
2414
|
+
const mappings = [];
|
|
2415
|
+
let anyExchangeRegistered = false;
|
|
2416
|
+
for (const [matcher, access2] of authorize ?? []) {
|
|
2417
|
+
let serverMatcher;
|
|
2418
|
+
if (matcher === "any-exchange") {
|
|
2419
|
+
anyExchangeRegistered = true;
|
|
2420
|
+
serverMatcher = anyExchange;
|
|
2421
|
+
} else if (anyExchangeRegistered) {
|
|
2422
|
+
throw new Error("Cannot register other matchers after 'any-exchange' matcher");
|
|
2423
|
+
} else {
|
|
2424
|
+
serverMatcher = matcher;
|
|
2425
|
+
}
|
|
2426
|
+
let manager2;
|
|
2427
|
+
if (access2.access === "permit-all") {
|
|
2428
|
+
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(true));
|
|
2429
|
+
} else if (access2.access === "deny-all") {
|
|
2430
|
+
manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(false));
|
|
2431
|
+
} else if (access2.access === "authenticated") {
|
|
2432
|
+
manager2 = new DefaultAuthorizationManager(async (p) => {
|
|
2433
|
+
const authentication = await p;
|
|
2434
|
+
if (authentication !== void 0) {
|
|
2435
|
+
return new AuthorizationDecision(authentication.authenticated);
|
|
2436
|
+
}
|
|
2437
|
+
return new AuthorizationDecision(false);
|
|
2438
|
+
});
|
|
2439
|
+
} else {
|
|
2440
|
+
throw new Error(`Unknown access type: ${JSON.stringify(access2)}`);
|
|
2441
|
+
}
|
|
2442
|
+
mappings.push([serverMatcher, manager2]);
|
|
2443
|
+
}
|
|
2444
|
+
return delegatingAuthorizationManager({ mappings });
|
|
2445
|
+
};
|
|
2446
|
+
const manager = buildAuthorizationManager(config.authorize);
|
|
2447
|
+
const filter = authorizationFilter({ manager, storage });
|
|
2448
|
+
filter[filterOrderSymbol] = filterOrder.authorization;
|
|
2449
|
+
middleware.push(filter);
|
|
2450
|
+
}
|
|
2451
|
+
middleware.sort((a, b) => {
|
|
2452
|
+
const aOrder = a[filterOrderSymbol] ?? filterOrder.last;
|
|
2453
|
+
const bOrder = b[filterOrderSymbol] ?? filterOrder.last;
|
|
2454
|
+
return aOrder - bOrder;
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
const security = new ServerHttpSecurity();
|
|
2459
|
+
security.build();
|
|
2460
|
+
return middleware;
|
|
2461
|
+
};
|
|
2462
|
+
|
|
1420
2463
|
// src/server.ts
|
|
1421
2464
|
var logger7 = getLogger("app");
|
|
1422
2465
|
function secureContextOptions(ssl) {
|
|
@@ -1426,20 +2469,81 @@ function secureContextOptions(ssl) {
|
|
|
1426
2469
|
if (ssl.ca) options.ca = readFileSync(ssl.ca);
|
|
1427
2470
|
return options;
|
|
1428
2471
|
}
|
|
1429
|
-
function createListener(middleware, routes3) {
|
|
1430
|
-
const storage = new AsyncLocalStorage();
|
|
2472
|
+
function createListener(storage, middleware, routes3, onSocketError) {
|
|
1431
2473
|
const listener = compose(
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
2474
|
+
server_header_default(),
|
|
2475
|
+
...config_default({
|
|
2476
|
+
authorize: [
|
|
2477
|
+
[async (exchange) => {
|
|
2478
|
+
return { match: exchange.path === "/health" && exchange.method === "GET" };
|
|
2479
|
+
}, { access: "permit-all" }],
|
|
2480
|
+
// ['any-exchange', {access: 'authenticated'}],
|
|
2481
|
+
["any-exchange", { access: "permit-all" }]
|
|
2482
|
+
],
|
|
2483
|
+
basic: {
|
|
2484
|
+
disabled: true,
|
|
2485
|
+
realm: "Gateway Server"
|
|
2486
|
+
},
|
|
2487
|
+
jwt: {
|
|
2488
|
+
disabled: true
|
|
2489
|
+
}
|
|
2490
|
+
}, storage),
|
|
1436
2491
|
...cors_default({
|
|
1437
|
-
origins: { allow: [/http:\/\/localhost(:\d+)
|
|
2492
|
+
origins: { allow: [/http:\/\/localhost(:\d+)?/, /file:\//] },
|
|
1438
2493
|
methods: { allow: ["GET", "HEAD", "POST", "DELETE"] },
|
|
1439
2494
|
headers: { allow: "*" },
|
|
1440
2495
|
credentials: { allow: true }
|
|
1441
2496
|
}),
|
|
1442
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") {
|
|
2507
|
+
const socket = request.socket;
|
|
2508
|
+
const host = request.host;
|
|
2509
|
+
const info = socketKey(request._req.socket);
|
|
2510
|
+
if (route?.wss) {
|
|
2511
|
+
socket.removeListener("error", onSocketError);
|
|
2512
|
+
const wss = route.wss;
|
|
2513
|
+
if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
|
|
2514
|
+
logger7.warn(`${info} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
|
|
2515
|
+
socket.destroy();
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
const origin = request.headers["origin"];
|
|
2519
|
+
if (!acceptsOrigin(origin, route.originFilters)) {
|
|
2520
|
+
logger7.info(`${info} dropping ws connection request from ${host} on ${path}. origin ${origin ?? "<missing>"}`);
|
|
2521
|
+
socket.destroy();
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
if (logger7.enabledFor("debug")) {
|
|
2525
|
+
logger7.debug(`${info} accepted new ws connection request from ${host} on ${path}`);
|
|
2526
|
+
}
|
|
2527
|
+
wss.handleUpgrade(request._req, socket, request._req["_upgradeHead"], (ws) => {
|
|
2528
|
+
response._res["_header"] = true;
|
|
2529
|
+
ws.on("pong", () => ws["connected"] = true);
|
|
2530
|
+
ws.on("ping", () => {
|
|
2531
|
+
});
|
|
2532
|
+
wss.emit("connection", ws, request._req);
|
|
2533
|
+
});
|
|
2534
|
+
} else {
|
|
2535
|
+
logger7.warn(`${info} rejected upgrade request from ${host} on ${path}`);
|
|
2536
|
+
socket.destroy();
|
|
2537
|
+
}
|
|
2538
|
+
} else {
|
|
2539
|
+
response.statusCode = 426;
|
|
2540
|
+
response._res.appendHeader("Upgrade", "websocket").appendHeader("Connection", "Upgrade").appendHeader("Content-Type", "text/plain");
|
|
2541
|
+
await response.end(`This service [${request.path}] requires use of the websocket protocol.`);
|
|
2542
|
+
}
|
|
2543
|
+
} else {
|
|
2544
|
+
await next();
|
|
2545
|
+
}
|
|
2546
|
+
},
|
|
1443
2547
|
async ({ request, response }, next) => {
|
|
1444
2548
|
if (request.method === "GET" && request.path === "/health") {
|
|
1445
2549
|
response.statusCode = 200;
|
|
@@ -1450,33 +2554,35 @@ function createListener(middleware, routes3) {
|
|
|
1450
2554
|
},
|
|
1451
2555
|
async ({ request, response }, next) => {
|
|
1452
2556
|
if (request.method === "GET" && request.path === "/") {
|
|
1453
|
-
response.
|
|
2557
|
+
await response.end(`io.Gateway Server`);
|
|
1454
2558
|
} else {
|
|
1455
2559
|
await next();
|
|
1456
2560
|
}
|
|
1457
2561
|
},
|
|
1458
2562
|
async ({ request, response }, _next) => {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
response.statusCode = 426;
|
|
1462
|
-
response._res.appendHeader("Upgrade", "websocket").appendHeader("Connection", "Upgrade").appendHeader("Content-Type", "text/plain");
|
|
1463
|
-
response._res.end(`This service [${request.path}] requires use of the websocket protocol.`);
|
|
1464
|
-
} else {
|
|
1465
|
-
response.statusCode = 404;
|
|
1466
|
-
response._res.end(http.STATUS_CODES[404]);
|
|
1467
|
-
}
|
|
2563
|
+
response.statusCode = 404;
|
|
2564
|
+
await response.end(http.STATUS_CODES[404]);
|
|
1468
2565
|
}
|
|
1469
2566
|
);
|
|
1470
2567
|
return (request, response) => {
|
|
2568
|
+
request.socket.addListener("error", onSocketError);
|
|
1471
2569
|
const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
|
|
1472
|
-
return storage.run(exchange, async () => {
|
|
2570
|
+
return storage.run({ exchange }, async () => {
|
|
1473
2571
|
if (logger7.enabledFor("debug")) {
|
|
1474
2572
|
const socket = exchange.request._req.socket;
|
|
1475
2573
|
if (logger7.enabledFor("debug")) {
|
|
1476
2574
|
logger7.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
|
|
1477
2575
|
}
|
|
1478
2576
|
}
|
|
1479
|
-
|
|
2577
|
+
try {
|
|
2578
|
+
return await listener(exchange);
|
|
2579
|
+
} catch (e) {
|
|
2580
|
+
if (logger7.enabledFor("warn")) {
|
|
2581
|
+
logger7.warn(`error processing request for ${exchange.path}`, e);
|
|
2582
|
+
}
|
|
2583
|
+
} finally {
|
|
2584
|
+
await exchange.response.end();
|
|
2585
|
+
}
|
|
1480
2586
|
});
|
|
1481
2587
|
};
|
|
1482
2588
|
}
|
|
@@ -1535,12 +2641,11 @@ var Factory = async (options) => {
|
|
|
1535
2641
|
}
|
|
1536
2642
|
const ports = portRange(options.port ?? 0);
|
|
1537
2643
|
const host = options.host;
|
|
2644
|
+
const storage = new AsyncLocalStorage2();
|
|
1538
2645
|
const serverP = new Promise((resolve, reject) => {
|
|
1539
2646
|
const onSocketError = (err) => logger7.error(`socket error: ${err}`, err);
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
createListener(middleware, routes3)
|
|
1543
|
-
);
|
|
2647
|
+
const listener = createListener(storage, middleware, routes3, onSocketError);
|
|
2648
|
+
const server2 = createServer({}, listener);
|
|
1544
2649
|
server2.on("error", (e) => {
|
|
1545
2650
|
if (e["code"] === "EADDRINUSE") {
|
|
1546
2651
|
logger7.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
|
|
@@ -1566,7 +2671,7 @@ var Factory = async (options) => {
|
|
|
1566
2671
|
logger7.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
|
|
1567
2672
|
const wss = new WebSocketServer({ noServer: true });
|
|
1568
2673
|
const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info.port}${path}`;
|
|
1569
|
-
const handler2 = await route.factory({ endpoint, wss });
|
|
2674
|
+
const handler2 = await route.factory({ endpoint, wss, storage });
|
|
1570
2675
|
const pingInterval = route.ping;
|
|
1571
2676
|
if (pingInterval) {
|
|
1572
2677
|
const pingIntervalId = setInterval(() => {
|
|
@@ -1594,42 +2699,10 @@ var Factory = async (options) => {
|
|
|
1594
2699
|
server2.on("upgrade", (req, socket, head) => {
|
|
1595
2700
|
socket.addListener("error", onSocketError);
|
|
1596
2701
|
try {
|
|
1597
|
-
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
return true;
|
|
1602
|
-
}
|
|
1603
|
-
});
|
|
1604
|
-
const host2 = request.host;
|
|
1605
|
-
const info = socketKey(request.socket);
|
|
1606
|
-
if (route?.wss) {
|
|
1607
|
-
socket.removeListener("error", onSocketError);
|
|
1608
|
-
const wss = route.wss;
|
|
1609
|
-
if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
|
|
1610
|
-
logger7.warn(`${info} dropping ws connection request from ${host2} on ${path}. max connections exceeded.`);
|
|
1611
|
-
socket.destroy();
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
const origin = request.headers["origin"];
|
|
1615
|
-
if (!acceptsOrigin(origin, route.originFilters)) {
|
|
1616
|
-
logger7.info(`${info} dropping ws connection request from ${host2} on ${path}. origin ${origin ?? "<missing>"}`);
|
|
1617
|
-
socket.destroy();
|
|
1618
|
-
return;
|
|
1619
|
-
}
|
|
1620
|
-
if (logger7.enabledFor("debug")) {
|
|
1621
|
-
logger7.debug(`${info} accepted new ws connection request from ${host2} on ${path}`);
|
|
1622
|
-
}
|
|
1623
|
-
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
1624
|
-
ws.on("pong", () => ws["connected"] = true);
|
|
1625
|
-
ws.on("ping", () => {
|
|
1626
|
-
});
|
|
1627
|
-
wss.emit("connection", ws, req);
|
|
1628
|
-
});
|
|
1629
|
-
} else {
|
|
1630
|
-
logger7.warn(`${info} rejected upgrade request from ${host2} on ${path}`);
|
|
1631
|
-
socket.destroy();
|
|
1632
|
-
}
|
|
2702
|
+
req._upgradeHead = head;
|
|
2703
|
+
const res = new http.ServerResponse(req);
|
|
2704
|
+
res.assignSocket(socket);
|
|
2705
|
+
listener(req, res);
|
|
1633
2706
|
} catch (err) {
|
|
1634
2707
|
logger7.error(`upgrade error: ${err}`, err);
|
|
1635
2708
|
}
|