@beignet/core 0.0.45 → 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.
- package/CHANGELOG.md +12 -0
- package/README.md +69 -22
- package/dist/server/context.d.ts +7 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js.map +1 -1
- package/dist/server/hooks/logging.d.ts +3 -0
- package/dist/server/hooks/logging.d.ts.map +1 -1
- package/dist/server/hooks/logging.js +18 -7
- package/dist/server/hooks/logging.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +7 -7
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +15 -9
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/hooks/security.d.ts +4 -3
- package/dist/server/hooks/security.d.ts.map +1 -1
- package/dist/server/hooks/security.js +5 -3
- package/dist/server/hooks/security.js.map +1 -1
- package/dist/server/http.d.ts +12 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/request-executor.d.ts +5 -2
- package/dist/server/request-executor.d.ts.map +1 -1
- package/dist/server/request-executor.js +21 -2
- package/dist/server/request-executor.js.map +1 -1
- package/dist/server/server-context.d.ts +2 -1
- package/dist/server/server-context.d.ts.map +1 -1
- package/dist/server/server-context.js +2 -1
- package/dist/server/server-context.js.map +1 -1
- package/dist/server/server.d.ts +9 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +14 -2
- package/dist/server/server.js.map +1 -1
- package/dist/server/trusted-proxy-internal.d.ts +4 -0
- package/dist/server/trusted-proxy-internal.d.ts.map +1 -0
- package/dist/server/trusted-proxy-internal.js +27 -0
- package/dist/server/trusted-proxy-internal.js.map +1 -0
- package/dist/server/trusted-proxy.d.ts +11 -6
- package/dist/server/trusted-proxy.d.ts.map +1 -1
- package/dist/server/trusted-proxy.js +6 -10
- package/dist/server/trusted-proxy.js.map +1 -1
- package/package.json +3 -1
- package/skills/app-architecture/SKILL.md +5 -2
- package/src/server/context.ts +7 -0
- package/src/server/hooks/logging.ts +35 -7
- package/src/server/hooks/rate-limit.ts +25 -15
- package/src/server/hooks/security.ts +11 -7
- package/src/server/http.ts +18 -1
- package/src/server/request-executor.ts +43 -2
- package/src/server/server-context.ts +2 -1
- package/src/server/server.ts +30 -1
- package/src/server/trusted-proxy-internal.ts +41 -0
- package/src/server/trusted-proxy.ts +20 -17
package/src/server/server.ts
CHANGED
|
@@ -73,6 +73,12 @@ import {
|
|
|
73
73
|
} from "./route-matching.js";
|
|
74
74
|
import type { RuntimeIntegrityCheck } from "./runtime-integrity.js";
|
|
75
75
|
import { runRuntimeIntegrityCheck } from "./runtime-integrity.js";
|
|
76
|
+
import {
|
|
77
|
+
resolveTrustedRequest,
|
|
78
|
+
type TrustedProxyConfig,
|
|
79
|
+
type TrustedRequestInfo,
|
|
80
|
+
} from "./trusted-proxy.js";
|
|
81
|
+
import { assertValidTrustedProxyConfig } from "./trusted-proxy-internal.js";
|
|
76
82
|
import {
|
|
77
83
|
createUseCaseRouteHandler,
|
|
78
84
|
isUseCaseRouteDef,
|
|
@@ -107,6 +113,15 @@ type AnyServiceProvider = ServiceProvider<
|
|
|
107
113
|
any
|
|
108
114
|
>;
|
|
109
115
|
|
|
116
|
+
function copyTrustedRequestInfo(
|
|
117
|
+
requestInfo: TrustedRequestInfo,
|
|
118
|
+
): TrustedRequestInfo {
|
|
119
|
+
return {
|
|
120
|
+
...requestInfo,
|
|
121
|
+
url: new URL(requestInfo.url),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
110
125
|
/**
|
|
111
126
|
* Options for creating a Beignet server instance.
|
|
112
127
|
*/
|
|
@@ -155,6 +170,14 @@ export type CreateServerOptions<
|
|
|
155
170
|
Ports & InferProviderPorts<Providers>,
|
|
156
171
|
ServiceInput
|
|
157
172
|
>;
|
|
173
|
+
/**
|
|
174
|
+
* Explicit policy for trusting proxy- or edge-provided request metadata.
|
|
175
|
+
*
|
|
176
|
+
* Forwarding headers are ignored by default. Configure this only when every
|
|
177
|
+
* request reaches the app through a trusted platform or reverse proxy that
|
|
178
|
+
* strips or normalizes those headers.
|
|
179
|
+
*/
|
|
180
|
+
trustedProxy?: TrustedProxyConfig;
|
|
158
181
|
/**
|
|
159
182
|
* Server hooks that wrap every registered route.
|
|
160
183
|
*/
|
|
@@ -387,10 +410,12 @@ export async function createServer<
|
|
|
387
410
|
...((options.hooks ?? []) as ServerHook<Ctx, FinalPorts>[]),
|
|
388
411
|
];
|
|
389
412
|
const contracts = options.routes ? contractsFromRoutes(options.routes) : [];
|
|
413
|
+
const trustedProxy = options.trustedProxy ?? false;
|
|
414
|
+
assertValidTrustedProxyConfig(trustedProxy);
|
|
390
415
|
|
|
391
416
|
// Fail startup on hook misconfiguration before provider setup runs.
|
|
392
417
|
for (const hook of hooks) {
|
|
393
|
-
hook.validate?.({ contracts });
|
|
418
|
+
hook.validate?.({ contracts, trustedProxy });
|
|
394
419
|
}
|
|
395
420
|
|
|
396
421
|
const resolvedContext = resolveServerContext<Ctx, FinalPorts, ServiceInput>(
|
|
@@ -403,11 +428,13 @@ export async function createServer<
|
|
|
403
428
|
const createRequestContext = async (
|
|
404
429
|
req: HttpRequestLike,
|
|
405
430
|
contract?: HttpContractConfig,
|
|
431
|
+
requestInfo: TrustedRequestInfo = resolveTrustedRequest(req, trustedProxy),
|
|
406
432
|
): Promise<Ctx> => {
|
|
407
433
|
const { requestId, trace } = instrumentation.prepareRequest(req);
|
|
408
434
|
return finalizeContext(
|
|
409
435
|
await resolvedContext.request({
|
|
410
436
|
req,
|
|
437
|
+
requestInfo: copyTrustedRequestInfo(requestInfo),
|
|
411
438
|
ports: finalPorts,
|
|
412
439
|
contract,
|
|
413
440
|
requestId,
|
|
@@ -502,6 +529,8 @@ export async function createServer<
|
|
|
502
529
|
const contextRuntime = {
|
|
503
530
|
createRequestContext,
|
|
504
531
|
finalizeContext,
|
|
532
|
+
resolveRequestInfo: (req: HttpRequestLike) =>
|
|
533
|
+
resolveTrustedRequest(req, trustedProxy),
|
|
505
534
|
};
|
|
506
535
|
|
|
507
536
|
let serviceContextsAvailable = false;
|
|
@@ -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
|
-
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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
|
}
|