@michaelthielemann/kestrel 5.0.2 → 5.0.3
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/dist/boot.js +2 -0
- package/dist/defineConfig.d.ts +26 -16
- package/dist/defineConfig.js +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/triggers/http.d.ts +7 -3
- package/dist/triggers/http.js +17 -8
- package/package.json +1 -1
package/dist/boot.js
CHANGED
|
@@ -237,6 +237,8 @@ export async function boot(input) {
|
|
|
237
237
|
server = createHttpServer(routes, run, logger, {
|
|
238
238
|
maxBodyBytes: http.maxBodyBytes,
|
|
239
239
|
trustProxy: http.trustProxy,
|
|
240
|
+
proxyHops: http.proxyHops,
|
|
241
|
+
...(http.trustedHeader === undefined ? {} : { trustedHeader: http.trustedHeader }),
|
|
240
242
|
allow: http.allow,
|
|
241
243
|
healthPath: http.healthPath,
|
|
242
244
|
inlineTypes: http.inlineTypes,
|
package/dist/defineConfig.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export declare const configSchema: z.ZodObject<{
|
|
|
44
44
|
corsOrigin: z.ZodOptional<z.ZodString>;
|
|
45
45
|
maxBodyBytes: z.ZodDefault<z.ZodNumber>;
|
|
46
46
|
trustProxy: z.ZodDefault<z.ZodBoolean>;
|
|
47
|
+
proxyHops: z.ZodDefault<z.ZodNumber>;
|
|
48
|
+
trustedHeader: z.ZodOptional<z.ZodString>;
|
|
47
49
|
allow: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
48
50
|
healthPath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
49
51
|
inlineTypes: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -61,11 +63,8 @@ export declare const configSchema: z.ZodObject<{
|
|
|
61
63
|
keepAliveMs?: number | undefined;
|
|
62
64
|
}>>;
|
|
63
65
|
}, "strict", z.ZodTypeAny, {
|
|
64
|
-
port: number;
|
|
65
|
-
host: string;
|
|
66
|
-
maxBodyBytes: number;
|
|
67
|
-
trustProxy: boolean;
|
|
68
66
|
allow: string[];
|
|
67
|
+
maxBodyBytes: number;
|
|
69
68
|
healthPath: string | null;
|
|
70
69
|
inlineTypes: string[];
|
|
71
70
|
timeouts: {
|
|
@@ -73,14 +72,16 @@ export declare const configSchema: z.ZodObject<{
|
|
|
73
72
|
headersMs: number;
|
|
74
73
|
keepAliveMs: number;
|
|
75
74
|
};
|
|
75
|
+
trustProxy: boolean;
|
|
76
|
+
proxyHops: number;
|
|
77
|
+
port: number;
|
|
78
|
+
host: string;
|
|
76
79
|
corsOrigin?: string | undefined;
|
|
80
|
+
trustedHeader?: string | undefined;
|
|
77
81
|
}, {
|
|
78
|
-
|
|
79
|
-
host?: string | undefined;
|
|
82
|
+
allow?: string[] | undefined;
|
|
80
83
|
corsOrigin?: string | undefined;
|
|
81
84
|
maxBodyBytes?: number | undefined;
|
|
82
|
-
trustProxy?: boolean | undefined;
|
|
83
|
-
allow?: string[] | undefined;
|
|
84
85
|
healthPath?: string | null | undefined;
|
|
85
86
|
inlineTypes?: string[] | undefined;
|
|
86
87
|
timeouts?: {
|
|
@@ -88,16 +89,18 @@ export declare const configSchema: z.ZodObject<{
|
|
|
88
89
|
headersMs?: number | undefined;
|
|
89
90
|
keepAliveMs?: number | undefined;
|
|
90
91
|
} | undefined;
|
|
92
|
+
trustProxy?: boolean | undefined;
|
|
93
|
+
proxyHops?: number | undefined;
|
|
94
|
+
trustedHeader?: string | undefined;
|
|
95
|
+
port?: number | undefined;
|
|
96
|
+
host?: string | undefined;
|
|
91
97
|
}>]>>;
|
|
92
98
|
pipelinesDir: z.ZodDefault<z.ZodString>;
|
|
93
99
|
shutdownTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
94
100
|
}, "strict", z.ZodTypeAny, {
|
|
95
101
|
http: {
|
|
96
|
-
port: number;
|
|
97
|
-
host: string;
|
|
98
|
-
maxBodyBytes: number;
|
|
99
|
-
trustProxy: boolean;
|
|
100
102
|
allow: string[];
|
|
103
|
+
maxBodyBytes: number;
|
|
101
104
|
healthPath: string | null;
|
|
102
105
|
inlineTypes: string[];
|
|
103
106
|
timeouts: {
|
|
@@ -105,7 +108,12 @@ export declare const configSchema: z.ZodObject<{
|
|
|
105
108
|
headersMs: number;
|
|
106
109
|
keepAliveMs: number;
|
|
107
110
|
};
|
|
111
|
+
trustProxy: boolean;
|
|
112
|
+
proxyHops: number;
|
|
113
|
+
port: number;
|
|
114
|
+
host: string;
|
|
108
115
|
corsOrigin?: string | undefined;
|
|
116
|
+
trustedHeader?: string | undefined;
|
|
109
117
|
} | null;
|
|
110
118
|
modules: {
|
|
111
119
|
use: string;
|
|
@@ -139,12 +147,9 @@ export declare const configSchema: z.ZodObject<{
|
|
|
139
147
|
pipeline: string;
|
|
140
148
|
})[];
|
|
141
149
|
http?: {
|
|
142
|
-
|
|
143
|
-
host?: string | undefined;
|
|
150
|
+
allow?: string[] | undefined;
|
|
144
151
|
corsOrigin?: string | undefined;
|
|
145
152
|
maxBodyBytes?: number | undefined;
|
|
146
|
-
trustProxy?: boolean | undefined;
|
|
147
|
-
allow?: string[] | undefined;
|
|
148
153
|
healthPath?: string | null | undefined;
|
|
149
154
|
inlineTypes?: string[] | undefined;
|
|
150
155
|
timeouts?: {
|
|
@@ -152,6 +157,11 @@ export declare const configSchema: z.ZodObject<{
|
|
|
152
157
|
headersMs?: number | undefined;
|
|
153
158
|
keepAliveMs?: number | undefined;
|
|
154
159
|
} | undefined;
|
|
160
|
+
trustProxy?: boolean | undefined;
|
|
161
|
+
proxyHops?: number | undefined;
|
|
162
|
+
trustedHeader?: string | undefined;
|
|
163
|
+
port?: number | undefined;
|
|
164
|
+
host?: string | undefined;
|
|
155
165
|
} | null | undefined;
|
|
156
166
|
pipelinesDir?: string | undefined;
|
|
157
167
|
shutdownTimeoutMs?: number | undefined;
|
package/dist/defineConfig.js
CHANGED
|
@@ -18,6 +18,8 @@ export const configSchema = z
|
|
|
18
18
|
corsOrigin: z.string().min(1).optional(),
|
|
19
19
|
maxBodyBytes: z.number().int().positive().default(10 * 1024 * 1024),
|
|
20
20
|
trustProxy: z.boolean().default(false),
|
|
21
|
+
proxyHops: z.number().int().positive().default(1),
|
|
22
|
+
trustedHeader: z.string().min(1).optional(),
|
|
21
23
|
allow: z.array(z.string().min(1)).default([]),
|
|
22
24
|
healthPath: z.string().regex(/^\/\S*$/).nullable().default("/health"),
|
|
23
25
|
inlineTypes: z.array(z.string().min(1)).default([]),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { boot, type Kestrel, type BootInput, type Triggers } from "./boot.ts";
|
|
2
|
-
export { matchRoute, routePattern, parseRequestBody, parseQuery, buildResponse, responseForRun, errorResponse, clientIp, BodyTooLarge, type Route, type ParsedBody, type HttpResponse, type HttpTimeouts, type ResponseMeta } from "./triggers/http.ts";
|
|
2
|
+
export { matchRoute, routePattern, parseRequestBody, parseQuery, buildResponse, responseForRun, errorResponse, clientIp, BodyTooLarge, type Route, type ParsedBody, type HttpResponse, type HttpTimeouts, type ClientIpOptions, type ResponseMeta } from "./triggers/http.ts";
|
|
3
3
|
export { defineConfig, type KestrelConfig, type KestrelConfigInput } from "./defineConfig.ts";
|
|
4
4
|
export { defineContract, type Contract } from "./defineContract.ts";
|
|
5
5
|
export { defineModule, type ModuleDefinition, type ModuleInput, type ModuleTriggers, type Deps, type EventEntry, type ContextPath, type JsonSchema, type StepDescription, type StepDescriptions } from "./defineModule.ts";
|
package/dist/triggers/http.d.ts
CHANGED
|
@@ -45,15 +45,19 @@ export interface HttpTimeouts {
|
|
|
45
45
|
headersMs?: number;
|
|
46
46
|
keepAliveMs?: number;
|
|
47
47
|
}
|
|
48
|
-
export interface
|
|
48
|
+
export interface ClientIpOptions {
|
|
49
|
+
trustProxy?: boolean;
|
|
50
|
+
proxyHops?: number;
|
|
51
|
+
trustedHeader?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface HttpOptions extends ClientIpOptions {
|
|
49
54
|
allow?: readonly string[];
|
|
50
55
|
corsOrigin?: string;
|
|
51
56
|
maxBodyBytes?: number;
|
|
52
|
-
trustProxy?: boolean;
|
|
53
57
|
healthPath?: string | null;
|
|
54
58
|
inlineTypes?: readonly string[];
|
|
55
59
|
timeouts?: HttpTimeouts;
|
|
56
60
|
}
|
|
57
|
-
export declare function clientIp(req: IncomingMessage,
|
|
61
|
+
export declare function clientIp(req: IncomingMessage, options?: boolean | ClientIpOptions): string | undefined;
|
|
58
62
|
export declare function createHttpServer(routes: readonly Route[], run: Runner, logger: Logger, options?: HttpOptions): Server;
|
|
59
63
|
export declare function listen(server: Server, port: number, host: string): Promise<AddressInfo>;
|
package/dist/triggers/http.js
CHANGED
|
@@ -215,12 +215,21 @@ function write(res, response) {
|
|
|
215
215
|
res.writeHead(response.status, response.headers);
|
|
216
216
|
res.end(typeof response.body === "string" ? response.body : Buffer.from(response.body));
|
|
217
217
|
}
|
|
218
|
-
|
|
218
|
+
function headerValues(value) {
|
|
219
|
+
return (Array.isArray(value) ? value : value === undefined ? [] : [value])
|
|
220
|
+
.flatMap((v) => v.split(","))
|
|
221
|
+
.map((v) => v.trim())
|
|
222
|
+
.filter((v) => v !== "");
|
|
223
|
+
}
|
|
224
|
+
// A proxy appends the peer it saw to X-Forwarded-For, so only the entries counted from the right are
|
|
225
|
+
// trustworthy; the left end is whatever the client sent. A trusted header names the client outright.
|
|
226
|
+
export function clientIp(req, options = {}) {
|
|
227
|
+
const { trustProxy = false, proxyHops = 1, trustedHeader } = typeof options === "boolean" ? { trustProxy: options } : options;
|
|
228
|
+
if (trustedHeader !== undefined)
|
|
229
|
+
return headerValues(req.headers[trustedHeader.toLowerCase()])[0];
|
|
219
230
|
if (trustProxy) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
if (first)
|
|
223
|
-
return first;
|
|
231
|
+
const chain = headerValues(req.headers["x-forwarded-for"]);
|
|
232
|
+
return chain.length >= proxyHops ? chain[chain.length - proxyHops] : undefined;
|
|
224
233
|
}
|
|
225
234
|
return req.socket.remoteAddress ?? undefined;
|
|
226
235
|
}
|
|
@@ -228,9 +237,9 @@ export function createHttpServer(routes, run, logger, options = {}) {
|
|
|
228
237
|
const started = Date.now();
|
|
229
238
|
const healthPath = options.healthPath === undefined ? "/health" : options.healthPath;
|
|
230
239
|
const allowlist = createAllowlist(options.allow ?? []);
|
|
231
|
-
const
|
|
240
|
+
const peer = { trustProxy: options.trustProxy ?? false, proxyHops: options.proxyHops ?? 1, ...(options.trustedHeader === undefined ? {} : { trustedHeader: options.trustedHeader }) };
|
|
232
241
|
const server = createServer((req, res) => {
|
|
233
|
-
if (allowlist !== null && !allowlist.check(clientIp(req,
|
|
242
|
+
if (allowlist !== null && !allowlist.check(clientIp(req, peer))) {
|
|
234
243
|
sendError(res, 403, "forbidden", requestIdOf(req.headers["x-request-id"]));
|
|
235
244
|
return;
|
|
236
245
|
}
|
|
@@ -281,7 +290,7 @@ export function createHttpServer(routes, run, logger, options = {}) {
|
|
|
281
290
|
if (typeof v === "string")
|
|
282
291
|
headers[k] = v;
|
|
283
292
|
const input = { trigger: { kind: "http", name: `${req.method} ${url.pathname}` }, payload, params: match.params, headers, files: body.files };
|
|
284
|
-
const ip = clientIp(req,
|
|
293
|
+
const ip = clientIp(req, peer);
|
|
285
294
|
if (ip !== undefined)
|
|
286
295
|
input.ip = ip;
|
|
287
296
|
const result = await run(match.route.pipeline, input);
|
package/package.json
CHANGED