@beignet/core 0.0.46 → 0.0.47

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 (51) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +39 -22
  3. package/dist/server/context.d.ts +7 -0
  4. package/dist/server/context.d.ts.map +1 -1
  5. package/dist/server/context.js.map +1 -1
  6. package/dist/server/hooks/logging.d.ts +3 -0
  7. package/dist/server/hooks/logging.d.ts.map +1 -1
  8. package/dist/server/hooks/logging.js +18 -7
  9. package/dist/server/hooks/logging.js.map +1 -1
  10. package/dist/server/hooks/rate-limit.d.ts +7 -7
  11. package/dist/server/hooks/rate-limit.d.ts.map +1 -1
  12. package/dist/server/hooks/rate-limit.js +15 -9
  13. package/dist/server/hooks/rate-limit.js.map +1 -1
  14. package/dist/server/hooks/security.d.ts +4 -3
  15. package/dist/server/hooks/security.d.ts.map +1 -1
  16. package/dist/server/hooks/security.js +5 -3
  17. package/dist/server/hooks/security.js.map +1 -1
  18. package/dist/server/http.d.ts +12 -0
  19. package/dist/server/http.d.ts.map +1 -1
  20. package/dist/server/request-executor.d.ts +5 -2
  21. package/dist/server/request-executor.d.ts.map +1 -1
  22. package/dist/server/request-executor.js +21 -2
  23. package/dist/server/request-executor.js.map +1 -1
  24. package/dist/server/server-context.d.ts +2 -1
  25. package/dist/server/server-context.d.ts.map +1 -1
  26. package/dist/server/server-context.js +2 -1
  27. package/dist/server/server-context.js.map +1 -1
  28. package/dist/server/server.d.ts +9 -0
  29. package/dist/server/server.d.ts.map +1 -1
  30. package/dist/server/server.js +14 -2
  31. package/dist/server/server.js.map +1 -1
  32. package/dist/server/trusted-proxy-internal.d.ts +4 -0
  33. package/dist/server/trusted-proxy-internal.d.ts.map +1 -0
  34. package/dist/server/trusted-proxy-internal.js +27 -0
  35. package/dist/server/trusted-proxy-internal.js.map +1 -0
  36. package/dist/server/trusted-proxy.d.ts +11 -6
  37. package/dist/server/trusted-proxy.d.ts.map +1 -1
  38. package/dist/server/trusted-proxy.js +6 -10
  39. package/dist/server/trusted-proxy.js.map +1 -1
  40. package/package.json +1 -1
  41. package/skills/app-architecture/SKILL.md +5 -2
  42. package/src/server/context.ts +7 -0
  43. package/src/server/hooks/logging.ts +35 -7
  44. package/src/server/hooks/rate-limit.ts +25 -15
  45. package/src/server/hooks/security.ts +11 -7
  46. package/src/server/http.ts +18 -1
  47. package/src/server/request-executor.ts +43 -2
  48. package/src/server/server-context.ts +2 -1
  49. package/src/server/server.ts +30 -1
  50. package/src/server/trusted-proxy-internal.ts +41 -0
  51. package/src/server/trusted-proxy.ts +20 -17
@@ -0,0 +1,41 @@
1
+ import type { TrustedProxyConfig } from "./trusted-proxy.js";
2
+
3
+ export function requireTrustedProxyHeaderName(
4
+ name: string,
5
+ optionName: string,
6
+ ): string {
7
+ const header = name.trim();
8
+ if (!header) {
9
+ throw new Error(`${optionName} must be a non-empty header name.`);
10
+ }
11
+
12
+ try {
13
+ new Headers().get(header);
14
+ } catch {
15
+ throw new Error(`${optionName} must be a valid HTTP header name.`);
16
+ }
17
+
18
+ return header;
19
+ }
20
+
21
+ export function assertValidTrustedProxyConfig(
22
+ config: TrustedProxyConfig,
23
+ ): void {
24
+ if (!config) return;
25
+
26
+ if (config.hostHeader !== undefined && config.hostHeader !== false) {
27
+ requireTrustedProxyHeaderName(config.hostHeader, "trustedProxy.hostHeader");
28
+ }
29
+ if (config.protocolHeader !== undefined && config.protocolHeader !== false) {
30
+ requireTrustedProxyHeaderName(
31
+ config.protocolHeader,
32
+ "trustedProxy.protocolHeader",
33
+ );
34
+ }
35
+ if (typeof config.clientIp === "object") {
36
+ requireTrustedProxyHeaderName(
37
+ config.clientIp.header,
38
+ "trustedProxy.clientIp.header",
39
+ );
40
+ }
41
+ }
@@ -1,4 +1,5 @@
1
1
  import type { HttpRequestLike } from "./http.js";
2
+ import { requireTrustedProxyHeaderName } from "./trusted-proxy-internal.js";
2
3
 
3
4
  /**
4
5
  * Header source used to resolve a client IP after an app has explicitly opted
@@ -51,27 +52,32 @@ export interface TrustedRequestInfo {
51
52
  /**
52
53
  * URL as seen by the app or reconstructed from trusted proxy headers.
53
54
  */
54
- url: URL;
55
+ readonly url: Readonly<URL>;
55
56
  /**
56
57
  * External request origin.
57
58
  */
58
- origin: string;
59
+ readonly origin: string;
59
60
  /**
60
61
  * External request protocol without a trailing colon.
61
62
  */
62
- protocol: "http" | "https";
63
+ readonly protocol: "http" | "https";
63
64
  /**
64
65
  * External request host, including port when present.
65
66
  */
66
- host: string;
67
+ readonly host: string;
67
68
  /**
68
69
  * Resolved client IP when a trusted client-IP source is configured.
69
70
  */
70
- clientIp?: string;
71
+ readonly clientIp?: string;
72
+ /**
73
+ * Whether the server policy configured a trusted client-IP source. This can
74
+ * be true while `clientIp` is absent when the expected header is missing.
75
+ */
76
+ readonly clientIpTrusted: boolean;
71
77
  /**
72
78
  * Whether forwarding headers were eligible to affect this result.
73
79
  */
74
- trustedProxy: boolean;
80
+ readonly trustedProxy: boolean;
75
81
  }
76
82
 
77
83
  const DEFAULT_PROTOCOL_HEADER = "x-forwarded-proto";
@@ -92,14 +98,6 @@ function firstHeaderValue(
92
98
  return splitForwardedHeader(req.headers.get(header))[0];
93
99
  }
94
100
 
95
- function requireHeaderName(name: string, optionName: string): string {
96
- const header = name.trim();
97
- if (!header) {
98
- throw new Error(`${optionName} must be a non-empty header name.`);
99
- }
100
- return header;
101
- }
102
-
103
101
  function normalizeProtocol(
104
102
  value: string | undefined,
105
103
  ): "http" | "https" | undefined {
@@ -172,7 +170,10 @@ export function resolveTrustedClientIp(
172
170
  if (typeof source === "object") {
173
171
  return firstHeaderValue(
174
172
  req,
175
- requireHeaderName(source.header, "trustedProxy.clientIp.header"),
173
+ requireTrustedProxyHeaderName(
174
+ source.header,
175
+ "trustedProxy.clientIp.header",
176
+ ),
176
177
  );
177
178
  }
178
179
 
@@ -205,6 +206,7 @@ export function resolveTrustedRequest(
205
206
  origin: baseUrl.origin,
206
207
  protocol: baseProtocol,
207
208
  host: baseHost,
209
+ clientIpTrusted: false,
208
210
  trustedProxy: false,
209
211
  };
210
212
  }
@@ -212,14 +214,14 @@ export function resolveTrustedRequest(
212
214
  const protocolHeader =
213
215
  config.protocolHeader === false
214
216
  ? undefined
215
- : requireHeaderName(
217
+ : requireTrustedProxyHeaderName(
216
218
  config.protocolHeader ?? DEFAULT_PROTOCOL_HEADER,
217
219
  "trustedProxy.protocolHeader",
218
220
  );
219
221
  const hostHeader =
220
222
  config.hostHeader === false
221
223
  ? undefined
222
- : requireHeaderName(
224
+ : requireTrustedProxyHeaderName(
223
225
  config.hostHeader ?? DEFAULT_HOST_HEADER,
224
226
  "trustedProxy.hostHeader",
225
227
  );
@@ -244,6 +246,7 @@ export function resolveTrustedRequest(
244
246
  protocol,
245
247
  host,
246
248
  ...(clientIp !== undefined ? { clientIp } : {}),
249
+ clientIpTrusted: Boolean(config.clientIp),
247
250
  trustedProxy: true,
248
251
  };
249
252
  }