expediate 1.0.4 → 1.0.6

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 (69) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/CONTRIBUTING.md +150 -0
  3. package/LICENSE +16 -16
  4. package/README.md +330 -444
  5. package/dist/apis.d.ts +504 -27
  6. package/dist/apis.d.ts.map +1 -1
  7. package/dist/apis.js +618 -107
  8. package/dist/apis.js.map +1 -1
  9. package/dist/cjs/index.js +4066 -0
  10. package/dist/cjs/package.json +1 -0
  11. package/dist/git.d.ts +72 -9
  12. package/dist/git.d.ts.map +1 -1
  13. package/dist/git.js +129 -74
  14. package/dist/git.js.map +1 -1
  15. package/dist/http-objects.d.ts +26 -0
  16. package/dist/http-objects.d.ts.map +1 -0
  17. package/dist/http-objects.js +588 -0
  18. package/dist/http-objects.js.map +1 -0
  19. package/dist/index.d.ts +18 -13
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +15 -24
  22. package/dist/index.js.map +1 -1
  23. package/dist/jwt-auth.d.ts +158 -57
  24. package/dist/jwt-auth.d.ts.map +1 -1
  25. package/dist/jwt-auth.js +447 -207
  26. package/dist/jwt-auth.js.map +1 -1
  27. package/dist/middleware.d.ts +476 -0
  28. package/dist/middleware.d.ts.map +1 -0
  29. package/dist/middleware.js +647 -0
  30. package/dist/middleware.js.map +1 -0
  31. package/dist/mimetypes.json +882 -1
  32. package/dist/misc.d.ts +268 -25
  33. package/dist/misc.d.ts.map +1 -1
  34. package/dist/misc.js +449 -168
  35. package/dist/misc.js.map +1 -1
  36. package/dist/openapi.d.ts +433 -0
  37. package/dist/openapi.d.ts.map +1 -0
  38. package/dist/openapi.js +624 -0
  39. package/dist/openapi.js.map +1 -0
  40. package/dist/router-types.d.ts +760 -0
  41. package/dist/router-types.d.ts.map +1 -0
  42. package/dist/router-types.js +23 -0
  43. package/dist/router-types.js.map +1 -0
  44. package/dist/router.d.ts +37 -201
  45. package/dist/router.d.ts.map +1 -1
  46. package/dist/router.js +502 -244
  47. package/dist/router.js.map +1 -1
  48. package/dist/static.d.ts +3 -3
  49. package/dist/static.d.ts.map +1 -1
  50. package/dist/static.js +164 -105
  51. package/dist/static.js.map +1 -1
  52. package/docs/THREAT_MODEL.md +52 -0
  53. package/docs/api-builder-v2-design.md +644 -0
  54. package/docs/api-builder-v3-design.md +397 -0
  55. package/docs/api-builder.md +454 -0
  56. package/docs/benchmark.md +27 -0
  57. package/docs/body-parsing.md +223 -0
  58. package/docs/errors.md +359 -0
  59. package/docs/expediate.png +0 -0
  60. package/docs/git.md +139 -0
  61. package/docs/jwt-auth.md +251 -0
  62. package/docs/logo.svg +12 -0
  63. package/docs/middleware.md +264 -0
  64. package/docs/openapi.md +180 -0
  65. package/docs/router.md +356 -0
  66. package/docs/static.md +128 -0
  67. package/docs/wiki.json +123 -0
  68. package/package.json +47 -8
  69. package/.npmignore +0 -16
@@ -0,0 +1,647 @@
1
+ /* Copyright 2021 Fabien Bavent
2
+ *
3
+ * Permission is hereby granted, free of charge, to any person obtaining a
4
+ * copy of this software and associated documentation files (the "Software"),
5
+ * to deal in the Software without restriction, including without limitation
6
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ * and/or sell copies of the Software, and to permit persons to whom the
8
+ * Software is furnished to do so, subject to the following conditions:
9
+ *
10
+ * The above copyright notice and this permission notice shall be included
11
+ * in all copies or substantial portions of the Software.
12
+ *
13
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ * DEALINGS IN THE SOFTWARE.
20
+ */
21
+ 'use strict';
22
+ import * as crypto from 'crypto';
23
+ import * as zlib from 'zlib';
24
+ // ---------------------------------------------------------------------------
25
+ // Internal helpers
26
+ // ---------------------------------------------------------------------------
27
+ /**
28
+ * Convert a string or Buffer value to a `Buffer`.
29
+ *
30
+ * @param chunk - The data to convert.
31
+ * @param encoding - Character encoding to use when `chunk` is a string.
32
+ * @returns A `Buffer` containing the bytes of `chunk`.
33
+ */
34
+ function toBuffer(chunk, encoding = 'utf8') {
35
+ return Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
36
+ }
37
+ /**
38
+ * Response compression middleware.
39
+ *
40
+ * Reads the `Accept-Encoding` request header and transparently compresses the
41
+ * response body using Brotli, gzip, or deflate (tested in that preference
42
+ * order). Responses whose total body is smaller than `threshold` bytes are
43
+ * sent uncompressed to avoid wasting CPU on tiny payloads.
44
+ *
45
+ * The middleware monkey-patches `res.write` and `res.end` so that all
46
+ * downstream code (including `res.json`, `res.send`, and manual writes)
47
+ * passes through the compressor without any changes to handler code.
48
+ *
49
+ * The `Content-Encoding` and `Vary: Accept-Encoding` headers are set
50
+ * automatically. `Content-Length` is removed because the compressed length
51
+ * cannot be known in advance.
52
+ *
53
+ * @param opts - Optional compression settings.
54
+ * @returns An Express-compatible middleware function.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const app = createRouter();
59
+ * app.use(compress()); // at the top — must come first
60
+ * app.get('/api/data', (_req, res) => res.json(bigPayload));
61
+ * ```
62
+ */
63
+ export function compress(opts) {
64
+ const threshold = opts?.threshold ?? 1024;
65
+ const brEnabled = opts?.br !== false;
66
+ const brotliQuality = opts?.brotliQuality ?? 4;
67
+ const gzipLevel = opts?.gzipLevel ?? zlib.constants.Z_DEFAULT_COMPRESSION;
68
+ return (req, res, next) => {
69
+ // User-supplied filter.
70
+ if (opts?.filter && !opts.filter(req, res))
71
+ return next();
72
+ const ae = (req.headers['accept-encoding']) ?? '';
73
+ // Negotiate encoding: Brotli > gzip > deflate.
74
+ let compressor = null;
75
+ let encoding = '';
76
+ if (brEnabled && /\bbr\b/.test(ae)) {
77
+ compressor = zlib.createBrotliCompress({
78
+ params: { [zlib.constants.BROTLI_PARAM_QUALITY]: brotliQuality },
79
+ });
80
+ encoding = 'br';
81
+ }
82
+ else if (/\bgzip\b/.test(ae)) {
83
+ compressor = zlib.createGzip({ level: gzipLevel });
84
+ encoding = 'gzip';
85
+ }
86
+ else if (/\bdeflate\b/.test(ae)) {
87
+ compressor = zlib.createDeflate({ level: gzipLevel });
88
+ encoding = 'deflate';
89
+ }
90
+ if (!compressor)
91
+ return next();
92
+ const comp = compressor;
93
+ const rawRes = res;
94
+ // Capture original write / end before we override them.
95
+ const origWrite = rawRes.write.bind(rawRes);
96
+ const origEnd = rawRes.end.bind(rawRes);
97
+ // Route compressor output back to the raw socket.
98
+ comp.on('data', (chunk) => { origWrite(chunk); });
99
+ comp.on('end', () => { origEnd(); });
100
+ comp.on('error', (e) => {
101
+ console.error('[compress] stream error:', e.message);
102
+ if (!rawRes.writableEnded)
103
+ origEnd();
104
+ });
105
+ // Buffer incoming data so we can decide compress vs bypass at end().
106
+ const pending = [];
107
+ let totalBytes = 0;
108
+ let decided = false; // true once we committed to compress or bypass
109
+ let doCompress = false;
110
+ /**
111
+ * Commit to compression: flush all buffered pending data to the compressor
112
+ * and mark `decided = true, doCompress = true`.
113
+ *
114
+ * @param andEnd - When `true`, also call `comp.end()` to flush and close
115
+ * the compressor after writing pending data.
116
+ */
117
+ const startCompressing = (andEnd) => {
118
+ decided = true;
119
+ doCompress = true;
120
+ res.setHeader('Content-Encoding', encoding);
121
+ res.setHeader('Vary', 'Accept-Encoding');
122
+ res.removeHeader('Content-Length');
123
+ for (const buf of pending)
124
+ comp.write(buf);
125
+ if (andEnd)
126
+ comp.end();
127
+ };
128
+ /**
129
+ * Commit to bypassing compression (total bytes below threshold).
130
+ * Undoes the tentative headers and writes buffered data directly.
131
+ */
132
+ const skipCompression = () => {
133
+ decided = true;
134
+ doCompress = false;
135
+ comp.destroy();
136
+ if (totalBytes > 0)
137
+ res.setHeader('Content-Length', totalBytes);
138
+ for (const buf of pending)
139
+ origWrite(buf);
140
+ };
141
+ // Override res.write.
142
+ res.write = (chunk, encOrCb, _cb) => {
143
+ const enc = typeof encOrCb === 'string' ? encOrCb : 'utf8';
144
+ const buf = toBuffer(chunk, enc);
145
+ if (decided) {
146
+ // Already committed — write directly to the right destination.
147
+ return doCompress ? comp.write(buf) : origWrite(buf);
148
+ }
149
+ pending.push(buf);
150
+ totalBytes += buf.length;
151
+ // Crossed threshold mid-stream: start compression now.
152
+ if (totalBytes >= threshold)
153
+ startCompressing(false);
154
+ return true;
155
+ };
156
+ // Override res.end.
157
+ res.end = (chunk, encOrCb, _cb) => {
158
+ if (typeof chunk === 'function')
159
+ chunk = undefined;
160
+ if (typeof encOrCb === 'function')
161
+ encOrCb = undefined;
162
+ if (chunk != null) {
163
+ const enc = typeof encOrCb === 'string' ? encOrCb : 'utf8';
164
+ const buf = toBuffer(chunk, enc);
165
+ if (decided) {
166
+ if (doCompress)
167
+ comp.write(buf);
168
+ else
169
+ origWrite(buf);
170
+ }
171
+ else {
172
+ pending.push(buf);
173
+ totalBytes += buf.length;
174
+ }
175
+ }
176
+ if (!decided) {
177
+ if (totalBytes >= threshold)
178
+ startCompressing(true);
179
+ else {
180
+ skipCompression();
181
+ origEnd();
182
+ }
183
+ }
184
+ else if (doCompress) {
185
+ comp.end();
186
+ }
187
+ else {
188
+ origEnd();
189
+ }
190
+ return rawRes;
191
+ };
192
+ next();
193
+ };
194
+ }
195
+ /**
196
+ * Request ID middleware.
197
+ *
198
+ * Attaches a unique identifier to every request as `req.id` and echoes it
199
+ * back in the response header (default: `X-Request-ID`). The ID is taken
200
+ * from the incoming header when `allowFromHeader` is `true` (the default);
201
+ * otherwise a new UUID is generated with `crypto.randomUUID()`.
202
+ *
203
+ * Use `req.id` in log statements and error responses to correlate distributed
204
+ * traces back to a single originating request.
205
+ *
206
+ * @param opts - Optional configuration.
207
+ * @returns An Express-compatible middleware function.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * app.use(requestId());
212
+ * app.use(logger()); // logger can now include req.id in output
213
+ * app.get('/health', (req, res) => res.json({ id: req.id, status: 'ok' }));
214
+ * ```
215
+ */
216
+ export function requestId(opts) {
217
+ const header = (opts?.header ?? 'x-request-id').toLowerCase();
218
+ const allowFromHeader = opts?.allowFromHeader !== false;
219
+ const generator = opts?.generator ?? (() => crypto.randomUUID());
220
+ return (req, res, next) => {
221
+ const incoming = req.headers[header];
222
+ const id = (allowFromHeader && incoming) ? incoming : generator();
223
+ req.id = id;
224
+ res.setHeader(header, id);
225
+ next();
226
+ };
227
+ }
228
+ /**
229
+ * In-memory sliding-window rate limiting middleware.
230
+ *
231
+ * Uses a `Map<key, timestamp[]>` to track request timestamps per client.
232
+ * On each request the list is pruned to the current window, then checked
233
+ * against `max`. No external dependencies are required.
234
+ *
235
+ * **Caveats:**
236
+ * - State is held in memory and is lost on process restart.
237
+ * - Not suitable for multi-process deployments without a shared store.
238
+ *
239
+ * @param opts - Rate-limit configuration. `windowMs` and `max` are required.
240
+ * @returns An Express-compatible middleware function.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * // Max 100 requests per minute per IP:
245
+ * app.use(rateLimit({ windowMs: 60_000, max: 100 }));
246
+ *
247
+ * // Stricter limit on a specific route:
248
+ * app.post('/auth/login', rateLimit({ windowMs: 60_000, max: 5 }), loginHandler);
249
+ * ```
250
+ */
251
+ export function rateLimit(opts) {
252
+ const windowMs = opts.windowMs;
253
+ const max = opts.max;
254
+ const keyBy = opts.keyBy ?? ((req) => req.ip ?? '');
255
+ const message = opts.message ?? 'Too Many Requests';
256
+ const statusCode = opts.statusCode ?? 429;
257
+ const sendHeaders = opts.headers !== false;
258
+ // Map<clientKey, requestTimestamps[]>
259
+ const store = new Map();
260
+ return (req, res, next) => {
261
+ const key = keyBy(req);
262
+ const now = Date.now();
263
+ const windowStart = now - windowMs;
264
+ // Prune expired timestamps for this key.
265
+ const timestamps = (store.get(key) ?? []).filter((t) => t > windowStart);
266
+ timestamps.push(now);
267
+ store.set(key, timestamps);
268
+ const count = timestamps.length;
269
+ const remaining = Math.max(0, max - count);
270
+ // Reset = when the oldest request in the current window will fall out.
271
+ const resetAt = timestamps.length > 0
272
+ ? Math.ceil((timestamps[0] + windowMs) / 1000)
273
+ : Math.ceil((now + windowMs) / 1000);
274
+ if (sendHeaders) {
275
+ res.setHeader('X-RateLimit-Limit', String(max));
276
+ res.setHeader('X-RateLimit-Remaining', String(remaining));
277
+ res.setHeader('X-RateLimit-Reset', String(resetAt));
278
+ }
279
+ if (count > max) {
280
+ res.setHeader('Retry-After', String(Math.ceil(windowMs / 1000)));
281
+ res.statusCode = statusCode;
282
+ res.end(message);
283
+ return;
284
+ }
285
+ next();
286
+ };
287
+ }
288
+ /**
289
+ * Response caching header middleware.
290
+ *
291
+ * Sets `Cache-Control`, and optionally `Expires` and `Vary` response headers
292
+ * based on the supplied options. All directives are optional — only the
293
+ * ones you specify are included in the header value.
294
+ *
295
+ * Mount at the router level for a global default, or on individual routes to
296
+ * apply per-route caching policies.
297
+ *
298
+ * @param opts - Cache policy options.
299
+ * @returns An Express-compatible middleware function.
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * // Global: 5-minute browser cache, CDN-agnostic
304
+ * app.use(cacheControl({ maxAge: 300, public: true }));
305
+ *
306
+ * // Per-route: content-addressed asset, cache forever
307
+ * app.get('/static/app.:hash.js', cacheControl({ maxAge: 31_536_000, immutable: true }), serveStatic('public'));
308
+ *
309
+ * // No caching at all
310
+ * app.use('/api', cacheControl({ noStore: true }));
311
+ * ```
312
+ */
313
+ export function cacheControl(opts = {}) {
314
+ // Pre-compute the Cache-Control value — it is the same for every response.
315
+ const directives = [];
316
+ if (opts.private)
317
+ directives.push('private');
318
+ if (opts.public)
319
+ directives.push('public');
320
+ if (opts.noStore)
321
+ directives.push('no-store');
322
+ if (opts.noCache)
323
+ directives.push('no-cache');
324
+ if (opts.mustRevalidate)
325
+ directives.push('must-revalidate');
326
+ if (opts.immutable)
327
+ directives.push('immutable');
328
+ if (opts.maxAge != null)
329
+ directives.push(`max-age=${opts.maxAge}`);
330
+ if (opts.sMaxAge != null)
331
+ directives.push(`s-maxage=${opts.sMaxAge}`);
332
+ const cacheControlValue = directives.join(', ');
333
+ const varyValue = Array.isArray(opts.vary)
334
+ ? opts.vary.join(', ')
335
+ : (opts.vary ?? null);
336
+ return (_req, res, next) => {
337
+ if (cacheControlValue)
338
+ res.setHeader('Cache-Control', cacheControlValue);
339
+ if (opts.maxAge != null) {
340
+ const expires = new Date(Date.now() + opts.maxAge * 1000);
341
+ res.setHeader('Expires', expires.toUTCString());
342
+ }
343
+ if (varyValue)
344
+ res.setHeader('Vary', varyValue);
345
+ next();
346
+ };
347
+ }
348
+ /** HTTP methods that do not mutate server state and require no CSRF check. */
349
+ const SAFE_CSRF_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']);
350
+ /**
351
+ * CSRF protection middleware (double-submit cookie pattern).
352
+ *
353
+ * On every request the middleware:
354
+ * 1. Reads (or generates) a random 256-bit hex token stored in a cookie.
355
+ * 2. Attaches `req.csrfToken()` so handlers can embed the token in forms /
356
+ * single-page applications.
357
+ * 3. For state-mutating requests (POST / PUT / PATCH / DELETE) it validates
358
+ * that the `X-CSRF-Token` header (or `_csrf` body field) matches the
359
+ * cookie value, responding **403** on mismatch.
360
+ *
361
+ * The cookie is **not** `HttpOnly` so that browser JavaScript can read the
362
+ * value and include it in subsequent AJAX request headers.
363
+ *
364
+ * @param opts - Optional configuration.
365
+ * @returns An Express-compatible middleware function.
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * app.use(csrf());
370
+ * app.get('/form', (req, res) =>
371
+ * res.send(`<input type="hidden" name="_csrf" value="${req.csrfToken!()}">`));
372
+ * app.post('/submit', (req, res) => res.send('ok')); // validated automatically
373
+ * ```
374
+ */
375
+ export function csrf(opts) {
376
+ const cookieName = opts?.cookieName ?? '_csrf';
377
+ const headerName = (opts?.headerName ?? 'x-csrf-token').toLowerCase();
378
+ const fieldName = opts?.fieldName ?? '_csrf';
379
+ const secure = opts?.secure ?? false;
380
+ const sameSite = opts?.sameSite ?? 'Strict';
381
+ return (req, res, next) => {
382
+ // Generate or reuse existing token from the incoming cookie.
383
+ const existing = req.cookies?.[cookieName];
384
+ const token = (typeof existing === 'string' && existing.length > 0)
385
+ ? existing
386
+ : crypto.randomBytes(32).toString('hex');
387
+ // Refresh the cookie when we generated a new token.
388
+ if (!existing) {
389
+ let cookieStr = `${cookieName}=${token}; Path=/; SameSite=${sameSite}`;
390
+ if (secure)
391
+ cookieStr += '; Secure';
392
+ // Append without clobbering other Set-Cookie headers.
393
+ const prev = res.getHeader('Set-Cookie');
394
+ if (prev == null)
395
+ res.setHeader('Set-Cookie', cookieStr);
396
+ else if (Array.isArray(prev))
397
+ res.setHeader('Set-Cookie', [...prev, cookieStr]);
398
+ else
399
+ res.setHeader('Set-Cookie', [prev, cookieStr]);
400
+ }
401
+ // Expose helper so handlers can embed the token in responses.
402
+ req.csrfToken = () => token;
403
+ // Safe methods skip validation entirely.
404
+ if (SAFE_CSRF_METHODS.has(req.method ?? ''))
405
+ return next();
406
+ // Validate: prefer header, fall back to parsed body field.
407
+ const submitted = req.headers[headerName] ??
408
+ (req.body?.[fieldName]) ??
409
+ '';
410
+ if (!submitted || submitted !== token) {
411
+ res.statusCode = 403;
412
+ res.end('Forbidden: invalid CSRF token');
413
+ return;
414
+ }
415
+ next();
416
+ };
417
+ }
418
+ /**
419
+ * Security-hardening response headers middleware.
420
+ *
421
+ * Sets a sensible baseline of HTTP response headers that reduce the attack
422
+ * surface for common web vulnerabilities (clickjacking, MIME sniffing, XSS,
423
+ * etc.). Every header can be individually disabled or overridden.
424
+ *
425
+ * Mount once at the top of the router so all responses are covered:
426
+ *
427
+ * ```ts
428
+ * app.use(securityHeaders());
429
+ * ```
430
+ *
431
+ * **Headers set by default:**
432
+ *
433
+ * | Header | Default value |
434
+ * |---------------------------|--------------------------------------------|
435
+ * | Strict-Transport-Security | max-age=15552000; includeSubDomains |
436
+ * | X-Frame-Options | SAMEORIGIN |
437
+ * | X-Content-Type-Options | nosniff |
438
+ * | Referrer-Policy | strict-origin-when-cross-origin |
439
+ * | Permissions-Policy | geolocation=(), microphone=(), camera=() |
440
+ * | X-XSS-Protection | 0 |
441
+ *
442
+ * @param opts - Per-header overrides. Omit to use all defaults.
443
+ * @returns An Express-compatible middleware function.
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * // Disable HSTS on a plain-HTTP development server:
448
+ * app.use(securityHeaders({ hsts: false }));
449
+ *
450
+ * // Deny all framing (not just same-origin):
451
+ * app.use(securityHeaders({ frameOptions: 'DENY' }));
452
+ *
453
+ * // Custom Permissions-Policy:
454
+ * app.use(securityHeaders({
455
+ * permissionsPolicy: 'geolocation=(), payment=(self)',
456
+ * }));
457
+ * ```
458
+ */
459
+ // ===========================================================================
460
+ // 7 · conditionalGet — RFC 7232 conditional GET / 304 Not Modified
461
+ // ===========================================================================
462
+ /**
463
+ * Determine whether a cached response is still fresh according to RFC 7232.
464
+ *
465
+ * Evaluation priority:
466
+ * 1. **`If-None-Match`** — compared against the `ETag` header using weak
467
+ * comparison (the `W/` prefix is stripped from both sides before matching).
468
+ * The wildcard `*` matches any ETag.
469
+ * 2. **`If-Modified-Since`** — only consulted when `If-None-Match` is absent.
470
+ * The response is fresh when `Last-Modified ≤ If-Modified-Since`.
471
+ *
472
+ * Returns `false` when neither condition applies so the full response is sent.
473
+ *
474
+ * @param etag - The current value of the `ETag` response header.
475
+ * @param lastModified - The current value of the `Last-Modified` response header.
476
+ * @param ifNoneMatch - The `If-None-Match` request header value (or array).
477
+ * @param ifModSince - The `If-Modified-Since` request header value (or array).
478
+ */
479
+ function isFreshResponse(etag, lastModified, ifNoneMatch, ifModSince) {
480
+ const inm = Array.isArray(ifNoneMatch) ? ifNoneMatch[0] : ifNoneMatch;
481
+ if (inm) {
482
+ if (!etag)
483
+ return false;
484
+ // `*` is a wildcard that matches any entity-tag.
485
+ if (inm.trim() === '*')
486
+ return true;
487
+ // Weak comparison: strip the W/ prefix before comparing quoted tag values.
488
+ const normalize = (tag) => tag.startsWith('W/') ? tag.slice(2) : tag;
489
+ const tags = inm.split(',').map((t) => normalize(t.trim()));
490
+ return tags.includes(normalize(etag));
491
+ }
492
+ const ims = Array.isArray(ifModSince) ? ifModSince[0] : ifModSince;
493
+ if (ims && lastModified) {
494
+ const imsMs = new Date(ims).getTime();
495
+ const lmMs = new Date(lastModified).getTime();
496
+ if (!isNaN(imsMs) && !isNaN(lmMs))
497
+ return lmMs <= imsMs;
498
+ }
499
+ return false;
500
+ }
501
+ /**
502
+ * Conditional GET middleware (RFC 7232).
503
+ *
504
+ * Transparently handles `If-None-Match` and `If-Modified-Since` request
505
+ * headers. When the response carries an `ETag` or `Last-Modified` header and
506
+ * the client's cached copy is still fresh, the middleware short-circuits with
507
+ * **304 Not Modified** — stripping the response body and content-related
508
+ * headers — instead of sending the full response.
509
+ *
510
+ * Mount this middleware **before** the route handler. The route handler runs
511
+ * normally and can call `res.etag()` / `res.json()` / `res.send()` as usual;
512
+ * the middleware intercepts the outgoing writes and decides whether to replace
513
+ * the response with a 304.
514
+ *
515
+ * ```ts
516
+ * app.get('/api/user/:id', conditionalGet(), (req, res) => {
517
+ * const user = getUser(req.params.id);
518
+ * res.etag(user.updatedAt.toISOString());
519
+ * res.json(user); // → 304 if ETag matches If-None-Match
520
+ * });
521
+ * ```
522
+ *
523
+ * **RFC 7232 compliance:**
524
+ * - `If-None-Match` is evaluated first (takes priority over `If-Modified-Since`).
525
+ * - Weak comparison is used for ETag matching (the `W/` prefix is ignored).
526
+ * - 304 responses strip `Content-Type`, `Content-Length`, and
527
+ * `Content-Encoding`, but retain `ETag`, `Cache-Control`, `Vary`, and
528
+ * `Last-Modified`.
529
+ * - Only GET and HEAD are eligible for 304 responses; other methods are
530
+ * passed through unchanged.
531
+ *
532
+ * @returns An Express-compatible middleware function.
533
+ *
534
+ * @example
535
+ * ```ts
536
+ * // With ETag
537
+ * app.get('/resource', conditionalGet(), (req, res) => {
538
+ * res.etag('v1.0').json({ value: 42 });
539
+ * });
540
+ *
541
+ * // With Last-Modified
542
+ * app.get('/file', conditionalGet(), (req, res) => {
543
+ * res.setHeader('Last-Modified', new Date().toUTCString());
544
+ * res.send(fileContent);
545
+ * });
546
+ * ```
547
+ */
548
+ export function conditionalGet() {
549
+ return (req, res, next) => {
550
+ // Only GET and HEAD can yield a 304 Not Modified.
551
+ const method = req.method ?? 'GET';
552
+ if (method !== 'GET' && method !== 'HEAD')
553
+ return next();
554
+ const rawRes = res;
555
+ const origWrite = rawRes.write.bind(rawRes);
556
+ const origEnd = rawRes.end.bind(rawRes);
557
+ // Buffer all outgoing body data until we can check freshness at end().
558
+ const pending = [];
559
+ res.write = (chunk, encOrCb, _cb) => {
560
+ const enc = typeof encOrCb === 'string' ? encOrCb : 'utf8';
561
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, enc);
562
+ pending.push(buf);
563
+ return true;
564
+ };
565
+ res.end = (chunk, encOrCb, _cb) => {
566
+ if (typeof chunk === 'function')
567
+ chunk = undefined;
568
+ if (typeof encOrCb === 'function')
569
+ encOrCb = undefined;
570
+ // Buffer any final chunk passed directly to end().
571
+ if (chunk != null) {
572
+ const enc = typeof encOrCb === 'string' ? encOrCb : 'utf8';
573
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, enc);
574
+ pending.push(buf);
575
+ }
576
+ // Read the freshness-relevant headers set by the route handler.
577
+ const etag = rawRes.getHeader('etag');
578
+ const lastMod = rawRes.getHeader('last-modified');
579
+ const ifNoneMatch = req.headers['if-none-match'];
580
+ const ifModSince = req.headers['if-modified-since'];
581
+ if (isFreshResponse(etag, lastMod, ifNoneMatch, ifModSince)) {
582
+ // RFC 7232 §4.1: 304 response MUST NOT include a message body.
583
+ // Strip content-related headers; retain ETag, Cache-Control, Vary, Last-Modified.
584
+ rawRes.removeHeader('content-type');
585
+ rawRes.removeHeader('content-length');
586
+ rawRes.removeHeader('content-encoding');
587
+ rawRes.statusCode = 304;
588
+ origEnd();
589
+ }
590
+ else {
591
+ // Not fresh — flush buffered body and end normally.
592
+ for (const buf of pending)
593
+ origWrite(buf);
594
+ origEnd();
595
+ }
596
+ return rawRes;
597
+ };
598
+ next();
599
+ };
600
+ }
601
+ export function securityHeaders(opts) {
602
+ // Pre-compute all header values once at middleware-creation time.
603
+ const headers = [];
604
+ // Strict-Transport-Security
605
+ const hsts = opts?.hsts;
606
+ if (hsts !== false) {
607
+ const h = typeof hsts === 'object' ? hsts : {};
608
+ const maxAge = h.maxAge ?? 15_552_000;
609
+ const subdomain = h.includeSubDomains !== false;
610
+ let value = `max-age=${maxAge}`;
611
+ if (subdomain)
612
+ value += '; includeSubDomains';
613
+ if (h.preload)
614
+ value += '; preload';
615
+ headers.push(['Strict-Transport-Security', value]);
616
+ }
617
+ // X-Frame-Options
618
+ const fo = opts?.frameOptions;
619
+ if (fo !== false) {
620
+ headers.push(['X-Frame-Options', fo ?? 'SAMEORIGIN']);
621
+ }
622
+ // X-Content-Type-Options
623
+ if (opts?.contentTypeOptions !== false) {
624
+ headers.push(['X-Content-Type-Options', 'nosniff']);
625
+ }
626
+ // Referrer-Policy
627
+ const rp = opts?.referrerPolicy;
628
+ if (rp !== false) {
629
+ headers.push(['Referrer-Policy', typeof rp === 'string' ? rp : 'strict-origin-when-cross-origin']);
630
+ }
631
+ // Permissions-Policy
632
+ const pp = opts?.permissionsPolicy;
633
+ if (pp !== false) {
634
+ headers.push(['Permissions-Policy', typeof pp === 'string' ? pp : 'geolocation=(), microphone=(), camera=()']);
635
+ }
636
+ // X-XSS-Protection
637
+ const xxp = opts?.xssProtection;
638
+ if (xxp !== false) {
639
+ headers.push(['X-XSS-Protection', typeof xxp === 'string' ? xxp : '0']);
640
+ }
641
+ return (_req, res, next) => {
642
+ for (const [name, value] of headers)
643
+ res.setHeader(name, value);
644
+ next();
645
+ };
646
+ }
647
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}