client-certificate-auth 2.0.2 → 2.1.1
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 -9
- package/lib/clientCertificateAuth.cjs +18 -2
- package/lib/clientCertificateAuth.d.ts +9 -0
- package/lib/clientCertificateAuth.js +25 -4
- package/lib/extractor.d.ts +7 -2
- package/lib/extractor.js +44 -6
- package/lib/fetch.cjs +20 -0
- package/lib/fetch.d.cts +13 -0
- package/lib/fetch.d.ts +28 -0
- package/lib/fetch.js +59 -0
- package/lib/lambda.cjs +20 -0
- package/lib/lambda.d.cts +13 -0
- package/lib/lambda.d.ts +78 -0
- package/lib/lambda.js +73 -0
- package/lib/parsers.d.ts +14 -1
- package/lib/parsers.js +44 -9
- package/package.json +32 -5
package/README.md
CHANGED
|
@@ -13,7 +13,27 @@ Comprehensive toolkit for client SSL certificate authentication (mTLS) in Node.j
|
|
|
13
13
|
|
|
14
14
|
**Recommended by AWS** - Featured in the [AWS API Gateway documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-client-side-ssl-authentication.html#certificate-validation).
|
|
15
15
|
|
|
16
|
-
**Fanatically Tested** - 100% line/branch/function/statement coverage, plus mutation testing and E2E tests against real nginx/Envoy/Traefik containers. ~
|
|
16
|
+
**Fanatically Tested** - 100% line/branch/function/statement coverage, plus mutation testing and E2E tests against real nginx/Envoy/Traefik containers. ~6,207 lines of test code for ~937 lines of source (measured by [cloc](https://github.com/AlDanial/cloc)).
|
|
17
|
+
|
|
18
|
+
## What Is This?
|
|
19
|
+
|
|
20
|
+
This library authenticates HTTP clients by their TLS client certificates, a scheme usually called mutual TLS (mTLS). Instead of presenting a password, API key, or bearer token, the client presents an X.509 certificate during the TLS handshake and proves possession of its private key; the server checks that the certificate was issued by a CA it trusts. The certificate is the credential.
|
|
21
|
+
|
|
22
|
+
Typical uses:
|
|
23
|
+
|
|
24
|
+
- **Service-to-service APIs**: internal microservices or partner integrations where each caller holds its own certificate rather than a shared secret.
|
|
25
|
+
- **Machine and device authentication**: CI runners, IoT devices, daemons. Certificates are issued per machine and revoked per machine.
|
|
26
|
+
- **Restricting sensitive endpoints**: admin interfaces, metrics, internal tooling that only known clients should reach.
|
|
27
|
+
- **Certificate-based user login**: enterprise PKI and smart-card environments, where certificates map to user accounts.
|
|
28
|
+
|
|
29
|
+
The TLS handshake proves who the client is; your application decides whether that client is allowed in. This library covers the part in between: it extracts the verified certificate from the request wherever your TLS terminates, parses it into a standard [`tls.PeerCertificate`](https://nodejs.org/api/tls.html#certificate-object) object, and passes it to your authorization callback. Supported certificate sources:
|
|
30
|
+
|
|
31
|
+
- **TLS terminated directly in Node.js**: the certificate is read from the TLS socket.
|
|
32
|
+
- **A TLS-terminating reverse proxy or load balancer** that forwards the certificate in an HTTP header: AWS ALB, Envoy/Istio, Cloudflare, Traefik, Azure App Service, nginx, HAProxy, and any proxy that implements RFC 9440 (e.g. Google Cloud Load Balancer).
|
|
33
|
+
- **AWS Lambda** behind API Gateway mTLS, via `client-certificate-auth/lambda`.
|
|
34
|
+
- **Web-standard `Request` runtimes** (Hono, Next.js, SvelteKit, Cloudflare Workers, Bun, Deno), via `client-certificate-auth/fetch`.
|
|
35
|
+
|
|
36
|
+
Express and Connect get drop-in middleware. Other frameworks use the same extraction logic through `extractClientCertificate()`. Pre-built authorization helpers cover the common policies (allowlist by CN, fingerprint, issuer, OU, SAN, and more).
|
|
17
37
|
|
|
18
38
|
## Installation
|
|
19
39
|
|
|
@@ -23,14 +43,6 @@ npm install client-certificate-auth
|
|
|
23
43
|
|
|
24
44
|
**Requirements:** Node.js >= 20
|
|
25
45
|
|
|
26
|
-
## Synopsis
|
|
27
|
-
|
|
28
|
-
This library provides everything you need to implement mutual TLS (mTLS) authentication in Node.js. It extracts client certificates from direct TLS connections (`req.socket`) or from HTTP headers forwarded by reverse proxies (AWS ALB, Envoy, Cloudflare, Traefik, nginx, HAProxy).
|
|
29
|
-
|
|
30
|
-
The certificate is parsed into a standard `tls.PeerCertificate` object and passed to your callback for authorization logic.
|
|
31
|
-
|
|
32
|
-
Compatible with Express, Connect, or any Node.js HTTP server framework by using the framework-agnostic `extractClientCertificate` function.
|
|
33
|
-
|
|
34
46
|
## Usage
|
|
35
47
|
|
|
36
48
|
### Basic Setup
|
|
@@ -32,6 +32,19 @@ function isThenable(value) {
|
|
|
32
32
|
&& typeof value.then === 'function';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Wrap primitive values thrown or rejected by the validation callback in an
|
|
37
|
+
* Error so `.message` and `.status` access is safe; objects pass through.
|
|
38
|
+
* @param {unknown} err
|
|
39
|
+
* @returns {any}
|
|
40
|
+
*/
|
|
41
|
+
function normalizeCallbackError(err) {
|
|
42
|
+
if (err !== null && (typeof err === 'object' || typeof err === 'function')) {
|
|
43
|
+
return err;
|
|
44
|
+
}
|
|
45
|
+
return new Error(err === null || err === undefined ? '' : String(err));
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
/**
|
|
36
49
|
* Options not supported by the sync CJS wrapper.
|
|
37
50
|
* These require the ESM module's async header parsing.
|
|
@@ -39,6 +52,7 @@ function isThenable(value) {
|
|
|
39
52
|
const UNSUPPORTED_OPTIONS = [
|
|
40
53
|
'certificateSource',
|
|
41
54
|
'certificateHeader',
|
|
55
|
+
'chainHeader',
|
|
42
56
|
'headerEncoding',
|
|
43
57
|
'fallbackToSocket',
|
|
44
58
|
'verifyHeader',
|
|
@@ -144,7 +158,8 @@ function clientCertificateAuth(callback, options = {}) {
|
|
|
144
158
|
try {
|
|
145
159
|
const result = callback(cert, req);
|
|
146
160
|
if (isThenable(result)) {
|
|
147
|
-
Promise.resolve(result).then(doneAuthorizing).catch((
|
|
161
|
+
Promise.resolve(result).then(doneAuthorizing).catch((rejection) => {
|
|
162
|
+
const err = normalizeCallbackError(rejection);
|
|
148
163
|
safeCallHook(onRejected, cert, req, err.message || 'callback_threw');
|
|
149
164
|
if (err.status === undefined) {
|
|
150
165
|
err.status = 401;
|
|
@@ -154,7 +169,8 @@ function clientCertificateAuth(callback, options = {}) {
|
|
|
154
169
|
} else {
|
|
155
170
|
doneAuthorizing(result);
|
|
156
171
|
}
|
|
157
|
-
} catch (
|
|
172
|
+
} catch (thrown) {
|
|
173
|
+
const err = normalizeCallbackError(thrown);
|
|
158
174
|
safeCallHook(onRejected, cert, req, err.message || 'callback_threw');
|
|
159
175
|
if (err.status === undefined) {
|
|
160
176
|
err.status = 401;
|
|
@@ -77,6 +77,15 @@ export interface ClientCertificateAuthOptions {
|
|
|
77
77
|
*/
|
|
78
78
|
certificateHeader?: string;
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Optional second header carrying the certificate chain alongside the leaf.
|
|
82
|
+
* Split on commas per RFC 9440, each item parsed with the same
|
|
83
|
+
* headerEncoding, results linked via issuerCertificate. For non-RFC-9440
|
|
84
|
+
* encodings the comma split may not match the encoding's list convention.
|
|
85
|
+
* Same trust boundary as certificateHeader.
|
|
86
|
+
*/
|
|
87
|
+
chainHeader?: string;
|
|
88
|
+
|
|
80
89
|
/**
|
|
81
90
|
* How to decode the header value.
|
|
82
91
|
* Required when using certificateHeader without certificateSource.
|
|
@@ -20,6 +20,19 @@ function isThenable(value) {
|
|
|
20
20
|
&& typeof /** @type {{then?: unknown}} */ (value).then === 'function';
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Wrap primitive values thrown or rejected by the validation callback in an
|
|
25
|
+
* Error so `.message` and `.status` access is safe; objects pass through.
|
|
26
|
+
* @param {unknown} err
|
|
27
|
+
* @returns {any}
|
|
28
|
+
*/
|
|
29
|
+
function normalizeCallbackError(err) {
|
|
30
|
+
if (err !== null && (typeof err === 'object' || typeof err === 'function')) {
|
|
31
|
+
return err;
|
|
32
|
+
}
|
|
33
|
+
return new Error(err === null || err === undefined ? '' : String(err));
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* @typedef {import('http').IncomingMessage & { secure?: boolean; socket: import('net').Socket & { authorized?: boolean; authorizationError?: Error | string; getPeerCertificate?: (detailed?: boolean) => import('tls').PeerCertificate | import('tls').DetailedPeerCertificate }; clientCertificate?: import('tls').PeerCertificate }} ClientCertRequest
|
|
25
38
|
* @typedef {import('http').ServerResponse & { redirect: (statusOrUrl: number | string, url?: string) => void }} ClientCertResponse
|
|
@@ -28,7 +41,7 @@ function isThenable(value) {
|
|
|
28
41
|
|
|
29
42
|
/**
|
|
30
43
|
* @typedef {Object} ClientCertificateAuthOptions
|
|
31
|
-
* @property {'aws-alb' | '
|
|
44
|
+
* @property {'aws-alb' | 'aws-alb-verify' | 'azure-app-service' | 'cloudflare' | 'cloudflare-rfc9440' | 'envoy' | 'traefik'} [certificateSource] - Use a preset
|
|
32
45
|
* configuration for a known reverse proxy. Header-based certs are only checked if this or
|
|
33
46
|
* certificateHeader is set. Trust boundary: the proxy must strip the preset's header from
|
|
34
47
|
* external requests; any source that can set it is trusted to assert client identity.
|
|
@@ -36,7 +49,11 @@ function isThenable(value) {
|
|
|
36
49
|
* Overrides preset header name if also using certificateSource. Trust boundary: the proxy
|
|
37
50
|
* must strip this header from external requests; any source that can set it is trusted to
|
|
38
51
|
* assert client identity.
|
|
39
|
-
* @property {
|
|
52
|
+
* @property {string} [chainHeader] - Optional second header carrying the certificate chain
|
|
53
|
+
* alongside the leaf in certificateHeader (or the preset's header). Split on commas per
|
|
54
|
+
* RFC 9440, each item parsed with the same headerEncoding, results linked via
|
|
55
|
+
* issuerCertificate. Same trust boundary as certificateHeader.
|
|
56
|
+
* @property {'url-pem' | 'url-pem-aws' | 'xfcc' | 'base64-der' | 'rfc9440'} [headerEncoding] -
|
|
40
57
|
* How to decode the header value. Required when using certificateHeader without certificateSource.
|
|
41
58
|
* @property {boolean} [fallbackToSocket=false] - If header-based extraction is configured but
|
|
42
59
|
* fails (header absent or malformed), try socket.getPeerCertificate() instead of returning 401.
|
|
@@ -96,6 +113,7 @@ export default function clientCertificateAuth(callback, options = {}) {
|
|
|
96
113
|
const {
|
|
97
114
|
certificateSource,
|
|
98
115
|
certificateHeader,
|
|
116
|
+
chainHeader,
|
|
99
117
|
headerEncoding,
|
|
100
118
|
fallbackToSocket = false,
|
|
101
119
|
includeChain = false,
|
|
@@ -132,6 +150,7 @@ export default function clientCertificateAuth(callback, options = {}) {
|
|
|
132
150
|
const result = extractClientCertificate(req, {
|
|
133
151
|
certificateSource,
|
|
134
152
|
certificateHeader,
|
|
153
|
+
chainHeader,
|
|
135
154
|
headerEncoding,
|
|
136
155
|
fallbackToSocket,
|
|
137
156
|
includeChain,
|
|
@@ -189,7 +208,8 @@ export default function clientCertificateAuth(callback, options = {}) {
|
|
|
189
208
|
try {
|
|
190
209
|
const callbackResult = callback(cert, req);
|
|
191
210
|
if (isThenable(callbackResult)) {
|
|
192
|
-
Promise.resolve(callbackResult).then(doneAuthorizing).catch((
|
|
211
|
+
Promise.resolve(callbackResult).then(doneAuthorizing).catch((rejection) => {
|
|
212
|
+
const err = normalizeCallbackError(rejection);
|
|
193
213
|
safeCallHook(onRejected, cert, req, err.message || 'callback_threw');
|
|
194
214
|
if (err.status === undefined) {
|
|
195
215
|
err.status = 401;
|
|
@@ -199,7 +219,8 @@ export default function clientCertificateAuth(callback, options = {}) {
|
|
|
199
219
|
} else {
|
|
200
220
|
doneAuthorizing(callbackResult);
|
|
201
221
|
}
|
|
202
|
-
} catch (
|
|
222
|
+
} catch (thrown) {
|
|
223
|
+
const err = normalizeCallbackError(thrown);
|
|
203
224
|
safeCallHook(onRejected, cert, req, err.message || 'callback_threw');
|
|
204
225
|
if (err.status === undefined) {
|
|
205
226
|
err.status = 401;
|
package/lib/extractor.d.ts
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
14
|
* @typedef {Object} ExtractorOptions
|
|
15
|
-
* @property {'aws-alb' | '
|
|
15
|
+
* @property {'aws-alb' | 'aws-alb-verify' | 'azure-app-service' | 'cloudflare' | 'cloudflare-rfc9440' | 'envoy' | 'traefik'} [certificateSource] - Preset configuration
|
|
16
16
|
* @property {string} [certificateHeader] - Custom header name
|
|
17
|
+
* @property {string} [chainHeader] - Optional second header carrying the certificate chain
|
|
17
18
|
* @property {'url-pem' | 'url-pem-aws' | 'xfcc' | 'base64-der' | 'rfc9440'} [headerEncoding] - Header encoding
|
|
18
19
|
* @property {boolean} [fallbackToSocket=false] - Try socket if header extraction fails
|
|
19
20
|
* @property {boolean} [includeChain=false] - Include issuerCertificate chain
|
|
@@ -89,11 +90,15 @@ export type ExtractorOptions = {
|
|
|
89
90
|
/**
|
|
90
91
|
* - Preset configuration
|
|
91
92
|
*/
|
|
92
|
-
certificateSource?: "aws-alb" | "
|
|
93
|
+
certificateSource?: "aws-alb" | "aws-alb-verify" | "azure-app-service" | "cloudflare" | "cloudflare-rfc9440" | "envoy" | "traefik";
|
|
93
94
|
/**
|
|
94
95
|
* - Custom header name
|
|
95
96
|
*/
|
|
96
97
|
certificateHeader?: string;
|
|
98
|
+
/**
|
|
99
|
+
* - Optional second header carrying the certificate chain
|
|
100
|
+
*/
|
|
101
|
+
chainHeader?: string;
|
|
97
102
|
/**
|
|
98
103
|
* - Header encoding
|
|
99
104
|
*/
|
package/lib/extractor.js
CHANGED
|
@@ -4,22 +4,23 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { getCertificateFromHeaders, PRESETS } from './parsers.js';
|
|
7
|
+
import { getCertificateFromHeaders, parseHeaderValue, PRESETS } from './parsers.js';
|
|
8
8
|
|
|
9
9
|
const VALID_ENCODINGS = ['url-pem', 'url-pem-aws', 'xfcc', 'base64-der', 'rfc9440'];
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Validate options shared by `extractClientCertificate` and the middleware
|
|
13
13
|
* constructor. Throws on misconfiguration (unknown preset, unknown encoding,
|
|
14
|
-
* `certificateHeader` without an encoding source,
|
|
15
|
-
* `verifyHeader`/`verifyValue`). Omitted
|
|
16
|
-
* as the empty object; explicit `null`
|
|
14
|
+
* `certificateHeader` without an encoding source, `chainHeader` without a
|
|
15
|
+
* leaf header source, or only one of `verifyHeader`/`verifyValue`). Omitted
|
|
16
|
+
* or `undefined` options are treated as the empty object; explicit `null`
|
|
17
|
+
* will throw a `TypeError`.
|
|
17
18
|
*
|
|
18
19
|
* @param {ExtractorOptions} [options]
|
|
19
20
|
* @throws {Error} when options are malformed
|
|
20
21
|
*/
|
|
21
22
|
export function validateExtractorOptions(options = {}) {
|
|
22
|
-
const { certificateSource, certificateHeader, headerEncoding, verifyHeader, verifyValue } = options;
|
|
23
|
+
const { certificateSource, certificateHeader, chainHeader, headerEncoding, verifyHeader, verifyValue } = options;
|
|
23
24
|
|
|
24
25
|
if ((verifyHeader && !verifyValue) || (!verifyHeader && verifyValue)) {
|
|
25
26
|
throw new Error(
|
|
@@ -44,6 +45,12 @@ export function validateExtractorOptions(options = {}) {
|
|
|
44
45
|
'client-certificate-auth: certificateHeader requires headerEncoding (or a certificateSource preset that supplies one)'
|
|
45
46
|
);
|
|
46
47
|
}
|
|
48
|
+
|
|
49
|
+
if (chainHeader && !certificateSource && !certificateHeader) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
'client-certificate-auth: chainHeader requires certificateSource or certificateHeader (chain header must accompany a leaf header configuration)'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -61,12 +68,16 @@ export function validateExtractorOptions(options = {}) {
|
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
70
|
* @typedef {Object} ExtractorOptions
|
|
64
|
-
* @property {'aws-alb' | '
|
|
71
|
+
* @property {'aws-alb' | 'aws-alb-verify' | 'azure-app-service' | 'cloudflare' | 'cloudflare-rfc9440' | 'envoy' | 'traefik'} [certificateSource] - Preset
|
|
65
72
|
* configuration. Trust boundary: the proxy must strip the preset's header from external
|
|
66
73
|
* requests; any source that can set it is trusted to assert client identity.
|
|
67
74
|
* @property {string} [certificateHeader] - Custom header name. Trust boundary: the proxy must
|
|
68
75
|
* strip this header from external requests; any source that can set it is trusted to assert
|
|
69
76
|
* client identity.
|
|
77
|
+
* @property {string} [chainHeader] - Optional second header carrying the certificate chain
|
|
78
|
+
* alongside the leaf. Split on commas per RFC 9440, each item parsed with the same
|
|
79
|
+
* `headerEncoding`, results linked via `issuerCertificate`. For non-RFC-9440 encodings
|
|
80
|
+
* the comma split may not match the encoding's list convention.
|
|
70
81
|
* @property {'url-pem' | 'url-pem-aws' | 'xfcc' | 'base64-der' | 'rfc9440'} [headerEncoding] - Header encoding
|
|
71
82
|
* @property {boolean} [fallbackToSocket=false] - Try socket if header extraction fails
|
|
72
83
|
* @property {boolean} [includeChain=false] - Include issuerCertificate chain
|
|
@@ -111,6 +122,7 @@ export function extractClientCertificate(req, options = {}) {
|
|
|
111
122
|
const {
|
|
112
123
|
certificateSource,
|
|
113
124
|
certificateHeader,
|
|
125
|
+
chainHeader,
|
|
114
126
|
headerEncoding,
|
|
115
127
|
fallbackToSocket = false,
|
|
116
128
|
includeChain = false,
|
|
@@ -142,6 +154,32 @@ export function extractClientCertificate(req, options = {}) {
|
|
|
142
154
|
headerEncoding,
|
|
143
155
|
});
|
|
144
156
|
|
|
157
|
+
// Append chain from a separate chain header if configured.
|
|
158
|
+
// Resolve: explicit chainHeader > preset.chainHeader; explicit encoding > preset.encoding.
|
|
159
|
+
// Split on commas per RFC 9440, parse each item, link via issuerCertificate.
|
|
160
|
+
if (cert) {
|
|
161
|
+
const preset = certificateSource ? PRESETS[certificateSource] : null;
|
|
162
|
+
const chainHeaderName = (chainHeader ?? preset?.chainHeader)?.toLowerCase();
|
|
163
|
+
const chainEncoding = headerEncoding ?? preset?.encoding;
|
|
164
|
+
if (chainHeaderName && chainEncoding) {
|
|
165
|
+
const chainHeaderValue = req.headers[chainHeaderName];
|
|
166
|
+
if (chainHeaderValue && !Array.isArray(chainHeaderValue)) {
|
|
167
|
+
const chainCerts = chainHeaderValue
|
|
168
|
+
.split(',')
|
|
169
|
+
.map(item => item.trim())
|
|
170
|
+
.filter(Boolean)
|
|
171
|
+
.map(item => parseHeaderValue(item, chainEncoding))
|
|
172
|
+
.filter(Boolean);
|
|
173
|
+
if (chainCerts.length > 0) {
|
|
174
|
+
cert.issuerCertificate = chainCerts[0];
|
|
175
|
+
for (let i = 0; i < chainCerts.length - 1; i++) {
|
|
176
|
+
chainCerts[i].issuerCertificate = chainCerts[i + 1];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
145
183
|
// Normalize: strip chain unless includeChain is true
|
|
146
184
|
if (cert && !includeChain && 'issuerCertificate' in cert) {
|
|
147
185
|
delete cert.issuerCertificate;
|
package/lib/fetch.cjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/fetch - CommonJS wrapper
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
let _module;
|
|
10
|
+
|
|
11
|
+
async function load() {
|
|
12
|
+
// Stryker disable next-line ConditionalExpression,BlockStatement: test ordering caches _module from prior test; ConditionalExpression→true (always re-import) re-imports same module successfully; BlockStatement→{} uses cached value
|
|
13
|
+
if (!_module) {
|
|
14
|
+
// Stryker disable next-line StringLiteral: test ordering caches _module; StringLiteral→"" fails import but cached value masks it
|
|
15
|
+
_module = await import('./fetch.js');
|
|
16
|
+
}
|
|
17
|
+
return _module;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { load };
|
package/lib/fetch.d.cts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/fetch - CommonJS type declarations
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type * as FetchModule from './fetch.js';
|
|
8
|
+
|
|
9
|
+
declare const fetchAdapter: {
|
|
10
|
+
load(): Promise<typeof FetchModule>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export = fetchAdapter;
|
package/lib/fetch.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/fetch - TypeScript declarations
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExtractorOptions, ExtractionResult } from './extractor.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Any object whose `headers` field iterates as `[name, value]` tuples.
|
|
11
|
+
* Web `Request`, undici `Headers`, Bun, Deno, and Node 18+ `Headers`
|
|
12
|
+
* all work without modification.
|
|
13
|
+
*/
|
|
14
|
+
export interface RequestLike {
|
|
15
|
+
headers: Iterable<[string, string]>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extract a client certificate from a Web standard `Request` or any
|
|
20
|
+
* object with iterable headers. Header-only (no TLS socket in Web Request).
|
|
21
|
+
*
|
|
22
|
+
* @param request - A Web Request or any object with iterable headers
|
|
23
|
+
* @param options - Same options as `extractClientCertificate`. Header options only.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractClientCertificateFromRequest(
|
|
26
|
+
request: RequestLike,
|
|
27
|
+
options?: ExtractorOptions
|
|
28
|
+
): ExtractionResult;
|
package/lib/fetch.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/fetch - Web Request adapter
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { extractClientCertificate } from './extractor.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import('./extractor.js').ExtractorOptions} ExtractorOptions
|
|
11
|
+
* @typedef {import('./extractor.js').ExtractionResult} ExtractionResult
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract a client certificate from a Web standard `Request` (or any object
|
|
16
|
+
* with an iterable `headers` field that yields `[name, value]` tuples).
|
|
17
|
+
*
|
|
18
|
+
* Normalizes header names to lowercase and delegates to the core
|
|
19
|
+
* `extractClientCertificate`. Header-only: Web `Request` has no TLS socket,
|
|
20
|
+
* so `fallbackToSocket` is stripped and has no effect.
|
|
21
|
+
*
|
|
22
|
+
* @param {{ headers: Iterable<[string, string]> }} request - A Web Request or any object whose `headers` iterates `[name, value]` pairs.
|
|
23
|
+
* @param {ExtractorOptions} [options={}] - Same options as `extractClientCertificate`. Header-extraction options only; socket options are ignored.
|
|
24
|
+
* @returns {ExtractionResult}
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Hono
|
|
28
|
+
* import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
|
|
29
|
+
*
|
|
30
|
+
* app.get('/secure', async (c) => {
|
|
31
|
+
* const result = extractClientCertificateFromRequest(c.req.raw, {
|
|
32
|
+
* certificateSource: 'cloudflare-rfc9440',
|
|
33
|
+
* });
|
|
34
|
+
* if (!result.success) return c.text('Unauthorized', 401);
|
|
35
|
+
* return c.text(`Hello ${result.certificate.subject.CN}`);
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Next.js Route Handler
|
|
40
|
+
* export async function GET(request) {
|
|
41
|
+
* const result = extractClientCertificateFromRequest(request, {
|
|
42
|
+
* certificateSource: 'aws-alb',
|
|
43
|
+
* });
|
|
44
|
+
* if (!result.success) return new Response('Unauthorized', { status: 401 });
|
|
45
|
+
* return Response.json({ user: result.certificate.subject.CN });
|
|
46
|
+
* }
|
|
47
|
+
*/
|
|
48
|
+
export function extractClientCertificateFromRequest(request, options = {}) {
|
|
49
|
+
// Web Headers normalizes to lowercase, but Map and other iterables
|
|
50
|
+
// preserve casing. Normalize here for the core extractor's lowercase lookup.
|
|
51
|
+
const headers = {};
|
|
52
|
+
for (const [name, value] of request.headers) {
|
|
53
|
+
headers[name.toLowerCase()] = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { fallbackToSocket: _ignored, ...rest } = options;
|
|
57
|
+
|
|
58
|
+
return extractClientCertificate({ headers }, rest);
|
|
59
|
+
}
|
package/lib/lambda.cjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/lambda - CommonJS wrapper
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
let _module;
|
|
10
|
+
|
|
11
|
+
async function load() {
|
|
12
|
+
// Stryker disable next-line ConditionalExpression,BlockStatement: test ordering caches _module from prior test; ConditionalExpression→true (always re-import) re-imports same module successfully; BlockStatement→{} uses cached value
|
|
13
|
+
if (!_module) {
|
|
14
|
+
// Stryker disable next-line StringLiteral: test ordering caches _module; StringLiteral→"" fails import but cached value masks it
|
|
15
|
+
_module = await import('./lambda.js');
|
|
16
|
+
}
|
|
17
|
+
return _module;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { load };
|
package/lib/lambda.d.cts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/lambda - CommonJS type declarations
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type * as LambdaModule from './lambda.js';
|
|
8
|
+
|
|
9
|
+
declare const lambda: {
|
|
10
|
+
load(): Promise<typeof LambdaModule>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export = lambda;
|
package/lib/lambda.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/lambda - TypeScript declarations
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PeerCertificate } from 'tls';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Client certificate object from the API Gateway Lambda event.
|
|
11
|
+
* Common to v1 and v2 payload formats. Compatible with `@types/aws-lambda`
|
|
12
|
+
* without requiring it as a dependency.
|
|
13
|
+
*/
|
|
14
|
+
export interface LambdaClientCert {
|
|
15
|
+
/** PEM-encoded client certificate with BEGIN/END delimiters. */
|
|
16
|
+
clientCertPem: string;
|
|
17
|
+
/** Optional subject distinguished name string, parsed by API Gateway. */
|
|
18
|
+
subjectDN?: string;
|
|
19
|
+
/** Optional issuer distinguished name string, parsed by API Gateway. */
|
|
20
|
+
issuerDN?: string;
|
|
21
|
+
/** Optional certificate serial number string, parsed by API Gateway. */
|
|
22
|
+
serialNumber?: string;
|
|
23
|
+
/** Optional validity window parsed by API Gateway. */
|
|
24
|
+
validity?: {
|
|
25
|
+
notBefore?: string;
|
|
26
|
+
notAfter?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Lambda event subset declaring only the cert-bearing paths.
|
|
32
|
+
* Accepts HTTP API (v2.0) and REST API (v1.0) payload formats.
|
|
33
|
+
*/
|
|
34
|
+
export interface LambdaEventWithClientCert {
|
|
35
|
+
requestContext?: {
|
|
36
|
+
/** v2.0 payload format: HTTP API. */
|
|
37
|
+
authentication?: {
|
|
38
|
+
clientCert?: LambdaClientCert;
|
|
39
|
+
};
|
|
40
|
+
/** v1.0 payload format: REST API. */
|
|
41
|
+
identity?: {
|
|
42
|
+
clientCert?: LambdaClientCert;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Result returned by `extractClientCertificateFromLambdaEvent`.
|
|
49
|
+
* Matches the core extractor's `ExtractionResult`.
|
|
50
|
+
*/
|
|
51
|
+
export interface LambdaExtractionResult {
|
|
52
|
+
/** Whether extraction succeeded. */
|
|
53
|
+
success: boolean;
|
|
54
|
+
/** Extracted certificate (null on failure). */
|
|
55
|
+
certificate: PeerCertificate | null;
|
|
56
|
+
/**
|
|
57
|
+
* Rejection reason code (null on success). Lambda-specific reasons:
|
|
58
|
+
* - 'lambda_event_missing_clientcert'
|
|
59
|
+
* - 'lambda_event_clientcert_malformed'
|
|
60
|
+
*/
|
|
61
|
+
reason: string | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract a client certificate from an AWS API Gateway Lambda event. Handles
|
|
66
|
+
* both v2.0 payload format (`event.requestContext.authentication.clientCert`)
|
|
67
|
+
* and v1.0 payload format (`event.requestContext.identity.clientCert`).
|
|
68
|
+
*
|
|
69
|
+
* Also accepts `null` and `undefined`, returning
|
|
70
|
+
* `lambda_event_missing_clientcert`.
|
|
71
|
+
*
|
|
72
|
+
* @param event - The Lambda event from API Gateway, or null/undefined
|
|
73
|
+
*
|
|
74
|
+
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-mutual-tls.html
|
|
75
|
+
*/
|
|
76
|
+
export declare function extractClientCertificateFromLambdaEvent(
|
|
77
|
+
event: LambdaEventWithClientCert | null | undefined
|
|
78
|
+
): LambdaExtractionResult;
|
package/lib/lambda.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* client-certificate-auth/lambda - AWS Lambda event adapter
|
|
3
|
+
* Copyright (C) 2013-2026 Tony Gies
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { pemToCertificate } from './parsers.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import('./extractor.js').ExtractionResult} ExtractionResult
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract a client certificate from an AWS API Gateway Lambda event.
|
|
15
|
+
*
|
|
16
|
+
* API Gateway HTTP API (v2.0 payload) delivers the validated mTLS client
|
|
17
|
+
* certificate as a pre-parsed object at
|
|
18
|
+
* `event.requestContext.authentication.clientCert`. The legacy REST API
|
|
19
|
+
* (v1.0 payload) delivers it at `event.requestContext.identity.clientCert`.
|
|
20
|
+
* Both payloads carry a `clientCertPem` field plus parsed `subjectDN`,
|
|
21
|
+
* `issuerDN`, `serialNumber`, and `validity` fields.
|
|
22
|
+
*
|
|
23
|
+
* Parses `clientCertPem` into a `PeerCertificate` so the same validation
|
|
24
|
+
* logic used with `getPeerCertificate()` or `extractClientCertificate()`
|
|
25
|
+
* works inside a Lambda handler. If both v1 and v2 fields are present,
|
|
26
|
+
* v2 takes precedence.
|
|
27
|
+
*
|
|
28
|
+
* @param {object | null | undefined} event - The Lambda event object from API Gateway (also accepts null/undefined)
|
|
29
|
+
* @returns {ExtractionResult}
|
|
30
|
+
*
|
|
31
|
+
* Rejection reasons:
|
|
32
|
+
* - 'lambda_event_missing_clientcert' - No clientCertPem at either v1 or v2 location
|
|
33
|
+
* - 'lambda_event_clientcert_malformed' - clientCertPem present but parsing failed
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* import { extractClientCertificateFromLambdaEvent } from 'client-certificate-auth/lambda';
|
|
37
|
+
*
|
|
38
|
+
* export const handler = async (event) => {
|
|
39
|
+
* const result = extractClientCertificateFromLambdaEvent(event);
|
|
40
|
+
* if (!result.success) return { statusCode: 401, body: result.reason };
|
|
41
|
+
* if (result.certificate.subject.CN !== 'authorized-client') {
|
|
42
|
+
* return { statusCode: 403 };
|
|
43
|
+
* }
|
|
44
|
+
* return { statusCode: 200, body: 'OK' };
|
|
45
|
+
* };
|
|
46
|
+
*/
|
|
47
|
+
export function extractClientCertificateFromLambdaEvent(event) {
|
|
48
|
+
const v2 = event?.requestContext?.authentication?.clientCert;
|
|
49
|
+
const v1 = event?.requestContext?.identity?.clientCert;
|
|
50
|
+
const clientCert = v2 ?? v1;
|
|
51
|
+
|
|
52
|
+
if (!clientCert?.clientCertPem) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
certificate: null,
|
|
56
|
+
reason: 'lambda_event_missing_clientcert',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
certificate: pemToCertificate(clientCert.clientCertPem),
|
|
64
|
+
reason: null,
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
certificate: null,
|
|
70
|
+
reason: 'lambda_event_clientcert_malformed',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
package/lib/parsers.d.ts
CHANGED
|
@@ -14,7 +14,14 @@ export type HeaderEncoding = 'url-pem' | 'url-pem-aws' | 'xfcc' | 'base64-der' |
|
|
|
14
14
|
/**
|
|
15
15
|
* Supported certificate source presets.
|
|
16
16
|
*/
|
|
17
|
-
export type CertificateSource =
|
|
17
|
+
export type CertificateSource =
|
|
18
|
+
| 'aws-alb'
|
|
19
|
+
| 'aws-alb-verify'
|
|
20
|
+
| 'azure-app-service'
|
|
21
|
+
| 'cloudflare'
|
|
22
|
+
| 'cloudflare-rfc9440'
|
|
23
|
+
| 'envoy'
|
|
24
|
+
| 'traefik';
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* Preset configuration for a reverse proxy.
|
|
@@ -22,6 +29,12 @@ export type CertificateSource = 'aws-alb' | 'envoy' | 'cloudflare' | 'traefik';
|
|
|
22
29
|
export interface PresetConfig {
|
|
23
30
|
/** HTTP header name (lowercase) */
|
|
24
31
|
header: string;
|
|
32
|
+
/**
|
|
33
|
+
* Optional second header carrying the certificate chain. Set on
|
|
34
|
+
* two-header schemes like RFC 9440 (Client-Cert + Client-Cert-Chain).
|
|
35
|
+
* Lowercased per Node convention.
|
|
36
|
+
*/
|
|
37
|
+
chainHeader?: string;
|
|
25
38
|
/** Encoding format used by this proxy */
|
|
26
39
|
encoding: HeaderEncoding;
|
|
27
40
|
}
|
package/lib/parsers.js
CHANGED
|
@@ -13,7 +13,8 @@ import { X509Certificate } from 'node:crypto';
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Preset configurations for common reverse proxies.
|
|
16
|
-
* Maps preset name to { header, encoding } configuration
|
|
16
|
+
* Maps preset name to { header, encoding } configuration, with optional
|
|
17
|
+
* `chainHeader` for two-header schemes (RFC 9440).
|
|
17
18
|
*/
|
|
18
19
|
export const PRESETS = {
|
|
19
20
|
/**
|
|
@@ -25,21 +26,55 @@ export const PRESETS = {
|
|
|
25
26
|
encoding: 'url-pem-aws',
|
|
26
27
|
},
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* AWS Application Load Balancer in mTLS verify mode. ALB validates the
|
|
30
|
+
* client certificate against a configured trust store and forwards the
|
|
31
|
+
* leaf as URL-encoded PEM (the leaf only, not the chain) plus parsed
|
|
32
|
+
* subject/issuer/serial/validity headers for convenience.
|
|
33
|
+
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html
|
|
30
34
|
*/
|
|
31
|
-
'
|
|
32
|
-
header: 'x-
|
|
33
|
-
encoding: '
|
|
35
|
+
'aws-alb-verify': {
|
|
36
|
+
header: 'x-amzn-mtls-clientcert-leaf',
|
|
37
|
+
encoding: 'url-pem-aws',
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* Azure App Service mTLS forwarding. App Service injects the bare
|
|
41
|
+
* base64-encoded DER (the body of a PEM cert without delimiters) into
|
|
42
|
+
* `X-ARR-ClientCert`. Same header convention is used by IIS/ARR.
|
|
43
|
+
* @see https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth
|
|
44
|
+
*/
|
|
45
|
+
'azure-app-service': {
|
|
46
|
+
header: 'x-arr-clientcert',
|
|
47
|
+
encoding: 'base64-der',
|
|
34
48
|
},
|
|
35
49
|
/**
|
|
36
|
-
* Cloudflare with client_certificate_forwarding enabled
|
|
50
|
+
* Cloudflare with client_certificate_forwarding enabled (legacy
|
|
51
|
+
* Cf-Client-Cert-* header family).
|
|
37
52
|
* @see https://developers.cloudflare.com/api-shield/security/mtls/configure/
|
|
38
53
|
*/
|
|
39
54
|
'cloudflare': {
|
|
40
55
|
header: 'cf-client-cert-der-base64',
|
|
41
56
|
encoding: 'base64-der',
|
|
42
57
|
},
|
|
58
|
+
/**
|
|
59
|
+
* Cloudflare with RFC 9440 forwarding enabled (March 2026 feature).
|
|
60
|
+
* Operators set `Client-Cert` and `Client-Cert-Chain` headers via
|
|
61
|
+
* Transform Rules. Leaf is `:base64:`-wrapped; chain is a structured
|
|
62
|
+
* field list of `:base64:` items separated by commas.
|
|
63
|
+
* @see https://developers.cloudflare.com/changelog/post/2026-03-25-rfc9440-mtls-fields/
|
|
64
|
+
*/
|
|
65
|
+
'cloudflare-rfc9440': {
|
|
66
|
+
header: 'client-cert',
|
|
67
|
+
chainHeader: 'client-cert-chain',
|
|
68
|
+
encoding: 'rfc9440',
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Envoy proxy / Istio service mesh using XFCC header.
|
|
72
|
+
* @see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert
|
|
73
|
+
*/
|
|
74
|
+
'envoy': {
|
|
75
|
+
header: 'x-forwarded-client-cert',
|
|
76
|
+
encoding: 'xfcc',
|
|
77
|
+
},
|
|
43
78
|
/**
|
|
44
79
|
* Traefik with PassTLSClientCert middleware (pem: true).
|
|
45
80
|
* Traefik sends raw base64 (no PEM delimiters, not URL-encoded).
|
|
@@ -126,7 +161,7 @@ function chainFromMultiBlockPem(pem) {
|
|
|
126
161
|
try {
|
|
127
162
|
return pemToCertificate(block);
|
|
128
163
|
} catch {
|
|
129
|
-
//
|
|
164
|
+
// Equivalent mutant (undefined also filtered by .filter(Boolean)) — catch-body BlockStatement unsuppressible via Stryker comments
|
|
130
165
|
return null;
|
|
131
166
|
}
|
|
132
167
|
}).filter(Boolean);
|
|
@@ -322,7 +357,7 @@ export function parseRfc9440(headerValue) {
|
|
|
322
357
|
let base64 = headerValue;
|
|
323
358
|
// Stryker disable next-line BlockStatement,ConditionalExpression,StringLiteral,LogicalOperator,MethodExpression: colon-stripping is defensive; all valid RFC 9440 inputs use :base64: format, so base64 decoder ignores colons either way
|
|
324
359
|
if (base64.startsWith(':') && base64.endsWith(':')) {
|
|
325
|
-
// Stryker disable next-line
|
|
360
|
+
// Stryker disable next-line MethodExpression: no-oping the assignment is equivalent — base64 decoder ignores colons
|
|
326
361
|
base64 = base64.slice(1, -1);
|
|
327
362
|
}
|
|
328
363
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "client-certificate-auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Express/Connect middleware for mTLS client certificate authentication with reverse proxy support (AWS ALB, Envoy, Cloudflare, Traefik)",
|
|
5
5
|
"homepage": "https://github.com/tgies/client-certificate-auth",
|
|
6
6
|
"bugs": {
|
|
@@ -51,6 +51,26 @@
|
|
|
51
51
|
"types": "./lib/extractor.d.cts",
|
|
52
52
|
"default": "./lib/extractor.cjs"
|
|
53
53
|
}
|
|
54
|
+
},
|
|
55
|
+
"./lambda": {
|
|
56
|
+
"import": {
|
|
57
|
+
"types": "./lib/lambda.d.ts",
|
|
58
|
+
"default": "./lib/lambda.js"
|
|
59
|
+
},
|
|
60
|
+
"require": {
|
|
61
|
+
"types": "./lib/lambda.d.cts",
|
|
62
|
+
"default": "./lib/lambda.cjs"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"./fetch": {
|
|
66
|
+
"import": {
|
|
67
|
+
"types": "./lib/fetch.d.ts",
|
|
68
|
+
"default": "./lib/fetch.js"
|
|
69
|
+
},
|
|
70
|
+
"require": {
|
|
71
|
+
"types": "./lib/fetch.d.cts",
|
|
72
|
+
"default": "./lib/fetch.cjs"
|
|
73
|
+
}
|
|
54
74
|
}
|
|
55
75
|
},
|
|
56
76
|
"main": "./lib/clientCertificateAuth.cjs",
|
|
@@ -65,6 +85,12 @@
|
|
|
65
85
|
],
|
|
66
86
|
"extractor": [
|
|
67
87
|
"./lib/extractor.d.ts"
|
|
88
|
+
],
|
|
89
|
+
"lambda": [
|
|
90
|
+
"./lib/lambda.d.ts"
|
|
91
|
+
],
|
|
92
|
+
"fetch": [
|
|
93
|
+
"./lib/fetch.d.ts"
|
|
68
94
|
]
|
|
69
95
|
}
|
|
70
96
|
},
|
|
@@ -73,8 +99,8 @@
|
|
|
73
99
|
},
|
|
74
100
|
"devDependencies": {
|
|
75
101
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
76
|
-
"@commitlint/cli": "^
|
|
77
|
-
"@commitlint/config-conventional": "^
|
|
102
|
+
"@commitlint/cli": "^21.0.0",
|
|
103
|
+
"@commitlint/config-conventional": "^21.0.0",
|
|
78
104
|
"@eslint/js": "^10.0.1",
|
|
79
105
|
"@stryker-mutator/core": "^9.6.0",
|
|
80
106
|
"@stryker-mutator/jest-runner": "^9.6.1",
|
|
@@ -85,7 +111,7 @@
|
|
|
85
111
|
"globals": "^17.5.0",
|
|
86
112
|
"husky": "^9.1.7",
|
|
87
113
|
"jest": "^30.3.0",
|
|
88
|
-
"lint-staged": "^
|
|
114
|
+
"lint-staged": "^17.0.4",
|
|
89
115
|
"selfsigned": "^5.5.0",
|
|
90
116
|
"typedoc": "^0.28.19",
|
|
91
117
|
"typedoc-plugin-markdown": "^4.11.0",
|
|
@@ -146,6 +172,7 @@
|
|
|
146
172
|
"lib/**/*.d.cts"
|
|
147
173
|
],
|
|
148
174
|
"overrides": {
|
|
149
|
-
"fflate": "0.8.2"
|
|
175
|
+
"fflate": "0.8.2",
|
|
176
|
+
"qs": "^6.15.2"
|
|
150
177
|
}
|
|
151
178
|
}
|