@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.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
- if (host === void 0) {
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
- if (dh === void 0) {
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
- if (dp === void 0) {
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
- constructor(_msg) {
175
- this._msg = _msg;
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 cookieHeaderValue = ctx.request.headers.list("cookie");
840
- const cookie = cookieHeaderValue?.join("; ").split("; ").find((value) => value.startsWith(`${COOKIE_NAME}=`));
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.headers.add("set-cookie", `${COOKIE_NAME}=${expires}; Path=/api; SameSite=strict`);
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
- for (const fn of middleware) {
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
- let index = -1;
920
- return await dispatch(0);
921
- async function dispatch(i) {
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
- await fn(ctx, dispatch.bind(null, i + 1));
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 headers = config.headers;
1256
- if (headers?.allow && headers.allow !== ALL) {
1257
- headers.allow = headers.allow.map((header) => header.toLowerCase());
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._res.end();
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 headers = request.headers;
1417
- return isPreFlight ? headers.list("access-control-request-headers") : Array.from(headers.keys());
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
- async ({ response }, next) => {
1433
- response.headers.set("server", "gateway-server");
1434
- await next();
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._res.end(`io.Gateway Server`);
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
- const route = routes3.get(request.path);
1460
- if (route) {
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
- return await listener(exchange);
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 server2 = createServer(
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
- const request = new HttpServerRequest(req);
1598
- const path = request.path ?? "/";
1599
- const route = routes3.get(path) ?? Array.from(routes3.values()).find((route2) => {
1600
- if (path === "/" && route2.default === true) {
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
  }