client-certificate-auth 1.3.5 → 1.3.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.
@@ -19,6 +19,19 @@ async function loadModule() {
19
19
  return _default;
20
20
  }
21
21
 
22
+ /**
23
+ * Duck-typed thenable check per Promises/A+: an object or function with
24
+ * a callable `.then` method. Matches the set of values `Promise.resolve`
25
+ * natively adopts as thenables.
26
+ * @param {unknown} value
27
+ * @returns {boolean}
28
+ */
29
+ function isThenable(value) {
30
+ return value !== null
31
+ && (typeof value === 'object' || typeof value === 'function')
32
+ && typeof value.then === 'function';
33
+ }
34
+
22
35
  /**
23
36
  * Options not supported by the sync CJS wrapper.
24
37
  * These require the ESM module's async header parsing.
@@ -72,8 +85,8 @@ function clientCertificateAuth(callback, options = {}) {
72
85
  queueMicrotask(() => {
73
86
  try {
74
87
  const result = hook(...args);
75
- if (result instanceof Promise) {
76
- result.catch(err => console.error('client-certificate-auth: hook error:', err));
88
+ if (isThenable(result)) {
89
+ Promise.resolve(result).catch(err => console.error('client-certificate-auth: hook error:', err));
77
90
  }
78
91
  } catch (err) {
79
92
  console.error('client-certificate-auth: hook error:', err);
@@ -118,8 +131,8 @@ function clientCertificateAuth(callback, options = {}) {
118
131
 
119
132
  try {
120
133
  const result = callback(cert, req);
121
- if (result instanceof Promise) {
122
- result.then(doneAuthorizing).catch((err) => {
134
+ if (isThenable(result)) {
135
+ Promise.resolve(result).then(doneAuthorizing).catch((err) => {
123
136
  safeCallHook(onRejected, cert, req, err.message || 'callback_threw');
124
137
  if (err.status === undefined) {
125
138
  err.status = 401;
@@ -7,6 +7,19 @@
7
7
 
8
8
  import { extractClientCertificate } from './extractor.js';
9
9
 
10
+ /**
11
+ * Duck-typed thenable check per Promises/A+: an object or function with
12
+ * a callable `.then` method. Matches the set of values `Promise.resolve`
13
+ * natively adopts as thenables.
14
+ * @param {unknown} value
15
+ * @returns {boolean}
16
+ */
17
+ function isThenable(value) {
18
+ return value !== null
19
+ && (typeof value === 'object' || typeof value === 'function')
20
+ && typeof /** @type {{then?: unknown}} */ (value).then === 'function';
21
+ }
22
+
10
23
  /**
11
24
  * @typedef {import('http').IncomingMessage & { secure?: boolean; socket: import('tls').TLSSocket & { authorized?: boolean; authorizationError?: string }; clientCertificate?: import('tls').PeerCertificate }} ClientCertRequest
12
25
  * @typedef {import('http').ServerResponse & { redirect: (statusOrUrl: number | string, url?: string) => void }} ClientCertResponse
@@ -107,8 +120,8 @@ export default function clientCertificateAuth(callback, options = {}) {
107
120
  queueMicrotask(() => {
108
121
  try {
109
122
  const result = hook(...args);
110
- if (result instanceof Promise) {
111
- result.catch(err => console.error('client-certificate-auth: hook error:', err));
123
+ if (isThenable(result)) {
124
+ Promise.resolve(result).catch(err => console.error('client-certificate-auth: hook error:', err));
112
125
  }
113
126
  } catch (err) {
114
127
  console.error('client-certificate-auth: hook error:', err);
@@ -177,8 +190,8 @@ export default function clientCertificateAuth(callback, options = {}) {
177
190
 
178
191
  try {
179
192
  const callbackResult = callback(cert, req);
180
- if (callbackResult instanceof Promise) {
181
- callbackResult.then(doneAuthorizing).catch((err) => {
193
+ if (isThenable(callbackResult)) {
194
+ Promise.resolve(callbackResult).then(doneAuthorizing).catch((err) => {
182
195
  safeCallHook(onRejected, cert, req, err.message || 'callback_threw');
183
196
  if (err.status === undefined) {
184
197
  err.status = 401;
package/lib/helpers.js CHANGED
@@ -38,7 +38,8 @@ export function allowCN(names) {
38
38
  * Create a validation callback that allows certificates with matching fingerprints.
39
39
  * Supports SHA-1 fingerprints (compared against cert.fingerprint) and SHA-256
40
40
  * fingerprints with "SHA256:" prefix (compared against cert.fingerprint256).
41
- * Fingerprints without a prefix are treated as SHA-1.
41
+ * Fingerprints without a prefix are treated as SHA-1. Hex inputs are
42
+ * normalized: case and colon delimiters are ignored on both sides.
42
43
  *
43
44
  * @param {string[]} fingerprints - Allowed fingerprints
44
45
  * @returns {ValidationCallback}
@@ -46,28 +47,30 @@ export function allowCN(names) {
46
47
  * @example
47
48
  * app.use(clientCertificateAuth(allowFingerprints([
48
49
  * 'SHA256:AB:CD:EF:...', // matched against cert.fingerprint256
49
- * 'AB:CD:EF:...' // matched against cert.fingerprint (SHA-1)
50
+ * 'AB:CD:EF:...', // colon-delimited, matched against cert.fingerprint
51
+ * 'ABCDEF...' // contiguous hex also matches cert.fingerprint
50
52
  * ])));
51
53
  */
52
54
  export function allowFingerprints(fingerprints) {
55
+ const normalize = (fp) => fp.toUpperCase().replace(/:/g, '');
53
56
  const sha256Allowed = new Set();
54
57
  const sha1Allowed = new Set();
55
58
 
56
59
  for (const fp of fingerprints) {
57
60
  const upper = fp.toUpperCase();
58
61
  if (upper.startsWith('SHA256:')) {
59
- sha256Allowed.add(upper.slice(7));
62
+ sha256Allowed.add(normalize(upper.slice(7)));
60
63
  } else {
61
- sha1Allowed.add(upper);
64
+ sha1Allowed.add(normalize(upper));
62
65
  }
63
66
  }
64
67
 
65
68
  return (cert) => {
66
69
  if (sha1Allowed.size > 0 && cert.fingerprint) {
67
- if (sha1Allowed.has(cert.fingerprint.toUpperCase())) {return true;}
70
+ if (sha1Allowed.has(normalize(cert.fingerprint))) {return true;}
68
71
  }
69
72
  if (sha256Allowed.size > 0 && cert.fingerprint256) {
70
- if (sha256Allowed.has(cert.fingerprint256.toUpperCase())) {return true;}
73
+ if (sha256Allowed.has(normalize(cert.fingerprint256))) {return true;}
71
74
  }
72
75
  return false;
73
76
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "client-certificate-auth",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
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": {