@daloyjs/core 0.36.0 → 0.37.0

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 (77) hide show
  1. package/README.md +21 -2
  2. package/bin/daloy.mjs +2 -0
  3. package/dist/adapters/bun.js +16 -9
  4. package/dist/adapters/deno.js +7 -1
  5. package/dist/adapters/node.d.ts +11 -0
  6. package/dist/adapters/node.js +24 -0
  7. package/dist/app.d.ts +144 -1
  8. package/dist/app.js +208 -1
  9. package/dist/asyncapi.d.ts +98 -0
  10. package/dist/asyncapi.js +212 -0
  11. package/dist/auto-ban.d.ts +205 -0
  12. package/dist/auto-ban.js +222 -0
  13. package/dist/bot-guard.d.ts +209 -0
  14. package/dist/bot-guard.js +291 -0
  15. package/dist/cli.d.ts +8 -0
  16. package/dist/cli.js +88 -4
  17. package/dist/concurrency-limit.d.ts +135 -0
  18. package/dist/concurrency-limit.js +254 -0
  19. package/dist/docs.d.ts +57 -6
  20. package/dist/docs.js +34 -3
  21. package/dist/errors.d.ts +20 -0
  22. package/dist/errors.js +27 -0
  23. package/dist/fetch-guard.js +4 -0
  24. package/dist/fetch-resilience.d.ts +295 -0
  25. package/dist/fetch-resilience.js +485 -0
  26. package/dist/geo-block.d.ts +184 -0
  27. package/dist/geo-block.js +153 -0
  28. package/dist/hashing.d.ts +2 -1
  29. package/dist/hashing.js +12 -1
  30. package/dist/http-signatures.d.ts +303 -0
  31. package/dist/http-signatures.js +782 -0
  32. package/dist/idempotency.d.ts +204 -0
  33. package/dist/idempotency.js +341 -0
  34. package/dist/index.d.ts +38 -4
  35. package/dist/index.js +18 -1
  36. package/dist/ip-reputation.d.ts +198 -0
  37. package/dist/ip-reputation.js +253 -0
  38. package/dist/jwk.d.ts +15 -0
  39. package/dist/jwk.js +24 -2
  40. package/dist/load-shedding.d.ts +5 -0
  41. package/dist/logger.js +6 -2
  42. package/dist/metrics.d.ts +208 -0
  43. package/dist/metrics.js +452 -0
  44. package/dist/middleware.js +0 -10
  45. package/dist/mtls.d.ts +266 -0
  46. package/dist/mtls.js +488 -0
  47. package/dist/multipart.js +1 -1
  48. package/dist/openapi-diff.d.ts +79 -0
  49. package/dist/openapi-diff.js +246 -0
  50. package/dist/openapi.js +4 -1
  51. package/dist/pagination.d.ts +210 -0
  52. package/dist/pagination.js +353 -0
  53. package/dist/rate-limit-redis.d.ts +8 -0
  54. package/dist/rate-limit-redis.js +8 -0
  55. package/dist/request-decompression.d.ts +200 -0
  56. package/dist/request-decompression.js +363 -0
  57. package/dist/response-cache.d.ts +205 -0
  58. package/dist/response-cache.js +374 -0
  59. package/dist/router.d.ts +22 -0
  60. package/dist/router.js +64 -7
  61. package/dist/safe-redirect.d.ts +2 -2
  62. package/dist/safe-redirect.js +3 -8
  63. package/dist/sbom.cdx.json +9 -9
  64. package/dist/sbom.spdx.json +5 -5
  65. package/dist/scheduler.d.ts +315 -0
  66. package/dist/scheduler.js +546 -0
  67. package/dist/security.d.ts +27 -7
  68. package/dist/security.js +27 -7
  69. package/dist/session.js +3 -3
  70. package/dist/types.d.ts +33 -0
  71. package/dist/waf.d.ts +213 -0
  72. package/dist/waf.js +334 -0
  73. package/dist/webhook-delivery.d.ts +263 -0
  74. package/dist/webhook-delivery.js +311 -0
  75. package/dist/websocket.d.ts +52 -0
  76. package/dist/websocket.js +13 -0
  77. package/package.json +76 -2
package/dist/mtls.d.ts ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Mutual-TLS / client-certificate authentication.
3
+ *
4
+ * The Web-standard {@link Request} does not surface the TLS peer certificate,
5
+ * so — exactly like {@link "./conn-info.js"} does for the peer IP — adapters
6
+ * (or a trusted TLS-terminating reverse proxy) attach a normalized
7
+ * {@link ClientCertificate} to the request, and the {@link clientCertAuth}
8
+ * middleware reads it back to enforce a client-certificate identity for
9
+ * zero-trust / service-to-service deployments.
10
+ *
11
+ * Two population paths are supported, both runtime-portable and dependency-free:
12
+ *
13
+ * 1. **Native TLS** — when the runtime terminates TLS itself (the Node adapter
14
+ * reads `tls.TLSSocket#getPeerCertificate()`), the adapter stashes a lazy
15
+ * thunk via {@link setClientCertificate}; the certificate is only normalized
16
+ * if a `clientCertAuth()`-guarded route actually reads it, so plain requests
17
+ * pay nothing.
18
+ * 2. **Forwarded by a trusted proxy** — when TLS is terminated upstream
19
+ * (Envoy, nginx, HAProxy, Traefik, a cloud load balancer), the proxy forwards
20
+ * the verified client identity in request headers. {@link clientCertAuth}
21
+ * can parse Envoy's `X-Forwarded-Client-Cert` (XFCC) or a set of operator-named
22
+ * structured headers (nginx `$ssl_client_*`, etc.). Because those headers are
23
+ * spoofable by anything that can reach the app directly, the header path is
24
+ * opt-in and must be paired with a `behindProxy` posture that guarantees the
25
+ * app is only reachable through the terminating proxy.
26
+ *
27
+ * @module
28
+ * @since 0.37.0
29
+ */
30
+ import type { BaseContext, Hooks } from "./types.js";
31
+ /**
32
+ * Normalized view of a TLS client certificate, independent of how it was
33
+ * obtained (native socket vs. forwarded proxy header). Every field except
34
+ * {@link subjectAltNames} and {@link verified} is optional because different
35
+ * sources expose different subsets.
36
+ *
37
+ * @since 0.37.0
38
+ */
39
+ export interface ClientCertificate {
40
+ /** Full subject distinguished name, e.g. `"CN=svc-a,OU=payments,O=acme"`. */
41
+ readonly subjectDN?: string;
42
+ /** Subject common name (the `CN=` RDN), if present. */
43
+ readonly subjectCN?: string;
44
+ /** Full issuer distinguished name. */
45
+ readonly issuerDN?: string;
46
+ /** Issuer common name (the `CN=` RDN), if present. */
47
+ readonly issuerCN?: string;
48
+ /** Certificate serial number (hex string), if known. */
49
+ readonly serialNumber?: string;
50
+ /**
51
+ * SHA-256 fingerprint. Normalized to uppercase hex **without** separators
52
+ * (colons stripped) so allow-list comparison is source-independent.
53
+ */
54
+ readonly fingerprint256?: string;
55
+ /**
56
+ * Subject Alternative Names as `TYPE:value` entries (e.g. `"DNS:svc-a.internal"`,
57
+ * `"URI:spiffe://acme/svc-a"`, `"IP:10.0.0.7"`). Empty array when none.
58
+ */
59
+ readonly subjectAltNames: readonly string[];
60
+ /** `notBefore` validity bound, if the source exposed it. */
61
+ readonly notBefore?: Date;
62
+ /** `notAfter` validity bound, if the source exposed it. */
63
+ readonly notAfter?: Date;
64
+ /**
65
+ * Whether the TLS terminator **cryptographically verified** the certificate
66
+ * chain. `clientCertAuth({ requireVerified: true })` (the default) refuses
67
+ * any certificate where this is `false`.
68
+ */
69
+ readonly verified: boolean;
70
+ /** PEM text, when the source forwarded it (XFCC `Cert=`). */
71
+ readonly pem?: string;
72
+ }
73
+ /**
74
+ * A {@link ClientCertificate} or a lazy thunk that produces one. Adapters stash
75
+ * a thunk so the (potentially expensive) certificate read only happens if a
76
+ * guarded route actually inspects it.
77
+ *
78
+ * @since 0.37.0
79
+ */
80
+ export type ClientCertificateSource = ClientCertificate | undefined | (() => ClientCertificate | undefined);
81
+ /**
82
+ * @internal Adapter helper — attach a {@link ClientCertificate} (or a lazy
83
+ * thunk producing one) to a `Request`. Mirrors {@link "./conn-info.js".setConnInfo}.
84
+ * Pass a thunk to defer the read until {@link getClientCertificate} is first
85
+ * called; the resolved value is cached back onto the request.
86
+ *
87
+ * @since 0.37.0
88
+ */
89
+ export declare function setClientCertificate(request: Request, source: ClientCertificateSource): void;
90
+ /**
91
+ * Read the {@link ClientCertificate} an adapter attached to this request, or
92
+ * `undefined` when the connection presented no client certificate (or the
93
+ * adapter does not expose TLS peer info). If a lazy thunk was stashed, it is
94
+ * resolved once and the result cached.
95
+ *
96
+ * @since 0.37.0
97
+ */
98
+ export declare function getClientCertificate(request: Request): ClientCertificate | undefined;
99
+ /**
100
+ * Shape of the object returned by Node's `tls.TLSSocket#getPeerCertificate(true)`.
101
+ * Declared structurally so {@link normalizePeerCertificate} stays dependency-free
102
+ * and importable in non-Node runtimes.
103
+ *
104
+ * @since 0.37.0
105
+ */
106
+ export interface PeerCertificateLike {
107
+ subject?: Record<string, string | string[]> | null;
108
+ issuer?: Record<string, string | string[]> | null;
109
+ valid_from?: string;
110
+ valid_to?: string;
111
+ fingerprint256?: string;
112
+ serialNumber?: string;
113
+ subjectaltname?: string;
114
+ }
115
+ /**
116
+ * Normalize a Node `getPeerCertificate(true)` result into a
117
+ * {@link ClientCertificate}. Returns `undefined` for the empty object Node
118
+ * returns when the peer presented no certificate.
119
+ *
120
+ * @param raw - The structured peer-certificate object from the TLS socket.
121
+ * @param verified - Whether the socket reported `authorized === true` (the
122
+ * chain was verified against the configured CA).
123
+ * @since 0.37.0
124
+ */
125
+ export declare function normalizePeerCertificate(raw: PeerCertificateLike | null | undefined, verified: boolean): ClientCertificate | undefined;
126
+ /**
127
+ * Parse an Envoy `X-Forwarded-Client-Cert` (XFCC) header value into a
128
+ * {@link ClientCertificate}. XFCC is a comma-separated list of proxy elements,
129
+ * each a `;`-delimited set of `Key=Value` pairs (`Hash`, `Subject`, `URI`,
130
+ * `DNS`, `Cert`, …). The **first** element is the client closest to the origin
131
+ * and is the one returned. Because Envoy only emits XFCC for connections it
132
+ * mutually authenticated, the result is marked `verified: true`.
133
+ *
134
+ * Returns `undefined` for an empty or unparseable header.
135
+ *
136
+ * @since 0.37.0
137
+ */
138
+ export declare function parseForwardedClientCert(headerValue: string | null | undefined): ClientCertificate | undefined;
139
+ /**
140
+ * Configuration for reading a forwarded client certificate out of request
141
+ * headers set by a trusted TLS-terminating proxy.
142
+ *
143
+ * @since 0.37.0
144
+ */
145
+ export type ClientCertHeaderConfig = {
146
+ /** Envoy `X-Forwarded-Client-Cert` format. */
147
+ readonly format: "xfcc";
148
+ /** Header name. Default: `"x-forwarded-client-cert"`. */
149
+ readonly name?: string;
150
+ } | {
151
+ /**
152
+ * Operator-named structured headers (nginx `$ssl_client_*`, HAProxy,
153
+ * Traefik). Each property is the header name carrying that field.
154
+ */
155
+ readonly format: "structured";
156
+ /** Header carrying the subject DN (e.g. nginx `$ssl_client_s_dn`). */
157
+ readonly subjectDN?: string;
158
+ /** Header carrying the issuer DN (e.g. nginx `$ssl_client_i_dn`). */
159
+ readonly issuerDN?: string;
160
+ /** Header carrying the SHA-256 fingerprint (e.g. nginx `$ssl_client_fingerprint`). */
161
+ readonly fingerprint?: string;
162
+ /** Header carrying the serial number. */
163
+ readonly serialNumber?: string;
164
+ /** Header carrying a comma-separated SAN list. */
165
+ readonly san?: string;
166
+ /** Header carrying the verification result (e.g. nginx `$ssl_client_verify`). */
167
+ readonly verify?: string;
168
+ /**
169
+ * Value of the `verify` header that means "chain verified". Default:
170
+ * `"SUCCESS"` (nginx). Compared case-insensitively.
171
+ */
172
+ readonly verifySuccessValue?: string;
173
+ };
174
+ /**
175
+ * Options for {@link clientCertAuth}.
176
+ *
177
+ * @since 0.37.0
178
+ */
179
+ export interface ClientCertAuthOptions {
180
+ /**
181
+ * Override how the certificate is sourced. Defaults to reading whatever the
182
+ * adapter attached via {@link setClientCertificate} (native TLS), falling
183
+ * back to {@link header} parsing when configured.
184
+ */
185
+ resolve?: (ctx: BaseContext<any, any>) => ClientCertificate | undefined;
186
+ /**
187
+ * Read the certificate from a trusted-proxy header instead of (or in
188
+ * addition to) the native adapter source. **Spoofable** unless the app is
189
+ * only reachable through the terminating proxy — pair with a strict
190
+ * `behindProxy` posture.
191
+ */
192
+ header?: ClientCertHeaderConfig;
193
+ /**
194
+ * Require the TLS terminator to have cryptographically verified the chain
195
+ * (`cert.verified === true`). Default: `true`. Only disable when an upstream
196
+ * component performs verification out of band.
197
+ */
198
+ requireVerified?: boolean;
199
+ /** If set, the subject CN must exactly match one of these values. */
200
+ allowSubjectCNs?: readonly string[];
201
+ /** If set, the issuer CN must exactly match one of these values. */
202
+ allowIssuerCNs?: readonly string[];
203
+ /**
204
+ * If set, the certificate's SHA-256 fingerprint must match one of these
205
+ * (compared in constant time; colons/spaces and case are ignored).
206
+ */
207
+ allowFingerprints?: readonly string[];
208
+ /**
209
+ * If set, at least one Subject Alternative Name must match one of these.
210
+ * Entries may be given as `TYPE:value` (e.g. `"URI:spiffe://acme/svc-a"`) or
211
+ * as a bare value matched against any SAN's value part.
212
+ */
213
+ allowSANs?: readonly string[];
214
+ /**
215
+ * Reject certificates outside their `[notBefore, notAfter]` validity window
216
+ * when those bounds are known. Default: `true`. (A verifying TLS terminator
217
+ * already enforces this, but a header-forwarded cert may not have been
218
+ * fully validated.)
219
+ */
220
+ checkValidity?: boolean;
221
+ /**
222
+ * Custom per-request check, run after all built-in checks pass. Returning
223
+ * `false` rejects with `403`; `true`/`undefined` accepts.
224
+ */
225
+ verify?: (cert: ClientCertificate, ctx: BaseContext<any, any>) => boolean | void | Promise<boolean | void>;
226
+ /** Rejection message for the `403` responses. Default: `"Client certificate not permitted"`. */
227
+ message?: string;
228
+ /** `ctx.state` key the accepted certificate is stamped on. Default: `"clientCertificate"`. */
229
+ stateKey?: string;
230
+ /** @internal Injectable clock for validity-window tests. */
231
+ now?: () => number;
232
+ }
233
+ /**
234
+ * Middleware enforcing mutual-TLS client-certificate authentication. Reads the
235
+ * normalized {@link ClientCertificate} attached by the adapter (native TLS) or
236
+ * parsed from a trusted-proxy header, enforces verification + optional
237
+ * allow-lists + validity window + a custom hook, and stamps the accepted
238
+ * certificate on `ctx.state` for downstream handlers.
239
+ *
240
+ * Rejection semantics:
241
+ * - **No certificate presented** → `401` `application/problem+json` with
242
+ * `Cache-Control: no-store`.
243
+ * - **Unverified / not allow-listed / expired / custom-rejected** → `403`
244
+ * {@link ForbiddenError} (never echoes certificate details).
245
+ *
246
+ * @example Native TLS (Node adapter terminates mTLS):
247
+ * ```ts
248
+ * app.use(clientCertAuth({
249
+ * allowIssuerCNs: ["acme-internal-ca"],
250
+ * allowSANs: ["URI:spiffe://acme/svc-a"],
251
+ * }));
252
+ * ```
253
+ *
254
+ * @example Behind an Envoy proxy forwarding XFCC:
255
+ * ```ts
256
+ * app.use(clientCertAuth({
257
+ * header: { format: "xfcc" },
258
+ * allowFingerprints: [process.env.PEER_FINGERPRINT!],
259
+ * }));
260
+ * ```
261
+ *
262
+ * @param opts - Verification, allow-list, header-source, and hook options.
263
+ * @returns A {@link Hooks} bundle for `app.use(...)`.
264
+ * @since 0.37.0
265
+ */
266
+ export declare function clientCertAuth(opts?: ClientCertAuthOptions): Hooks;