client-certificate-auth 1.3.0 → 1.3.2
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 +24 -9
- package/lib/helpers.js +20 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Express/Connect middleware for client SSL certificate authentication (mTLS).
|
|
|
7
7
|
[](https://codecov.io/gh/tgies/client-certificate-auth)
|
|
8
8
|
[](https://dashboard.stryker-mutator.io/reports/github.com/tgies/client-certificate-auth/master)
|
|
9
9
|
|
|
10
|
-
100% line/branch/function/statement coverage, plus mutation testing and E2E tests against real nginx/Envoy/Traefik containers. ~4,
|
|
10
|
+
100% line/branch/function/statement coverage, plus mutation testing and E2E tests against real nginx/Envoy/Traefik containers. ~4,742 lines of test code for ~709 lines of source (measured by [cloc](https://github.com/AlDanial/cloc)).
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -413,7 +413,11 @@ app.use(clientCertificateAuth(checkAuth, {
|
|
|
413
413
|
| `aws-alb` | `X-Amzn-Mtls-Clientcert` | URL-encoded PEM (AWS variant) |
|
|
414
414
|
| `envoy` | `X-Forwarded-Client-Cert` | XFCC structured format |
|
|
415
415
|
| `cloudflare` | `Cf-Client-Cert-Der-Base64` | Base64-encoded DER |
|
|
416
|
-
| `traefik` | `X-Forwarded-Tls-Client-Cert` | Base64-encoded DER |
|
|
416
|
+
| `traefik` | `X-Forwarded-Tls-Client-Cert` | Base64-encoded DER \* |
|
|
417
|
+
|
|
418
|
+
> \* **Traefik note:** The `traefik` preset targets Traefik v3's `PassTLSClientCert` middleware with `pem: true`. Despite Traefik's docs describing this as "PEM format", the wire format is the base64 body without PEM headers — equivalent to base64-encoded DER. Behavior may differ in Traefik v2.
|
|
419
|
+
|
|
420
|
+
> **Cloudflare note:** Cloudflare also provides certificates via the `CF-Client-Cert-PEM` header (URL-encoded PEM). If you use that header instead, configure manually with `certificateHeader: 'CF-Client-Cert-PEM'` and `headerEncoding: 'url-pem'`.
|
|
417
421
|
|
|
418
422
|
### Custom Headers
|
|
419
423
|
|
|
@@ -604,7 +608,7 @@ import clientCertificateAuth from 'client-certificate-auth';
|
|
|
604
608
|
import { allowCN, allowFingerprints, allowIssuer, allOf, anyOf } from 'client-certificate-auth/helpers';
|
|
605
609
|
```
|
|
606
610
|
|
|
607
|
-
> **Note:**
|
|
611
|
+
> **Note:** In CommonJS, the `/helpers`, `/parsers`, and `/extractor` subpath exports provide a `load()` function for async access. See the [CommonJS](#commonjs) section for details.
|
|
608
612
|
|
|
609
613
|
### Basic Helpers
|
|
610
614
|
|
|
@@ -762,13 +766,24 @@ The `load()` function dynamically imports the ESM module and caches it. Subseque
|
|
|
762
766
|
| `verifyHeader` / `verifyValue` | No | Yes |
|
|
763
767
|
| `fallbackToSocket` | No | Yes |
|
|
764
768
|
|
|
765
|
-
|
|
769
|
+
### Subpath Exports in CJS
|
|
770
|
+
|
|
771
|
+
The `/helpers`, `/parsers`, and `/extractor` subpath exports each provide a `load()` function for async access in CommonJS. The individual functions are not synchronously available via `require()`.
|
|
766
772
|
|
|
767
773
|
```javascript
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
774
|
+
// Helpers
|
|
775
|
+
const { load } = require('client-certificate-auth/helpers');
|
|
776
|
+
const { allowCN, allOf, allowIssuer } = await load();
|
|
777
|
+
|
|
778
|
+
// Extractor
|
|
779
|
+
const { load: loadExtractor } = require('client-certificate-auth/extractor');
|
|
780
|
+
const { extractClientCertificate } = await loadExtractor();
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
Alternatively, you can use dynamic `import()`:
|
|
784
|
+
|
|
785
|
+
```javascript
|
|
786
|
+
const { allowCN } = await import('client-certificate-auth/helpers');
|
|
772
787
|
```
|
|
773
788
|
|
|
774
789
|
## Testing
|
|
@@ -886,7 +901,7 @@ const clientCertificateAuth = await require('client-certificate-auth').load();
|
|
|
886
901
|
|
|
887
902
|
The sync CJS wrapper does not support reverse proxy options (`certificateSource`, `certificateHeader`, etc.). Passing these options will throw a descriptive error. Use `load()` to access the full ESM module from CJS code. See the [CommonJS](#commonjs) section for details.
|
|
888
903
|
|
|
889
|
-
The `/helpers
|
|
904
|
+
The `/helpers`, `/parsers`, and `/extractor` subpath exports each provide a `load()` function in CJS. See [Subpath Exports in CJS](#subpath-exports-in-cjs) for details.
|
|
890
905
|
|
|
891
906
|
## License
|
|
892
907
|
|
package/lib/helpers.js
CHANGED
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
* @typedef {(cert: PeerCertificate, req?: import('http').IncomingMessage) => boolean | Promise<boolean>} ValidationCallback
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a certificate DN field value to an array.
|
|
14
|
+
* Node.js returns string for single-valued and string[] for multi-valued DN attributes.
|
|
15
|
+
* @param {string | string[] | undefined} value
|
|
16
|
+
* @returns {string[]}
|
|
17
|
+
*/
|
|
18
|
+
function toArray(value) {
|
|
19
|
+
if (value === undefined || value === null) {return [];}
|
|
20
|
+
return Array.isArray(value) ? value : [value];
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
/**
|
|
13
24
|
* Create a validation callback that allows certificates with matching Common Names.
|
|
14
25
|
*
|
|
@@ -20,7 +31,7 @@
|
|
|
20
31
|
*/
|
|
21
32
|
export function allowCN(names) {
|
|
22
33
|
const allowed = new Set(names);
|
|
23
|
-
return (cert) =>
|
|
34
|
+
return (cert) => toArray(cert.subject?.CN).some((cn) => allowed.has(cn));
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
/**
|
|
@@ -74,9 +85,10 @@ export function allowFingerprints(fingerprints) {
|
|
|
74
85
|
*/
|
|
75
86
|
export function allowIssuer(match) {
|
|
76
87
|
const entries = Object.entries(match);
|
|
88
|
+
if (entries.length === 0) {return () => false;}
|
|
77
89
|
return (cert) => {
|
|
78
90
|
if (!cert.issuer) {return false;}
|
|
79
|
-
return entries.every(([key, value]) => cert.issuer[key]
|
|
91
|
+
return entries.every(([key, value]) => toArray(cert.issuer[key]).includes(value));
|
|
80
92
|
};
|
|
81
93
|
}
|
|
82
94
|
|
|
@@ -92,9 +104,10 @@ export function allowIssuer(match) {
|
|
|
92
104
|
*/
|
|
93
105
|
export function allowSubject(match) {
|
|
94
106
|
const entries = Object.entries(match);
|
|
107
|
+
if (entries.length === 0) {return () => false;}
|
|
95
108
|
return (cert) => {
|
|
96
109
|
if (!cert.subject) {return false;}
|
|
97
|
-
return entries.every(([key, value]) => cert.subject[key]
|
|
110
|
+
return entries.every(([key, value]) => toArray(cert.subject[key]).includes(value));
|
|
98
111
|
};
|
|
99
112
|
}
|
|
100
113
|
|
|
@@ -109,7 +122,7 @@ export function allowSubject(match) {
|
|
|
109
122
|
*/
|
|
110
123
|
export function allowOU(ous) {
|
|
111
124
|
const allowed = new Set(ous);
|
|
112
|
-
return (cert) =>
|
|
125
|
+
return (cert) => toArray(cert.subject?.OU).some((ou) => allowed.has(ou));
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
/**
|
|
@@ -123,7 +136,7 @@ export function allowOU(ous) {
|
|
|
123
136
|
*/
|
|
124
137
|
export function allowOrganization(orgs) {
|
|
125
138
|
const allowed = new Set(orgs);
|
|
126
|
-
return (cert) =>
|
|
139
|
+
return (cert) => toArray(cert.subject?.O).some((o) => allowed.has(o));
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
/**
|
|
@@ -196,9 +209,9 @@ export function allowEmail(emails) {
|
|
|
196
209
|
const allowed = new Set(emails.map((e) => e.toLowerCase()));
|
|
197
210
|
|
|
198
211
|
return (cert) => {
|
|
199
|
-
// Check subject.emailAddress
|
|
212
|
+
// Check subject.emailAddress (may be string or string[] for multi-valued)
|
|
200
213
|
if (cert.subject?.emailAddress) {
|
|
201
|
-
if (
|
|
214
|
+
if (toArray(cert.subject.emailAddress).some((e) => allowed.has(e.toLowerCase()))) {
|
|
202
215
|
return true;
|
|
203
216
|
}
|
|
204
217
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "client-certificate-auth",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
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": {
|