@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.
- package/README.md +21 -2
- package/bin/daloy.mjs +2 -0
- package/dist/adapters/bun.js +16 -9
- package/dist/adapters/deno.js +7 -1
- package/dist/adapters/node.d.ts +11 -0
- package/dist/adapters/node.js +24 -0
- package/dist/app.d.ts +144 -1
- package/dist/app.js +208 -1
- package/dist/asyncapi.d.ts +98 -0
- package/dist/asyncapi.js +212 -0
- package/dist/auto-ban.d.ts +205 -0
- package/dist/auto-ban.js +222 -0
- package/dist/bot-guard.d.ts +209 -0
- package/dist/bot-guard.js +291 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +88 -4
- package/dist/concurrency-limit.d.ts +135 -0
- package/dist/concurrency-limit.js +254 -0
- package/dist/docs.d.ts +57 -6
- package/dist/docs.js +34 -3
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +27 -0
- package/dist/fetch-guard.js +4 -0
- package/dist/fetch-resilience.d.ts +295 -0
- package/dist/fetch-resilience.js +485 -0
- package/dist/geo-block.d.ts +184 -0
- package/dist/geo-block.js +153 -0
- package/dist/hashing.d.ts +2 -1
- package/dist/hashing.js +12 -1
- package/dist/http-signatures.d.ts +303 -0
- package/dist/http-signatures.js +782 -0
- package/dist/idempotency.d.ts +204 -0
- package/dist/idempotency.js +341 -0
- package/dist/index.d.ts +38 -4
- package/dist/index.js +18 -1
- package/dist/ip-reputation.d.ts +198 -0
- package/dist/ip-reputation.js +253 -0
- package/dist/jwk.d.ts +15 -0
- package/dist/jwk.js +24 -2
- package/dist/load-shedding.d.ts +5 -0
- package/dist/logger.js +6 -2
- package/dist/metrics.d.ts +208 -0
- package/dist/metrics.js +452 -0
- package/dist/middleware.js +0 -10
- package/dist/mtls.d.ts +266 -0
- package/dist/mtls.js +488 -0
- package/dist/multipart.js +1 -1
- package/dist/openapi-diff.d.ts +79 -0
- package/dist/openapi-diff.js +246 -0
- package/dist/openapi.js +4 -1
- package/dist/pagination.d.ts +210 -0
- package/dist/pagination.js +353 -0
- package/dist/rate-limit-redis.d.ts +8 -0
- package/dist/rate-limit-redis.js +8 -0
- package/dist/request-decompression.d.ts +200 -0
- package/dist/request-decompression.js +363 -0
- package/dist/response-cache.d.ts +205 -0
- package/dist/response-cache.js +374 -0
- package/dist/router.d.ts +22 -0
- package/dist/router.js +64 -7
- package/dist/safe-redirect.d.ts +2 -2
- package/dist/safe-redirect.js +3 -8
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/dist/scheduler.d.ts +315 -0
- package/dist/scheduler.js +546 -0
- package/dist/security.d.ts +27 -7
- package/dist/security.js +27 -7
- package/dist/session.js +3 -3
- package/dist/types.d.ts +33 -0
- package/dist/waf.d.ts +213 -0
- package/dist/waf.js +334 -0
- package/dist/webhook-delivery.d.ts +263 -0
- package/dist/webhook-delivery.js +311 -0
- package/dist/websocket.d.ts +52 -0
- package/dist/websocket.js +13 -0
- 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;
|