@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.
Files changed (42) hide show
  1. package/changelog.md +18 -0
  2. package/dist/gateway-ent.cjs +25 -2
  3. package/dist/gateway-ent.cjs.map +2 -2
  4. package/dist/gateway-ent.js +25 -2
  5. package/dist/gateway-ent.js.map +2 -2
  6. package/dist/index.cjs +1182 -109
  7. package/dist/index.cjs.map +4 -4
  8. package/dist/index.js +1182 -109
  9. package/dist/index.js.map +4 -4
  10. package/dist/metrics/publisher/rest.cjs +60 -0
  11. package/dist/metrics/publisher/rest.cjs.map +7 -0
  12. package/dist/metrics/publisher/rest.js +23 -0
  13. package/dist/metrics/publisher/rest.js.map +7 -0
  14. package/package.json +12 -12
  15. package/types/gateway-ent.d.ts +2 -1
  16. package/dist/metrics-rest.cjs +0 -21440
  17. package/dist/metrics-rest.cjs.map +0 -7
  18. package/dist/metrics-rest.js +0 -21430
  19. package/dist/metrics-rest.js.map +0 -7
  20. package/src/common/compose.ts +0 -40
  21. package/src/gateway/ent/config.ts +0 -174
  22. package/src/gateway/ent/index.ts +0 -18
  23. package/src/gateway/ent/logging.ts +0 -89
  24. package/src/gateway/ent/server.ts +0 -34
  25. package/src/gateway/metrics/rest.ts +0 -20
  26. package/src/gateway/ws/core.ts +0 -90
  27. package/src/index.ts +0 -3
  28. package/src/logger.ts +0 -6
  29. package/src/mesh/connections.ts +0 -101
  30. package/src/mesh/rest-directory/routes.ts +0 -38
  31. package/src/mesh/ws/broker/core.ts +0 -163
  32. package/src/mesh/ws/cluster/core.ts +0 -107
  33. package/src/mesh/ws/relays/core.ts +0 -159
  34. package/src/metrics/routes.ts +0 -86
  35. package/src/server/address.ts +0 -47
  36. package/src/server/cors.ts +0 -311
  37. package/src/server/exchange.ts +0 -379
  38. package/src/server/monitoring.ts +0 -167
  39. package/src/server/types.ts +0 -69
  40. package/src/server/ws-client-verify.ts +0 -79
  41. package/src/server.ts +0 -316
  42. package/src/utils.ts +0 -10
package/dist/index.cjs CHANGED
@@ -44,7 +44,7 @@ var import_ws = require("ws");
44
44
  var import_node_http = __toESM(require("node:http"), 1);
45
45
  var import_node_https = __toESM(require("node:https"), 1);
46
46
  var import_node_fs = require("node:fs");
47
- var import_node_async_hooks = require("node:async_hooks");
47
+ var import_node_async_hooks2 = require("node:async_hooks");
48
48
  var import_gateway7 = require("@interopio/gateway");
49
49
 
50
50
  // src/logger.ts
@@ -73,6 +73,7 @@ var WebExchange = class {
73
73
  };
74
74
 
75
75
  // src/server/exchange.ts
76
+ var import_tough_cookie = require("tough-cookie");
76
77
  function requestToProtocol(request, defaultProtocol) {
77
78
  let proto = request.headers.get("x-forwarded-proto");
78
79
  if (Array.isArray(proto)) {
@@ -96,9 +97,7 @@ function requestToHost(request, defaultHost) {
96
97
  host = `${host}:${port}`;
97
98
  }
98
99
  }
99
- if (host === void 0) {
100
- host = request.headers.one("host");
101
- }
100
+ host ??= request.headers.one("host");
102
101
  }
103
102
  if (Array.isArray(host)) {
104
103
  host = host[0];
@@ -108,14 +107,22 @@ function requestToHost(request, defaultHost) {
108
107
  }
109
108
  return defaultHost;
110
109
  }
110
+ function parseCookies(request) {
111
+ return request.headers.list("cookie").map((s) => s.split(";").map((cs) => import_tough_cookie.Cookie.parse(cs))).flat(1).filter((tc) => tc !== void 0).map((tc) => {
112
+ const result = { name: tc.key, value: tc.value };
113
+ return result;
114
+ });
115
+ }
111
116
  var HttpServerRequest = class {
112
- constructor(_req) {
113
- this._req = _req;
114
- this._headers = new IncomingMessageHeaders(_req);
115
- }
116
117
  _body;
117
118
  _url;
119
+ _cookies;
118
120
  _headers;
121
+ _req;
122
+ constructor(req) {
123
+ this._req = req;
124
+ this._headers = new IncomingMessageHeaders(this._req);
125
+ }
119
126
  get http2() {
120
127
  return this._req.httpVersionMajor >= 2;
121
128
  }
@@ -140,9 +147,7 @@ var HttpServerRequest = class {
140
147
  if (this._req.httpVersionMajor >= 2) {
141
148
  dh = (this._req?.headers)[":authority"];
142
149
  }
143
- if (dh === void 0) {
144
- dh = this._req?.socket.remoteAddress;
145
- }
150
+ dh ??= this._req?.socket.remoteAddress;
146
151
  return requestToHost(this, dh);
147
152
  }
148
153
  get protocol() {
@@ -150,14 +155,16 @@ var HttpServerRequest = class {
150
155
  if (this._req.httpVersionMajor > 2) {
151
156
  dp = this._req.headers[":scheme"];
152
157
  }
153
- if (dp === void 0) {
154
- dp = this._req?.socket["encrypted"] ? "https" : "http";
155
- }
158
+ dp ??= this._req?.socket["encrypted"] ? "https" : "http";
156
159
  return requestToProtocol(this, dp);
157
160
  }
158
161
  get socket() {
159
162
  return this._req.socket;
160
163
  }
164
+ get cookies() {
165
+ this._cookies ??= parseCookies(this);
166
+ return this._cookies;
167
+ }
161
168
  get body() {
162
169
  this._body ??= new Promise((resolve, reject) => {
163
170
  const chunks = [];
@@ -170,6 +177,13 @@ var HttpServerRequest = class {
170
177
  get text() {
171
178
  return this.body.then(async (blob) => await blob.text());
172
179
  }
180
+ get formData() {
181
+ return this.body.then(async (blob) => {
182
+ const text = await blob.text();
183
+ const formData = new URLSearchParams(text);
184
+ return formData;
185
+ });
186
+ }
173
187
  get json() {
174
188
  return this.body.then(async (blob) => {
175
189
  const json = JSON.parse(await blob.text());
@@ -202,8 +216,9 @@ var IncomingMessageHeaders = class {
202
216
  }
203
217
  };
204
218
  var OutgoingMessageHeaders = class {
205
- constructor(_msg) {
206
- this._msg = _msg;
219
+ _msg;
220
+ constructor(msg) {
221
+ this._msg = msg;
207
222
  }
208
223
  has(name) {
209
224
  return this._msg.hasHeader(name);
@@ -248,11 +263,12 @@ var OutgoingMessageHeaders = class {
248
263
  }
249
264
  };
250
265
  var HttpServerResponse = class {
251
- constructor(_res) {
252
- this._res = _res;
253
- this._headers = new OutgoingMessageHeaders(_res);
254
- }
255
266
  _headers;
267
+ _res;
268
+ constructor(res) {
269
+ this._res = res;
270
+ this._headers = new OutgoingMessageHeaders(res);
271
+ }
256
272
  get statusCode() {
257
273
  return this._res.statusCode;
258
274
  }
@@ -262,9 +278,57 @@ var HttpServerResponse = class {
262
278
  }
263
279
  this._res.statusCode = value;
264
280
  }
281
+ set statusMessage(value) {
282
+ this._res.statusMessage = value;
283
+ }
265
284
  get headers() {
266
285
  return this._headers;
267
286
  }
287
+ get cookies() {
288
+ return this.headers.list("set-cookie").map((cookie) => {
289
+ const parsed = import_tough_cookie.Cookie.parse(cookie);
290
+ if (parsed) {
291
+ const result = { name: parsed.key, value: parsed.value, maxAge: Number(parsed.maxAge ?? -1) };
292
+ if (parsed.httpOnly) result.httpOnly = true;
293
+ if (parsed.domain) result.domain = parsed.domain;
294
+ if (parsed.path) result.path = parsed.path;
295
+ if (parsed.secure) result.secure = true;
296
+ if (parsed.httpOnly) result.httpOnly = true;
297
+ if (parsed.sameSite) result.sameSite = parsed.sameSite;
298
+ return result;
299
+ }
300
+ }).filter((cookie) => cookie !== void 0);
301
+ }
302
+ end(chunk) {
303
+ if (!this._res.headersSent) {
304
+ return new Promise((resolve, reject) => {
305
+ if (chunk === void 0) {
306
+ this._res.end(() => {
307
+ resolve(true);
308
+ });
309
+ } else {
310
+ this._res.end(chunk, () => {
311
+ resolve(true);
312
+ });
313
+ }
314
+ });
315
+ } else {
316
+ return Promise.resolve(false);
317
+ }
318
+ }
319
+ addCookie(cookie) {
320
+ this.headers.add("set-cookie", new import_tough_cookie.Cookie({
321
+ key: cookie.name,
322
+ value: cookie.value,
323
+ maxAge: cookie.maxAge,
324
+ domain: cookie.domain,
325
+ path: cookie.path,
326
+ secure: cookie.secure,
327
+ httpOnly: cookie.httpOnly,
328
+ sameSite: cookie.sameSite
329
+ }).toString());
330
+ return this;
331
+ }
268
332
  };
269
333
  var DefaultWebExchange = class extends WebExchange {
270
334
  constructor(request, response) {
@@ -272,6 +336,9 @@ var DefaultWebExchange = class extends WebExchange {
272
336
  this.request = request;
273
337
  this.response = response;
274
338
  }
339
+ get principal() {
340
+ return Promise.resolve(void 0);
341
+ }
275
342
  };
276
343
  function toList(values) {
277
344
  if (typeof values === "string") {
@@ -315,17 +382,61 @@ function parseHeader(value) {
315
382
  }
316
383
  return list;
317
384
  }
385
+ var MapHttpHeaders = class extends Map {
386
+ get(name) {
387
+ return super.get(name.toLowerCase());
388
+ }
389
+ one(name) {
390
+ return this.get(name)?.[0];
391
+ }
392
+ list(name) {
393
+ const values = super.get(name.toLowerCase());
394
+ return toList(values);
395
+ }
396
+ set(name, value) {
397
+ if (typeof value === "number") {
398
+ value = String(value);
399
+ }
400
+ if (typeof value === "string") {
401
+ value = [value];
402
+ }
403
+ if (value) {
404
+ return super.set(name.toLowerCase(), value);
405
+ } else {
406
+ super.delete(name.toLowerCase());
407
+ return this;
408
+ }
409
+ }
410
+ add(name, value) {
411
+ const prev = super.get(name.toLowerCase());
412
+ if (typeof value === "string") {
413
+ value = [value];
414
+ }
415
+ if (prev) {
416
+ value = prev.concat(value);
417
+ }
418
+ this.set(name, value);
419
+ return this;
420
+ }
421
+ };
318
422
 
319
423
  // src/gateway/ws/core.ts
320
424
  var import_gateway2 = require("@interopio/gateway");
321
425
  var GatewayEncoders = import_gateway2.IOGateway.Encoding;
322
426
  var log = getLogger("ws");
323
427
  var codec = GatewayEncoders.json();
324
- function initClient(key, socket, host) {
428
+ function initClient(key, socket, host, securityContextPromise) {
325
429
  const opts = {
326
430
  key,
327
431
  host,
328
432
  codec,
433
+ onAuthenticate: async () => {
434
+ const authentication = (await securityContextPromise)?.authentication;
435
+ if (authentication?.authenticated) {
436
+ return { type: "success", user: authentication.name };
437
+ }
438
+ throw new Error(`no valid client authentication ${key}`);
439
+ },
329
440
  onPing: () => {
330
441
  socket.ping((err) => {
331
442
  if (err) {
@@ -362,10 +473,11 @@ async function create(server) {
362
473
  log.error("error starting the gateway websocket server", err);
363
474
  }).on("connection", (socket, req) => {
364
475
  const request = new HttpServerRequest(req);
476
+ const securityContextPromise = server.storage?.getStore()?.securityContext;
365
477
  const key = socketKey(request.socket);
366
478
  const host = request.host;
367
479
  log.info(`${key} connected on gw from ${host}`);
368
- const client = initClient.call(this, key, socket);
480
+ const client = initClient.call(this, key, socket, host, securityContextPromise);
369
481
  if (!client) {
370
482
  log.error(`${key} gw client init failed`);
371
483
  socket.terminate();
@@ -867,9 +979,8 @@ var logger5 = getLogger("metrics");
867
979
  var COOKIE_NAME = "GW_LOGIN";
868
980
  function loggedIn(auth, ctx) {
869
981
  if (auth) {
870
- const cookieHeaderValue = ctx.request.headers.list("cookie");
871
- const cookie = cookieHeaderValue?.join("; ").split("; ").find((value) => value.startsWith(`${COOKIE_NAME}=`));
872
- return cookie && parseInt(cookie?.substring(COOKIE_NAME.length + 1)) > Date.now();
982
+ const value = ctx.request.cookies.find((cookie) => cookie.name === COOKIE_NAME)?.value;
983
+ return value && parseInt(value) > Date.now();
873
984
  }
874
985
  return true;
875
986
  }
@@ -895,7 +1006,7 @@ async function routes2(config) {
895
1006
  if (ctx.method === "GET" && ctx.path === "/api/login") {
896
1007
  const redirectTo = new URLSearchParams(ctx.request.query ?? void 0).get("redirectTo");
897
1008
  const expires = Date.now() + 180 * 1e3;
898
- ctx.response.headers.add("set-cookie", `${COOKIE_NAME}=${expires}; Path=/api; SameSite=strict`);
1009
+ ctx.response.addCookie({ name: COOKIE_NAME, value: `${expires}`, maxAge: Infinity, path: "/api", sameSite: "strict" });
899
1010
  if (redirectTo) {
900
1011
  ctx.response.statusCode = 302;
901
1012
  ctx.response.headers.set("location", redirectTo);
@@ -941,30 +1052,38 @@ function compose(...middleware) {
941
1052
  if (!Array.isArray(middleware)) {
942
1053
  throw new Error("middleware must be array!");
943
1054
  }
944
- for (const fn of middleware) {
1055
+ const fns = middleware.flat();
1056
+ for (const fn of fns) {
945
1057
  if (typeof fn !== "function") {
946
1058
  throw new Error("middleware must be compose of functions!");
947
1059
  }
948
1060
  }
949
1061
  return async function(ctx, next) {
950
- let index = -1;
951
- return await dispatch(0);
952
- async function dispatch(i) {
953
- if (i < index) {
954
- throw new Error("next() called multiple times");
955
- }
956
- index = i;
957
- let fn;
958
- if (i === middleware.length) {
959
- fn = next;
960
- } else {
961
- fn = middleware[i];
962
- }
963
- if (!fn) {
1062
+ const dispatch = async (i) => {
1063
+ const fn = i === fns.length ? next : fns[i];
1064
+ if (fn === void 0) {
964
1065
  return;
965
1066
  }
966
- await fn(ctx, dispatch.bind(null, i + 1));
967
- }
1067
+ let nextCalled = false;
1068
+ let nextResolved = false;
1069
+ const nextFn = async () => {
1070
+ if (nextCalled) {
1071
+ throw new Error("next() called multiple times");
1072
+ }
1073
+ nextCalled = true;
1074
+ try {
1075
+ return await dispatch(i + 1);
1076
+ } finally {
1077
+ nextResolved = true;
1078
+ }
1079
+ };
1080
+ const result = await fn(ctx, nextFn);
1081
+ if (nextCalled && !nextResolved) {
1082
+ throw new Error("middleware resolved before downstream.\n You are probably missing an await or return statement in your middleware function.");
1083
+ }
1084
+ return result;
1085
+ };
1086
+ return dispatch(0);
968
1087
  };
969
1088
  }
970
1089
 
@@ -1283,9 +1402,9 @@ function processRequest(exchange, config) {
1283
1402
  }
1284
1403
  function validateConfig(config) {
1285
1404
  if (config) {
1286
- const headers = config.headers;
1287
- if (headers?.allow && headers.allow !== ALL) {
1288
- headers.allow = headers.allow.map((header) => header.toLowerCase());
1405
+ const headers2 = config.headers;
1406
+ if (headers2?.allow && headers2.allow !== ALL) {
1407
+ headers2.allow = headers2.allow.map((header) => header.toLowerCase());
1289
1408
  }
1290
1409
  const origins = config.origins;
1291
1410
  if (origins?.allow && origins.allow !== ALL) {
@@ -1304,7 +1423,7 @@ var handler = (config) => {
1304
1423
  return async (ctx, next) => {
1305
1424
  const isValid = processRequest(ctx, config);
1306
1425
  if (!isValid || isPreFlightRequest(ctx.request)) {
1307
- ctx.response._res.end();
1426
+ await ctx.response.end();
1308
1427
  } else {
1309
1428
  await next();
1310
1429
  }
@@ -1444,10 +1563,934 @@ function getMethodToUse(request, isPreFlight) {
1444
1563
  return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
1445
1564
  }
1446
1565
  function getHeadersToUse(request, isPreFlight) {
1447
- const headers = request.headers;
1448
- return isPreFlight ? headers.list("access-control-request-headers") : Array.from(headers.keys());
1566
+ const headers2 = request.headers;
1567
+ return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
1568
+ }
1569
+
1570
+ // src/server/server-header.ts
1571
+ var serverHeader = (server) => {
1572
+ return async ({ response }, next) => {
1573
+ if (!response.headers.has("server")) {
1574
+ response.headers.set("Server", server);
1575
+ }
1576
+ await next();
1577
+ };
1578
+ };
1579
+ var server_header_default = (server = "gateway-server") => serverHeader(server);
1580
+
1581
+ // src/server/security/http-headers.ts
1582
+ var staticServerHttpHeadersWriter = (headers2) => {
1583
+ return async (exchange) => {
1584
+ let containsNoHeaders = true;
1585
+ const { response } = exchange;
1586
+ for (const name of headers2.keys()) {
1587
+ if (response.headers.has(name)) {
1588
+ containsNoHeaders = false;
1589
+ }
1590
+ }
1591
+ if (containsNoHeaders) {
1592
+ for (const [name, value] of headers2) {
1593
+ response.headers.set(name, value);
1594
+ }
1595
+ }
1596
+ };
1597
+ };
1598
+ var cacheControlServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
1599
+ new MapHttpHeaders().add("cache-control", "no-cache, no-store, max-age=0, must-revalidate").add("pragma", "no-cache").add("expires", "0")
1600
+ );
1601
+ var contentTypeServerHttpHeadersWriter = () => staticServerHttpHeadersWriter(
1602
+ new MapHttpHeaders().add("x-content-type-options", "nosniff")
1603
+ );
1604
+ var strictTransportSecurityServerHttpHeadersWriter = (maxAgeInSeconds, includeSubDomains, preload) => {
1605
+ let headerValue = `max-age=${maxAgeInSeconds}`;
1606
+ if (includeSubDomains) {
1607
+ headerValue += " ; includeSubDomains";
1608
+ }
1609
+ if (preload) {
1610
+ headerValue += " ; preload";
1611
+ }
1612
+ const delegate = staticServerHttpHeadersWriter(
1613
+ new MapHttpHeaders().add("strict-transport-security", headerValue)
1614
+ );
1615
+ const isSecure = (exchange) => {
1616
+ const protocol = exchange.request.URL.protocol;
1617
+ return protocol === "https:";
1618
+ };
1619
+ return async (exchange) => {
1620
+ if (isSecure(exchange)) {
1621
+ await delegate(exchange);
1622
+ }
1623
+ };
1624
+ };
1625
+ var frameOptionsServerHttpHeadersWriter = (mode) => {
1626
+ return staticServerHttpHeadersWriter(
1627
+ new MapHttpHeaders().add("x-frame-options", mode)
1628
+ );
1629
+ };
1630
+ var xssProtectionServerHttpHeadersWriter = (headerValue) => staticServerHttpHeadersWriter(
1631
+ new MapHttpHeaders().add("x-xss-protection", headerValue)
1632
+ );
1633
+ var permissionsPolicyServerHttpHeadersWriter = (policyDirectives) => {
1634
+ const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
1635
+ new MapHttpHeaders().add("permissions-policy", policyDirectives)
1636
+ );
1637
+ return async (exchange) => {
1638
+ if (delegate !== void 0) {
1639
+ await delegate(exchange);
1640
+ }
1641
+ };
1642
+ };
1643
+ var contentSecurityPolicyServerHttpHeadersWriter = (policyDirectives, reportOnly) => {
1644
+ const headerName = reportOnly ? "content-security-policy-report-only" : "content-security-policy";
1645
+ const delegate = policyDirectives === void 0 ? void 0 : staticServerHttpHeadersWriter(
1646
+ new MapHttpHeaders().add(headerName, policyDirectives)
1647
+ );
1648
+ return async (exchange) => {
1649
+ if (delegate !== void 0) {
1650
+ await delegate(exchange);
1651
+ }
1652
+ };
1653
+ };
1654
+ var refererPolicyServerHttpHeadersWriter = (policy = "no-referrer") => {
1655
+ return staticServerHttpHeadersWriter(
1656
+ new MapHttpHeaders().add("referer-policy", policy)
1657
+ );
1658
+ };
1659
+ var crossOriginOpenerPolicyServerHttpHeadersWriter = (policy) => {
1660
+ const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
1661
+ new MapHttpHeaders().add("cross-origin-opener-policy", policy)
1662
+ );
1663
+ return async (exchange) => {
1664
+ if (delegate !== void 0) {
1665
+ await delegate(exchange);
1666
+ }
1667
+ };
1668
+ };
1669
+ var crossOriginEmbedderPolicyServerHttpHeadersWriter = (policy) => {
1670
+ const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
1671
+ new MapHttpHeaders().add("cross-origin-embedder-policy", policy)
1672
+ );
1673
+ return async (exchange) => {
1674
+ if (delegate !== void 0) {
1675
+ await delegate(exchange);
1676
+ }
1677
+ };
1678
+ };
1679
+ var crossOriginResourcePolicyServerHttpHeadersWriter = (policy) => {
1680
+ const delegate = policy === void 0 ? void 0 : staticServerHttpHeadersWriter(
1681
+ new MapHttpHeaders().add("cross-origin-resource-policy", policy)
1682
+ );
1683
+ return async (exchange) => {
1684
+ if (delegate !== void 0) {
1685
+ await delegate(exchange);
1686
+ }
1687
+ };
1688
+ };
1689
+ var compositeServerHttpHeadersWriter = (...writers) => {
1690
+ return async (exchange) => {
1691
+ for (const writer of writers) {
1692
+ await writer(exchange);
1693
+ }
1694
+ };
1695
+ };
1696
+ function headers(opts) {
1697
+ const writers = [];
1698
+ if (!opts?.cache?.disabled) {
1699
+ writers.push(cacheControlServerHttpHeadersWriter());
1700
+ }
1701
+ if (!opts?.contentType?.disabled) {
1702
+ writers.push(contentTypeServerHttpHeadersWriter());
1703
+ }
1704
+ if (!opts?.hsts?.disabled) {
1705
+ writers.push(strictTransportSecurityServerHttpHeadersWriter(opts?.hsts?.maxAge ?? 365 * 24 * 60 * 60, opts?.hsts?.includeSubDomains ?? true, opts?.hsts?.preload ?? false));
1706
+ }
1707
+ if (!opts?.frameOptions?.disabled) {
1708
+ writers.push(frameOptionsServerHttpHeadersWriter(opts?.frameOptions?.mode ?? "DENY"));
1709
+ }
1710
+ if (!opts?.xss?.disabled) {
1711
+ writers.push(xssProtectionServerHttpHeadersWriter(opts?.xss?.headerValue ?? "0"));
1712
+ }
1713
+ if (!opts?.permissionsPolicy?.disabled) {
1714
+ writers.push(permissionsPolicyServerHttpHeadersWriter(opts?.permissionsPolicy?.policyDirectives));
1715
+ }
1716
+ if (!opts?.contentSecurityPolicy?.disabled) {
1717
+ writers.push(contentSecurityPolicyServerHttpHeadersWriter(opts?.contentSecurityPolicy?.policyDirectives ?? "default-src 'self'", opts?.contentSecurityPolicy?.reportOnly));
1718
+ }
1719
+ if (!opts?.refererPolicy?.disabled) {
1720
+ writers.push(refererPolicyServerHttpHeadersWriter(opts?.refererPolicy?.policy ?? "no-referrer"));
1721
+ }
1722
+ if (!opts?.crossOriginOpenerPolicy?.disabled) {
1723
+ writers.push(crossOriginOpenerPolicyServerHttpHeadersWriter(opts?.crossOriginOpenerPolicy?.policy));
1724
+ }
1725
+ if (!opts?.crossOriginEmbedderPolicy?.disabled) {
1726
+ writers.push(crossOriginEmbedderPolicyServerHttpHeadersWriter(opts?.crossOriginEmbedderPolicy?.policy));
1727
+ }
1728
+ if (!opts?.crossOriginResourcePolicy?.disabled) {
1729
+ writers.push(crossOriginResourcePolicyServerHttpHeadersWriter(opts?.crossOriginResourcePolicy?.policy));
1730
+ }
1731
+ if (opts?.writers) {
1732
+ writers.push(...opts.writers);
1733
+ }
1734
+ const writer = compositeServerHttpHeadersWriter(...writers);
1735
+ return async (exchange, next) => {
1736
+ await writer(exchange);
1737
+ await next();
1738
+ };
1449
1739
  }
1450
1740
 
1741
+ // src/server/security/types.ts
1742
+ var AuthenticationError = class extends Error {
1743
+ _authentication;
1744
+ get authentication() {
1745
+ return this._authentication;
1746
+ }
1747
+ set authentication(value) {
1748
+ if (value === void 0) {
1749
+ throw new TypeError("Authentication cannot be undefined");
1750
+ }
1751
+ this._authentication = value;
1752
+ }
1753
+ };
1754
+ var InsufficientAuthenticationError = class extends AuthenticationError {
1755
+ };
1756
+ var BadCredentialsError = class extends AuthenticationError {
1757
+ };
1758
+ var AccessDeniedError = class extends Error {
1759
+ };
1760
+ var AuthorizationDecision = class {
1761
+ constructor(granted) {
1762
+ this.granted = granted;
1763
+ }
1764
+ granted;
1765
+ };
1766
+ var DefaultAuthorizationManager = class {
1767
+ check;
1768
+ constructor(check) {
1769
+ this.check = check;
1770
+ }
1771
+ async verify(authentication, object) {
1772
+ const decision = await this.check(authentication, object);
1773
+ if (!decision?.granted) {
1774
+ throw new AccessDeniedError("Access denied");
1775
+ }
1776
+ }
1777
+ async authorize(authentication, object) {
1778
+ return await this.check(authentication, object);
1779
+ }
1780
+ };
1781
+ var AuthenticationServiceError = class extends AuthenticationError {
1782
+ };
1783
+
1784
+ // src/server/security/entry-point-failure-handler.ts
1785
+ var serverAuthenticationEntryPointFailureHandler = (opts) => {
1786
+ const entryPoint = opts.entryPoint;
1787
+ const rethrowAuthenticationServiceError = opts?.rethrowAuthenticationServiceError ?? true;
1788
+ return async ({ exchange }, error) => {
1789
+ if (!rethrowAuthenticationServiceError) {
1790
+ return entryPoint(exchange, error);
1791
+ }
1792
+ if (!(error instanceof AuthenticationServiceError)) {
1793
+ return entryPoint(exchange, error);
1794
+ }
1795
+ throw error;
1796
+ };
1797
+ };
1798
+
1799
+ // src/server/security/http-basic-entry-point.ts
1800
+ var DEFAULT_REALM = "Realm";
1801
+ var createHeaderValue = (realm) => {
1802
+ return `Basic realm="${realm}"`;
1803
+ };
1804
+ var httpBasicEntryPoint = (opts) => {
1805
+ const headerValue = createHeaderValue(opts?.realm ?? DEFAULT_REALM);
1806
+ return async (exchange, _error) => {
1807
+ const { response } = exchange;
1808
+ response.statusCode = 401;
1809
+ response.headers.set("WWW-Authenticate", headerValue);
1810
+ };
1811
+ };
1812
+
1813
+ // src/server/security/http-basic-converter.ts
1814
+ var BASIC = "Basic ";
1815
+ var httpBasicAuthenticationConverter = (opts) => {
1816
+ return async (exchange) => {
1817
+ const { request } = exchange;
1818
+ const authorization = request.headers.one("authorization");
1819
+ if (!authorization || !/basic/i.test(authorization.substring(0))) {
1820
+ return;
1821
+ }
1822
+ const credentials = authorization.length <= BASIC.length ? "" : authorization.substring(BASIC.length);
1823
+ const decoded = Buffer.from(credentials, "base64").toString(opts?.credentialsEncoding ?? "utf-8");
1824
+ const parts = decoded.split(":", 2);
1825
+ if (parts.length !== 2) {
1826
+ return void 0;
1827
+ }
1828
+ return { type: "UsernamePassword", authenticated: false, principal: parts[0], credentials: parts[1] };
1829
+ };
1830
+ };
1831
+
1832
+ // src/server/util/matchers.ts
1833
+ var or = (matchers) => {
1834
+ return async (exchange) => {
1835
+ for (const matcher of matchers) {
1836
+ const result = await matcher(exchange);
1837
+ if (result.match) {
1838
+ return { match: true };
1839
+ }
1840
+ }
1841
+ return { match: false };
1842
+ };
1843
+ };
1844
+ var and = (matchers) => {
1845
+ return async (exchange) => {
1846
+ for (const matcher of matchers) {
1847
+ const result = await matcher(exchange);
1848
+ if (!result.match) {
1849
+ return { match: false };
1850
+ }
1851
+ }
1852
+ return { match: true };
1853
+ };
1854
+ };
1855
+ var not = (matcher) => {
1856
+ return async (exchange) => {
1857
+ const result = await matcher(exchange);
1858
+ return { match: !result.match };
1859
+ };
1860
+ };
1861
+ var anyExchange = async (_exchange) => {
1862
+ return { match: true };
1863
+ };
1864
+ var mediaType = (opts) => {
1865
+ const shouldIgnore = (requestedMediaType) => {
1866
+ if (opts.ignoredMediaTypes !== void 0) {
1867
+ for (const ignoredMediaType of opts.ignoredMediaTypes) {
1868
+ if (requestedMediaType === ignoredMediaType || ignoredMediaType === "*/*") {
1869
+ return true;
1870
+ }
1871
+ }
1872
+ }
1873
+ return false;
1874
+ };
1875
+ return async (exchange) => {
1876
+ const request = exchange.request;
1877
+ let requestMediaTypes;
1878
+ try {
1879
+ requestMediaTypes = request.headers.list("accept");
1880
+ } catch (e) {
1881
+ return { match: false };
1882
+ }
1883
+ for (const requestedMediaType of requestMediaTypes) {
1884
+ if (shouldIgnore(requestedMediaType)) {
1885
+ continue;
1886
+ }
1887
+ for (const mediaType2 of opts.mediaTypes) {
1888
+ if (requestedMediaType.startsWith(mediaType2)) {
1889
+ return { match: true };
1890
+ }
1891
+ }
1892
+ }
1893
+ return { match: false };
1894
+ };
1895
+ };
1896
+
1897
+ // src/server/security/security-context.ts
1898
+ var import_node_async_hooks = require("node:async_hooks");
1899
+ var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
1900
+ static hasSecurityContext(storage) {
1901
+ return storage.getStore()?.securityContext !== void 0;
1902
+ }
1903
+ static async getSecurityContext(storage) {
1904
+ return await storage.getStore()?.securityContext;
1905
+ }
1906
+ static clearSecurityContext(storage) {
1907
+ delete storage.getStore()?.securityContext;
1908
+ }
1909
+ static withSecurityContext(securityContext) {
1910
+ return (storage = new import_node_async_hooks.AsyncLocalStorage()) => {
1911
+ storage.getStore().securityContext = securityContext;
1912
+ return storage;
1913
+ };
1914
+ }
1915
+ static withAuthentication(authentication) {
1916
+ return _AsyncStorageSecurityContextHolder.withSecurityContext(Promise.resolve({ authentication }));
1917
+ }
1918
+ static async getContext(storage) {
1919
+ if (_AsyncStorageSecurityContextHolder.hasSecurityContext(storage)) {
1920
+ return _AsyncStorageSecurityContextHolder.getSecurityContext(storage);
1921
+ }
1922
+ }
1923
+ };
1924
+
1925
+ // src/server/security/authentication-filter.ts
1926
+ async function authenticate(exchange, next, token, managerResolver, successHandler, storage) {
1927
+ const authManager = await managerResolver(exchange);
1928
+ const authentication = await authManager?.(token);
1929
+ if (authentication === void 0) {
1930
+ throw new Error("No authentication manager found for the exchange");
1931
+ }
1932
+ try {
1933
+ await onAuthenticationSuccess(authentication, { exchange, next }, successHandler, storage);
1934
+ } catch (e) {
1935
+ if (e instanceof AuthenticationError) {
1936
+ }
1937
+ throw e;
1938
+ }
1939
+ }
1940
+ async function onAuthenticationSuccess(authentication, filterExchange, successHandler, storage) {
1941
+ AsyncStorageSecurityContextHolder.withAuthentication(authentication)(storage);
1942
+ await successHandler(filterExchange, authentication);
1943
+ }
1944
+ function authenticationFilter(opts) {
1945
+ const auth = {
1946
+ matcher: anyExchange,
1947
+ successHandler: async ({ next }) => {
1948
+ await next();
1949
+ },
1950
+ converter: httpBasicAuthenticationConverter({}),
1951
+ failureHandler: serverAuthenticationEntryPointFailureHandler({ entryPoint: httpBasicEntryPoint({}) }),
1952
+ ...opts
1953
+ };
1954
+ let managerResolver = auth.managerResolver;
1955
+ if (managerResolver === void 0 && auth.manager !== void 0) {
1956
+ const manager = auth.manager;
1957
+ managerResolver = async (_exchange) => {
1958
+ return manager;
1959
+ };
1960
+ }
1961
+ if (managerResolver === void 0) {
1962
+ throw new Error("Authentication filter requires a managerResolver or a manager");
1963
+ }
1964
+ return async (exchange, next) => {
1965
+ const matchResult = await auth.matcher(exchange);
1966
+ const token = matchResult.match ? await auth.converter(exchange) : void 0;
1967
+ if (token === void 0) {
1968
+ await next();
1969
+ return;
1970
+ }
1971
+ try {
1972
+ await authenticate(exchange, next, token, managerResolver, auth.successHandler, auth.storage);
1973
+ } catch (error) {
1974
+ if (error instanceof AuthenticationError) {
1975
+ await auth.failureHandler({ exchange, next }, error);
1976
+ return;
1977
+ }
1978
+ throw error;
1979
+ }
1980
+ };
1981
+ }
1982
+
1983
+ // src/server/security/oauth2/token-error.ts
1984
+ var BearerTokenErrorCodes = {
1985
+ invalid_request: "invalid_request",
1986
+ invalid_token: "invalid_token"
1987
+ };
1988
+ var DEFAULT_URI = "https://tools.ietf.org/html/rfc6750#section-3.1";
1989
+ function invalidToken(message) {
1990
+ return { errorCode: BearerTokenErrorCodes.invalid_token, httpStatus: 401, description: message, uri: DEFAULT_URI };
1991
+ }
1992
+ function invalidRequest(message) {
1993
+ return { errorCode: BearerTokenErrorCodes.invalid_request, httpStatus: 400, description: message, uri: DEFAULT_URI };
1994
+ }
1995
+
1996
+ // src/server/security/oauth2/token-converter.ts
1997
+ var ACCESS_TOKEN_PARAMETER_NAME = "access_token";
1998
+ var authorizationPattern = /^Bearer\s+(?<token>[a-zA-Z0-9-._~+/]+=*)$/i;
1999
+ var Oauth2AuthenticationError = class extends AuthenticationError {
2000
+ error;
2001
+ constructor(error, message, options) {
2002
+ super(message ?? (typeof error === "string" ? void 0 : error.description), options);
2003
+ this.error = typeof error === "string" ? { errorCode: error } : error;
2004
+ }
2005
+ };
2006
+ var isBearerTokenAuthenticationToken = (authentication) => {
2007
+ return authentication.type === "BearerToken";
2008
+ };
2009
+ var serverBearerTokenAuthenticationConverter = (opts) => {
2010
+ return async (exchange) => {
2011
+ const { request } = exchange;
2012
+ return Promise.all([
2013
+ resolveFromAuthorizationHeader(request.headers, opts?.headerName).then((token) => token !== void 0 ? [token] : void 0),
2014
+ resolveFromQueryString(request, opts?.uriQueryParameter),
2015
+ resolveFromBody(exchange, opts?.formEncodedBodyParameter)
2016
+ ]).then((rs) => rs.filter((r) => r !== void 0).flat(1)).then(resolveToken).then((token) => {
2017
+ if (token) return { authenticated: false, type: "BearerToken", token };
2018
+ });
2019
+ };
2020
+ };
2021
+ async function resolveToken(accessTokens) {
2022
+ if (accessTokens.length === 0) {
2023
+ return;
2024
+ }
2025
+ if (accessTokens.length > 1) {
2026
+ const error = invalidRequest("Found multiple access tokens in the request");
2027
+ throw new Oauth2AuthenticationError(error);
2028
+ }
2029
+ const accessToken = accessTokens[0];
2030
+ if (!accessToken || accessToken.length === 0) {
2031
+ const error = invalidRequest("The requested access token parameter is an empty string");
2032
+ throw new Oauth2AuthenticationError(error);
2033
+ }
2034
+ return accessToken;
2035
+ }
2036
+ async function resolveFromAuthorizationHeader(headers2, headerName = "authorization") {
2037
+ const authorization = headers2.one(headerName);
2038
+ if (!authorization || !/bearer/i.test(authorization.substring(0))) {
2039
+ return;
2040
+ }
2041
+ const match = authorizationPattern.exec(authorization);
2042
+ if (match === null) {
2043
+ const error = invalidToken("Bearer token is malformed");
2044
+ throw new Oauth2AuthenticationError(error);
2045
+ }
2046
+ return match.groups?.token;
2047
+ }
2048
+ async function resolveTokens(parameters) {
2049
+ const accessTokens = parameters.getAll(ACCESS_TOKEN_PARAMETER_NAME);
2050
+ if (accessTokens.length === 0) {
2051
+ return;
2052
+ }
2053
+ return accessTokens;
2054
+ }
2055
+ async function resolveFromQueryString(request, allow = false) {
2056
+ if (!allow || request.method !== "GET") {
2057
+ return;
2058
+ }
2059
+ return resolveTokens(request.URL.searchParams);
2060
+ }
2061
+ async function resolveFromBody(exchange, allow = false) {
2062
+ const { request } = exchange;
2063
+ if (!allow || "application/x-www-form-urlencoded" !== request.headers.one("content-type") || request.method !== "POST") {
2064
+ return;
2065
+ }
2066
+ return resolveTokens(await exchange.request.formData);
2067
+ }
2068
+ var token_converter_default = serverBearerTokenAuthenticationConverter;
2069
+
2070
+ // src/server/security/oauth2/token-entry-point.ts
2071
+ function computeWWWAuthenticate(parameters) {
2072
+ let wwwAuthenticate = "Bearer";
2073
+ if (parameters.size !== 0) {
2074
+ wwwAuthenticate += " ";
2075
+ let i = 0;
2076
+ for (const [key, value] of parameters) {
2077
+ wwwAuthenticate += `${key}="${value}"`;
2078
+ if (i !== parameters.size - 1) {
2079
+ wwwAuthenticate += ", ";
2080
+ }
2081
+ i++;
2082
+ }
2083
+ }
2084
+ return wwwAuthenticate;
2085
+ }
2086
+ var isBearerTokenError = (error) => {
2087
+ return error.httpStatus !== void 0;
2088
+ };
2089
+ function getStatus(authError) {
2090
+ if (authError instanceof Oauth2AuthenticationError) {
2091
+ const { error } = authError;
2092
+ if (isBearerTokenError(error)) {
2093
+ return error.httpStatus;
2094
+ }
2095
+ }
2096
+ return 401;
2097
+ }
2098
+ function createParameters(authError, realm) {
2099
+ const parameters = /* @__PURE__ */ new Map();
2100
+ if (realm) {
2101
+ parameters.set("realm", realm);
2102
+ }
2103
+ if (authError instanceof Oauth2AuthenticationError) {
2104
+ const { error } = authError;
2105
+ parameters.set("error", error.errorCode);
2106
+ if (error.description) {
2107
+ parameters.set("error_description", error.description);
2108
+ }
2109
+ if (error.uri) {
2110
+ parameters.set("error_uri", error.uri);
2111
+ }
2112
+ if (isBearerTokenError(error) && error.scope) {
2113
+ parameters.set("scope", error.scope);
2114
+ }
2115
+ }
2116
+ return parameters;
2117
+ }
2118
+ var bearerTokenServerAuthenticationEntryPoint = (opts) => {
2119
+ return async (exchange, error) => {
2120
+ const status = getStatus(error);
2121
+ const parameters = createParameters(error, opts?.realmName);
2122
+ const wwwAuthenticate = computeWWWAuthenticate(parameters);
2123
+ const { response } = exchange;
2124
+ response.headers.set("WWW-Authenticate", wwwAuthenticate);
2125
+ response.statusCode = status;
2126
+ await response.end();
2127
+ };
2128
+ };
2129
+ var token_entry_point_default = bearerTokenServerAuthenticationEntryPoint;
2130
+
2131
+ // src/server/security/oauth2/jwt-auth-manager.ts
2132
+ var jwtAuthConverter = (opts) => {
2133
+ const principalClaimName = opts?.principalClaimName ?? "sub";
2134
+ return (jwt) => {
2135
+ const name = jwt.getClaimAsString(principalClaimName);
2136
+ return { type: "JwtToken", authenticated: true, name };
2137
+ };
2138
+ };
2139
+ var asyncJwtConverter = (converter) => {
2140
+ return async (jwt) => {
2141
+ return converter(jwt);
2142
+ };
2143
+ };
2144
+ var JwtError = class extends Error {
2145
+ };
2146
+ var BadJwtError = class extends JwtError {
2147
+ };
2148
+ function onError(error) {
2149
+ if (error instanceof BadJwtError) {
2150
+ return new Oauth2AuthenticationError(invalidToken(error.message), error.message, { cause: error });
2151
+ }
2152
+ throw new AuthenticationServiceError(error.message, { cause: error });
2153
+ }
2154
+ function jwtAuthManager(opts) {
2155
+ const decoder = opts.decoder;
2156
+ const authConverter = opts.authConverter ?? asyncJwtConverter(jwtAuthConverter({}));
2157
+ return async (authentication) => {
2158
+ if (isBearerTokenAuthenticationToken(authentication)) {
2159
+ const token = authentication.token;
2160
+ try {
2161
+ const jwt = await decoder(token);
2162
+ return await authConverter(jwt);
2163
+ } catch (e) {
2164
+ if (e instanceof JwtError) {
2165
+ throw onError(e);
2166
+ }
2167
+ throw e;
2168
+ }
2169
+ }
2170
+ };
2171
+ }
2172
+
2173
+ // src/server/security/oauth2-resource-server.ts
2174
+ function resourceServer(opts) {
2175
+ const entryPoint = opts.entryPoint ?? token_entry_point_default({});
2176
+ const converter = opts?.converter ?? token_converter_default({});
2177
+ const failureHandler = opts.failureHandler ?? serverAuthenticationEntryPointFailureHandler({ entryPoint });
2178
+ if (opts.managerResolver !== void 0) {
2179
+ return authenticationFilter({
2180
+ storage: opts.storage,
2181
+ converter,
2182
+ failureHandler,
2183
+ managerResolver: opts.managerResolver
2184
+ });
2185
+ }
2186
+ if (opts.jwt !== void 0) {
2187
+ const manager = opts.jwt.manager ?? jwtAuthManager(opts.jwt);
2188
+ return authenticationFilter({
2189
+ storage: opts.storage,
2190
+ converter,
2191
+ failureHandler,
2192
+ managerResolver: async (_exchange) => {
2193
+ return manager;
2194
+ }
2195
+ });
2196
+ }
2197
+ throw new Error("Invalid resource server configuration: either managerResolver or jwt must be provided");
2198
+ }
2199
+
2200
+ // src/server/security/http-status-entry-point.ts
2201
+ var httpStatusEntryPoint = (opts) => {
2202
+ return async (exchange, _error) => {
2203
+ const response = exchange.response;
2204
+ response.statusCode = opts.httpStatus.code;
2205
+ response.statusMessage = opts.httpStatus.message;
2206
+ };
2207
+ };
2208
+
2209
+ // src/server/security/delegating-entry-point.ts
2210
+ var delegatingEntryPoint = (opts) => {
2211
+ const defaultEntryPoint = opts.defaultEntryPoint ?? (async ({ response }, _error) => {
2212
+ response.statusCode = 401;
2213
+ await response.end();
2214
+ });
2215
+ return async (exchange, error) => {
2216
+ for (const [matcher, entryPoint] of opts.entryPoints) {
2217
+ const match = await matcher(exchange);
2218
+ if (match.match) {
2219
+ return entryPoint(exchange, error);
2220
+ }
2221
+ }
2222
+ return defaultEntryPoint(exchange, error);
2223
+ };
2224
+ };
2225
+
2226
+ // src/server/security/delegating-success-handler.ts
2227
+ var delegatingSuccessHandler = (handlers) => {
2228
+ return async ({ exchange, next }, authentication) => {
2229
+ for (const handler2 of handlers) {
2230
+ await handler2({ exchange, next }, authentication);
2231
+ }
2232
+ };
2233
+ };
2234
+
2235
+ // src/server/security/http-basic.ts
2236
+ function httpBasic(opts) {
2237
+ const xhrMatcher = async (exchange) => {
2238
+ const headers2 = exchange.request.headers;
2239
+ const h = headers2.list("X-Requested-With");
2240
+ if (h.includes("XMLHttpRequest")) {
2241
+ return { match: true };
2242
+ }
2243
+ return { match: false };
2244
+ };
2245
+ const defaultEntryPoint = delegatingEntryPoint({
2246
+ entryPoints: [[xhrMatcher, httpStatusEntryPoint({ httpStatus: { code: 401 } })]],
2247
+ defaultEntryPoint: httpBasicEntryPoint({})
2248
+ });
2249
+ const entryPoint = opts.entryPoint ?? defaultEntryPoint;
2250
+ const manager = opts.manager;
2251
+ const restMatcher = mediaType({
2252
+ mediaTypes: [
2253
+ "application/atom+xml",
2254
+ "application/x-www-form-urlencoded",
2255
+ "application/json",
2256
+ "application/octet-stream",
2257
+ "application/xml",
2258
+ "multipart/form-data",
2259
+ "text/xml"
2260
+ ],
2261
+ ignoredMediaTypes: ["*/*"]
2262
+ });
2263
+ const notHtmlMatcher = not(mediaType({ mediaTypes: ["text/html"] }));
2264
+ const restNoHtmlMatcher = and([notHtmlMatcher, restMatcher]);
2265
+ const preferredMatcher = or([xhrMatcher, restNoHtmlMatcher]);
2266
+ opts.defaultEntryPoints.push([preferredMatcher, entryPoint]);
2267
+ const failureHandler = opts.failureHandler ?? serverAuthenticationEntryPointFailureHandler({ entryPoint });
2268
+ const successHandler = delegatingSuccessHandler(opts.successHandlers === void 0 ? opts.defaultSuccessHandlers : opts.successHandlers);
2269
+ const converter = httpBasicAuthenticationConverter({});
2270
+ return authenticationFilter({
2271
+ storage: opts.storage,
2272
+ manager,
2273
+ failureHandler,
2274
+ successHandler,
2275
+ converter
2276
+ });
2277
+ }
2278
+
2279
+ // src/server/security/config.ts
2280
+ var import_jwt = require("@interopio/gateway/jose/jwt");
2281
+
2282
+ // src/server/security/error-filter.ts
2283
+ async function commenceAuthentication(exchange, authentication, entryPoint) {
2284
+ const cause = new InsufficientAuthenticationError(`Full authentication is required to access this resource.`);
2285
+ const e = new AuthenticationError("Access Denied", { cause });
2286
+ if (authentication) {
2287
+ e.authentication = authentication;
2288
+ }
2289
+ await entryPoint(exchange, e);
2290
+ }
2291
+ function httpStatusAccessDeniedHandler(status, message) {
2292
+ return async (exchange, _error) => {
2293
+ exchange.response.statusCode = status;
2294
+ exchange.response.statusMessage = message;
2295
+ exchange.response.headers.set("Content-Type", "text/plain");
2296
+ const bytes = Buffer.from("Access Denied", "utf-8");
2297
+ exchange.response.headers.set("Content-Length", bytes.length);
2298
+ await exchange.response.end(bytes);
2299
+ };
2300
+ }
2301
+ var errorFilter = (opts) => {
2302
+ const accessDeniedHandler = httpStatusAccessDeniedHandler(403, "Forbidden");
2303
+ const authenticationEntryPoint = opts.authenticationEntryPoint ?? httpBasicEntryPoint();
2304
+ return async (exchange, next) => {
2305
+ try {
2306
+ await next();
2307
+ } catch (error) {
2308
+ if (error instanceof AccessDeniedError) {
2309
+ const principal = await exchange.principal;
2310
+ if (principal === void 0) {
2311
+ await commenceAuthentication(exchange, void 0, authenticationEntryPoint);
2312
+ } else {
2313
+ if (!principal.authenticated) {
2314
+ return accessDeniedHandler(exchange, error);
2315
+ }
2316
+ await commenceAuthentication(exchange, principal, authenticationEntryPoint);
2317
+ }
2318
+ }
2319
+ }
2320
+ };
2321
+ };
2322
+
2323
+ // src/server/security/authorization-filter.ts
2324
+ function authorizationFilter(opts) {
2325
+ const { manager, storage } = opts;
2326
+ return async (exchange, next) => {
2327
+ const promise = AsyncStorageSecurityContextHolder.getContext(storage).then((c) => c?.authentication);
2328
+ try {
2329
+ await manager.verify(promise, exchange);
2330
+ } catch (error) {
2331
+ if (error instanceof AccessDeniedError) {
2332
+ }
2333
+ throw error;
2334
+ }
2335
+ await next();
2336
+ };
2337
+ }
2338
+
2339
+ // src/server/security/delegating-authorization-manager.ts
2340
+ function delegatingAuthorizationManager(opts) {
2341
+ const check = async (authentication, exchange) => {
2342
+ let decision;
2343
+ for (const [matcher, manager] of opts.mappings) {
2344
+ if ((await matcher(exchange))?.match) {
2345
+ const checkResult = await manager.check(authentication, { exchange });
2346
+ if (checkResult !== void 0) {
2347
+ decision = checkResult;
2348
+ break;
2349
+ }
2350
+ }
2351
+ }
2352
+ decision ??= new AuthorizationDecision(false);
2353
+ return decision;
2354
+ };
2355
+ return new DefaultAuthorizationManager(check);
2356
+ }
2357
+
2358
+ // src/server/security/config.ts
2359
+ var filterOrder = {
2360
+ first: Number.MAX_SAFE_INTEGER,
2361
+ http_headers: 1 * 100,
2362
+ https_redirect: 2 * 100,
2363
+ cors: 3 * 100,
2364
+ http_basic: 6 * 100,
2365
+ authentication: 8 * 100,
2366
+ error_translation: 18 * 100,
2367
+ authorization: 19 * 100,
2368
+ last: Number.MAX_SAFE_INTEGER
2369
+ };
2370
+ var filterOrderSymbol = Symbol.for("filterOrder");
2371
+ var config_default = (config, storage) => {
2372
+ const middleware = [];
2373
+ class ServerHttpSecurity {
2374
+ #authenticationEntryPoint;
2375
+ #defaultEntryPoints = [];
2376
+ manager;
2377
+ get authenticationEntryPoint() {
2378
+ if (this.#authenticationEntryPoint !== void 0 || this.#defaultEntryPoints.length === 0) {
2379
+ return this.#authenticationEntryPoint;
2380
+ }
2381
+ if (this.#defaultEntryPoints.length === 1) {
2382
+ return this.#defaultEntryPoints[0][1];
2383
+ }
2384
+ return delegatingEntryPoint({
2385
+ entryPoints: this.#defaultEntryPoints,
2386
+ defaultEntryPoint: this.#defaultEntryPoints[this.#defaultEntryPoints.length - 1][1]
2387
+ });
2388
+ }
2389
+ build() {
2390
+ if (config.headers !== void 0 && config.headers.disabled !== true) {
2391
+ const writer = headers(config.headers);
2392
+ writer[filterOrderSymbol] = filterOrder.http_headers;
2393
+ middleware.push(writer);
2394
+ }
2395
+ if (config.basic !== void 0 && config.basic?.disabled !== true) {
2396
+ const username = config.basic.user?.name.toLowerCase();
2397
+ const password = config.basic.user?.password ?? "";
2398
+ const authorities = config.basic.user?.authorities ?? [];
2399
+ const manager = async (auth) => {
2400
+ const principal = auth["principal"];
2401
+ const credentials = auth["credentials"];
2402
+ if (principal.toLowerCase() !== username || credentials !== password) {
2403
+ throw new BadCredentialsError("Invalid username or password");
2404
+ }
2405
+ return { type: "UsernamePassword", authenticated: true, principal, credentials, authorities: [...authorities] };
2406
+ };
2407
+ const defaultSuccessHandlers = [
2408
+ async ({ exchange: _, next }, _authentication) => {
2409
+ return next();
2410
+ }
2411
+ ];
2412
+ const filter = httpBasic({
2413
+ storage,
2414
+ manager,
2415
+ defaultEntryPoints: this.#defaultEntryPoints,
2416
+ defaultSuccessHandlers
2417
+ });
2418
+ filter[filterOrderSymbol] = filterOrder.http_basic;
2419
+ middleware.push(filter);
2420
+ }
2421
+ if (config.jwt !== void 0 && config.jwt.disabled !== true) {
2422
+ const verifier = (0, import_jwt.jwtVerifier)({
2423
+ issuerBaseUri: config.jwt.issuerBaseUri,
2424
+ issuer: config.jwt.issuer,
2425
+ audience: config.jwt.audience
2426
+ });
2427
+ const decoder = async (token) => {
2428
+ const { payload } = await verifier(token);
2429
+ return {
2430
+ subject: payload.sub,
2431
+ getClaimAsString(claim) {
2432
+ return payload[claim];
2433
+ }
2434
+ };
2435
+ };
2436
+ const filter = resourceServer({ storage, jwt: { decoder } });
2437
+ filter[filterOrderSymbol] = filterOrder.authentication;
2438
+ middleware.push(filter);
2439
+ }
2440
+ if (config.authorize !== void 0) {
2441
+ const errorFf = errorFilter({ authenticationEntryPoint: this.authenticationEntryPoint });
2442
+ errorFf[filterOrderSymbol] = filterOrder.error_translation;
2443
+ middleware.push(errorFf);
2444
+ const buildAuthorizationManager = (authorize) => {
2445
+ const mappings = [];
2446
+ let anyExchangeRegistered = false;
2447
+ for (const [matcher, access2] of authorize ?? []) {
2448
+ let serverMatcher;
2449
+ if (matcher === "any-exchange") {
2450
+ anyExchangeRegistered = true;
2451
+ serverMatcher = anyExchange;
2452
+ } else if (anyExchangeRegistered) {
2453
+ throw new Error("Cannot register other matchers after 'any-exchange' matcher");
2454
+ } else {
2455
+ serverMatcher = matcher;
2456
+ }
2457
+ let manager2;
2458
+ if (access2.access === "permit-all") {
2459
+ manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(true));
2460
+ } else if (access2.access === "deny-all") {
2461
+ manager2 = new DefaultAuthorizationManager(async () => new AuthorizationDecision(false));
2462
+ } else if (access2.access === "authenticated") {
2463
+ manager2 = new DefaultAuthorizationManager(async (p) => {
2464
+ const authentication = await p;
2465
+ if (authentication !== void 0) {
2466
+ return new AuthorizationDecision(authentication.authenticated);
2467
+ }
2468
+ return new AuthorizationDecision(false);
2469
+ });
2470
+ } else {
2471
+ throw new Error(`Unknown access type: ${JSON.stringify(access2)}`);
2472
+ }
2473
+ mappings.push([serverMatcher, manager2]);
2474
+ }
2475
+ return delegatingAuthorizationManager({ mappings });
2476
+ };
2477
+ const manager = buildAuthorizationManager(config.authorize);
2478
+ const filter = authorizationFilter({ manager, storage });
2479
+ filter[filterOrderSymbol] = filterOrder.authorization;
2480
+ middleware.push(filter);
2481
+ }
2482
+ middleware.sort((a, b) => {
2483
+ const aOrder = a[filterOrderSymbol] ?? filterOrder.last;
2484
+ const bOrder = b[filterOrderSymbol] ?? filterOrder.last;
2485
+ return aOrder - bOrder;
2486
+ });
2487
+ }
2488
+ }
2489
+ const security = new ServerHttpSecurity();
2490
+ security.build();
2491
+ return middleware;
2492
+ };
2493
+
1451
2494
  // src/server.ts
1452
2495
  var logger7 = getLogger("app");
1453
2496
  function secureContextOptions(ssl) {
@@ -1457,20 +2500,81 @@ function secureContextOptions(ssl) {
1457
2500
  if (ssl.ca) options.ca = (0, import_node_fs.readFileSync)(ssl.ca);
1458
2501
  return options;
1459
2502
  }
1460
- function createListener(middleware, routes3) {
1461
- const storage = new import_node_async_hooks.AsyncLocalStorage();
2503
+ function createListener(storage, middleware, routes3, onSocketError) {
1462
2504
  const listener = compose(
1463
- async ({ response }, next) => {
1464
- response.headers.set("server", "gateway-server");
1465
- await next();
1466
- },
2505
+ server_header_default(),
2506
+ ...config_default({
2507
+ authorize: [
2508
+ [async (exchange) => {
2509
+ return { match: exchange.path === "/health" && exchange.method === "GET" };
2510
+ }, { access: "permit-all" }],
2511
+ // ['any-exchange', {access: 'authenticated'}],
2512
+ ["any-exchange", { access: "permit-all" }]
2513
+ ],
2514
+ basic: {
2515
+ disabled: true,
2516
+ realm: "Gateway Server"
2517
+ },
2518
+ jwt: {
2519
+ disabled: true
2520
+ }
2521
+ }, storage),
1467
2522
  ...cors_default({
1468
- origins: { allow: [/http:\/\/localhost(:\d+)?/] },
2523
+ origins: { allow: [/http:\/\/localhost(:\d+)?/, /file:\//] },
1469
2524
  methods: { allow: ["GET", "HEAD", "POST", "DELETE"] },
1470
2525
  headers: { allow: "*" },
1471
2526
  credentials: { allow: true }
1472
2527
  }),
1473
2528
  ...middleware,
2529
+ async ({ request, response }, next) => {
2530
+ const path = request.path ?? "/";
2531
+ const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
2532
+ if (path === "/" && route2.default === true) {
2533
+ return true;
2534
+ }
2535
+ });
2536
+ if (route) {
2537
+ if (request.method === "GET" && request.headers.one("connection") === "Upgrade" && request.headers.one("upgrade")?.toLowerCase() === "websocket") {
2538
+ const socket = request.socket;
2539
+ const host = request.host;
2540
+ const info = socketKey(request._req.socket);
2541
+ if (route?.wss) {
2542
+ socket.removeListener("error", onSocketError);
2543
+ const wss = route.wss;
2544
+ if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
2545
+ logger7.warn(`${info} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
2546
+ socket.destroy();
2547
+ return;
2548
+ }
2549
+ const origin = request.headers["origin"];
2550
+ if (!acceptsOrigin(origin, route.originFilters)) {
2551
+ logger7.info(`${info} dropping ws connection request from ${host} on ${path}. origin ${origin ?? "<missing>"}`);
2552
+ socket.destroy();
2553
+ return;
2554
+ }
2555
+ if (logger7.enabledFor("debug")) {
2556
+ logger7.debug(`${info} accepted new ws connection request from ${host} on ${path}`);
2557
+ }
2558
+ wss.handleUpgrade(request._req, socket, request._req["_upgradeHead"], (ws) => {
2559
+ response._res["_header"] = true;
2560
+ ws.on("pong", () => ws["connected"] = true);
2561
+ ws.on("ping", () => {
2562
+ });
2563
+ wss.emit("connection", ws, request._req);
2564
+ });
2565
+ } else {
2566
+ logger7.warn(`${info} rejected upgrade request from ${host} on ${path}`);
2567
+ socket.destroy();
2568
+ }
2569
+ } else {
2570
+ response.statusCode = 426;
2571
+ response._res.appendHeader("Upgrade", "websocket").appendHeader("Connection", "Upgrade").appendHeader("Content-Type", "text/plain");
2572
+ await response.end(`This service [${request.path}] requires use of the websocket protocol.`);
2573
+ }
2574
+ } else {
2575
+ await next();
2576
+ }
2577
+ },
1474
2578
  async ({ request, response }, next) => {
1475
2579
  if (request.method === "GET" && request.path === "/health") {
1476
2580
  response.statusCode = 200;
@@ -1481,33 +2585,35 @@ function createListener(middleware, routes3) {
1481
2585
  },
1482
2586
  async ({ request, response }, next) => {
1483
2587
  if (request.method === "GET" && request.path === "/") {
1484
- response._res.end(`io.Gateway Server`);
2588
+ await response.end(`io.Gateway Server`);
1485
2589
  } else {
1486
2590
  await next();
1487
2591
  }
1488
2592
  },
1489
2593
  async ({ request, response }, _next) => {
1490
- const route = routes3.get(request.path);
1491
- if (route) {
1492
- response.statusCode = 426;
1493
- response._res.appendHeader("Upgrade", "websocket").appendHeader("Connection", "Upgrade").appendHeader("Content-Type", "text/plain");
1494
- response._res.end(`This service [${request.path}] requires use of the websocket protocol.`);
1495
- } else {
1496
- response.statusCode = 404;
1497
- response._res.end(import_node_http.default.STATUS_CODES[404]);
1498
- }
2594
+ response.statusCode = 404;
2595
+ await response.end(import_node_http.default.STATUS_CODES[404]);
1499
2596
  }
1500
2597
  );
1501
2598
  return (request, response) => {
2599
+ request.socket.addListener("error", onSocketError);
1502
2600
  const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
1503
- return storage.run(exchange, async () => {
2601
+ return storage.run({ exchange }, async () => {
1504
2602
  if (logger7.enabledFor("debug")) {
1505
2603
  const socket = exchange.request._req.socket;
1506
2604
  if (logger7.enabledFor("debug")) {
1507
2605
  logger7.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
1508
2606
  }
1509
2607
  }
1510
- return await listener(exchange);
2608
+ try {
2609
+ return await listener(exchange);
2610
+ } catch (e) {
2611
+ if (logger7.enabledFor("warn")) {
2612
+ logger7.warn(`error processing request for ${exchange.path}`, e);
2613
+ }
2614
+ } finally {
2615
+ await exchange.response.end();
2616
+ }
1511
2617
  });
1512
2618
  };
1513
2619
  }
@@ -1566,12 +2672,11 @@ var Factory = async (options) => {
1566
2672
  }
1567
2673
  const ports = portRange(options.port ?? 0);
1568
2674
  const host = options.host;
2675
+ const storage = new import_node_async_hooks2.AsyncLocalStorage();
1569
2676
  const serverP = new Promise((resolve, reject) => {
1570
2677
  const onSocketError = (err) => logger7.error(`socket error: ${err}`, err);
1571
- const server2 = createServer(
1572
- {},
1573
- createListener(middleware, routes3)
1574
- );
2678
+ const listener = createListener(storage, middleware, routes3, onSocketError);
2679
+ const server2 = createServer({}, listener);
1575
2680
  server2.on("error", (e) => {
1576
2681
  if (e["code"] === "EADDRINUSE") {
1577
2682
  logger7.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
@@ -1597,7 +2702,7 @@ var Factory = async (options) => {
1597
2702
  logger7.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
1598
2703
  const wss = new import_ws.WebSocketServer({ noServer: true });
1599
2704
  const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info.port}${path}`;
1600
- const handler2 = await route.factory({ endpoint, wss });
2705
+ const handler2 = await route.factory({ endpoint, wss, storage });
1601
2706
  const pingInterval = route.ping;
1602
2707
  if (pingInterval) {
1603
2708
  const pingIntervalId = setInterval(() => {
@@ -1625,42 +2730,10 @@ var Factory = async (options) => {
1625
2730
  server2.on("upgrade", (req, socket, head) => {
1626
2731
  socket.addListener("error", onSocketError);
1627
2732
  try {
1628
- const request = new HttpServerRequest(req);
1629
- const path = request.path ?? "/";
1630
- const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
1631
- if (path === "/" && route2.default === true) {
1632
- return true;
1633
- }
1634
- });
1635
- const host2 = request.host;
1636
- const info = socketKey(request.socket);
1637
- if (route?.wss) {
1638
- socket.removeListener("error", onSocketError);
1639
- const wss = route.wss;
1640
- if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
1641
- logger7.warn(`${info} dropping ws connection request from ${host2} on ${path}. max connections exceeded.`);
1642
- socket.destroy();
1643
- return;
1644
- }
1645
- const origin = request.headers["origin"];
1646
- if (!acceptsOrigin(origin, route.originFilters)) {
1647
- logger7.info(`${info} dropping ws connection request from ${host2} on ${path}. origin ${origin ?? "<missing>"}`);
1648
- socket.destroy();
1649
- return;
1650
- }
1651
- if (logger7.enabledFor("debug")) {
1652
- logger7.debug(`${info} accepted new ws connection request from ${host2} on ${path}`);
1653
- }
1654
- wss.handleUpgrade(req, socket, head, (ws) => {
1655
- ws.on("pong", () => ws["connected"] = true);
1656
- ws.on("ping", () => {
1657
- });
1658
- wss.emit("connection", ws, req);
1659
- });
1660
- } else {
1661
- logger7.warn(`${info} rejected upgrade request from ${host2} on ${path}`);
1662
- socket.destroy();
1663
- }
2733
+ req._upgradeHead = head;
2734
+ const res = new import_node_http.default.ServerResponse(req);
2735
+ res.assignSocket(socket);
2736
+ listener(req, res);
1664
2737
  } catch (err) {
1665
2738
  logger7.error(`upgrade error: ${err}`, err);
1666
2739
  }