lacewing 1.0.0 → 1.0.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/GETTING-STARTED.md +654 -0
  3. package/README.md +151 -28
  4. package/SECURITY.md +21 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/http/bearer.js +6 -10
  8. package/dist/src/http/bearer.js.map +1 -1
  9. package/dist/src/http/cookies.js +15 -12
  10. package/dist/src/http/cookies.js.map +1 -1
  11. package/dist/src/http/source.js +49 -0
  12. package/dist/src/http/source.js.map +1 -0
  13. package/dist/src/jwks/local.js +3 -2
  14. package/dist/src/jwks/local.js.map +1 -1
  15. package/dist/src/jwks/remote.js +82 -13
  16. package/dist/src/jwks/remote.js.map +1 -1
  17. package/dist/src/jwt/decrypt.js +10 -0
  18. package/dist/src/jwt/decrypt.js.map +1 -1
  19. package/dist/src/jwt/decryption_profile.js +6 -0
  20. package/dist/src/jwt/decryption_profile.js.map +1 -1
  21. package/dist/src/jwt/profile.js +7 -0
  22. package/dist/src/jwt/profile.js.map +1 -1
  23. package/dist/src/lib/claims.js +12 -2
  24. package/dist/src/lib/claims.js.map +1 -1
  25. package/dist/src/lib/entropy.js +7 -1
  26. package/dist/src/lib/entropy.js.map +1 -1
  27. package/dist/src/lib/payload_hygiene.js +40 -6
  28. package/dist/src/lib/payload_hygiene.js.map +1 -1
  29. package/dist/src/types.js.map +1 -1
  30. package/dist/types/index.d.ts +1 -0
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/src/http/bearer.d.ts +6 -3
  33. package/dist/types/src/http/bearer.d.ts.map +1 -1
  34. package/dist/types/src/http/cookies.d.ts +7 -3
  35. package/dist/types/src/http/cookies.d.ts.map +1 -1
  36. package/dist/types/src/http/source.d.ts +27 -0
  37. package/dist/types/src/http/source.d.ts.map +1 -0
  38. package/dist/types/src/jwks/local.d.ts +9 -1
  39. package/dist/types/src/jwks/local.d.ts.map +1 -1
  40. package/dist/types/src/jwks/remote.d.ts +12 -1
  41. package/dist/types/src/jwks/remote.d.ts.map +1 -1
  42. package/dist/types/src/jwt/decrypt.d.ts.map +1 -1
  43. package/dist/types/src/jwt/decryption_profile.d.ts +3 -1
  44. package/dist/types/src/jwt/decryption_profile.d.ts.map +1 -1
  45. package/dist/types/src/jwt/profile.d.ts +7 -1
  46. package/dist/types/src/jwt/profile.d.ts.map +1 -1
  47. package/dist/types/src/lib/claims.d.ts.map +1 -1
  48. package/dist/types/src/lib/entropy.d.ts.map +1 -1
  49. package/dist/types/src/lib/payload_hygiene.d.ts.map +1 -1
  50. package/dist/types/src/types.d.ts +6 -0
  51. package/dist/types/src/types.d.ts.map +1 -1
  52. package/package.json +5 -5
package/README.md CHANGED
@@ -3,15 +3,15 @@
3
3
 
4
4
  **Lacewing is an opinionated JWT library that makes common JWT security mistakes impossible by construction.**
5
5
 
6
- Instead of exposing low-level primitives and trusting you to compose them correctly, Lacewing provides secure verification profiles, safe defaults, mandatory claim validation, built-in revocation support, secure cookie helpers, and a curated algorithm set - everything [RFC 8725 (JWT Best Current Practices)](https://datatracker.ietf.org/doc/html/rfc8725) says an application MUST or SHOULD do is enforced by the type system, enforced at runtime, or impossible to express.
6
+ Instead of exposing low-level primitives and trusting you to compose them correctly, Lacewing provides secure verification profiles, safe defaults, mandatory claim validation, built-in revocation support, secure cookie helpers, and a curated algorithm set. Everything [RFC 8725 (JWT Best Current Practices)](https://datatracker.ietf.org/doc/html/rfc8725) says an application MUST or SHOULD do is enforced by the type system, enforced at runtime, or impossible to express.
7
7
 
8
8
  **Batteries loaded (RFC 8725).** Out of the box:
9
9
 
10
- - **Verification profiles** - the only way to verify; `typ`, issuer, audience, algorithm allowlist and key source are structurally mandatory
11
- - **Remote JWKS** with caching, rotation handling, and `kid` hygiene
12
- - **Token revocation** - unique `jti` on every token, pluggable `RevocationStore`, in-memory store included
13
- - **Secure cookie + bearer helpers** - `HttpOnly; Secure; SameSite` enforced, strict RFC 6750 parsing
14
- - **Access/refresh token presets** - mutually exclusive by `typ`, shipped rather than left as homework
10
+ - **Verification profiles**: the only way to verify; `typ`, issuer, audience, algorithm allowlist and key source are structurally mandatory
11
+ - **Remote JWKS** with caching, rotation handling, `kid` hygiene, and a *bounded* stale-serving window when the endpoint goes down
12
+ - **Token revocation**: unique `jti` on every token, pluggable `RevocationStore`, in-memory store included
13
+ - **Secure cookie + bearer helpers**: `HttpOnly; Secure; SameSite` enforced, strict RFC 6750 parsing
14
+ - **Access/refresh token presets**: mutually exclusive by `typ`, shipped rather than left as homework
15
15
  - **Encrypted JWTs (JWE)** with the same profile discipline
16
16
  - **Payload hygiene scanning** at sign time (passwords, card numbers, PEM keys never leave in plaintext)
17
17
  - **Typed errors** with machine-readable codes and no attacker-facing oracle
@@ -28,9 +28,14 @@ Instead of exposing low-level primitives and trusting you to compose them correc
28
28
  npm install lacewing
29
29
  ```
30
30
 
31
+ [Getting started](./GETTING-STARTED.md) walks through adopting Lacewing in a
32
+ real service: keys, profiles, transport, refresh tokens, revocation, key
33
+ rotation, and a migration path from `jsonwebtoken`. The rest of this README is
34
+ the pitch and the reference.
35
+
31
36
  ## Quick start
32
37
 
33
- The complete lifecycle - generate a key, sign, define a profile once, verify everywhere:
38
+ The complete lifecycle (generate a key, sign, define a profile once, verify everywhere):
34
39
 
35
40
  ```ts
36
41
  import { SignJWT, defineProfile, jwtVerify, generateKeyPair } from "lacewing";
@@ -53,16 +58,16 @@ const profile = defineProfile({
53
58
  maxTokenAge: "10m",
54
59
  });
55
60
 
56
- const { payload } = await jwtVerify(token, profile); // VerifiedJwt - every check passed
61
+ const { payload } = await jwtVerify(token, profile); // VerifiedJwt: every check passed
57
62
  ```
58
63
 
59
64
  A runnable version of everything in this README ships with the repo: `npm run demo`.
60
65
 
61
66
  ## Concepts
62
67
 
63
- **Profiles are the central abstraction.** Configuration is something you create once - not something every endpoint reinvents. A profile names the expected `typ`, the trusted issuer, the required audience, an explicit algorithm allowlist, and the key source bound to that issuer. `jwtVerify(token, profile)` is the *only* verify path: there is no `decode()`, no `ignoreExpiration`, no "accept whatever the header says" mode. If any check fails you get a typed error (`JWTExpired`, `AlgorithmNotAllowed`, `JWTClaimValidationFailed`, ...) and no partial result.
68
+ **Profiles are the central abstraction.** Configuration is something you create once, not something every endpoint reinvents. A profile names the expected `typ`, the trusted issuer, the required audience, an explicit algorithm allowlist, and the key source bound to that issuer. `jwtVerify(token, profile)` is the *only* verify path: there is no `decode()`, no `ignoreExpiration`, no "accept whatever the header says" mode. If any check fails you get a typed error (`JWTExpired`, `AlgorithmNotAllowed`, `JWTClaimValidationFailed`, ...) and no partial result.
64
69
 
65
- **Built on jose.** Lacewing does not reimplement cryptography. It is a hardened policy layer over [jose](https://github.com/panva/jose) - the audited, maintained, runtime-portable JOSE implementation - so the novel code is the policy, not the crypto. Tokens Lacewing signs or encrypts are standard JWTs/JWEs that jose (and any other conforming implementation) can consume, and the conformance suite proves it against the RFC 7515/7516/7519 worked examples plus tokens produced by OpenSSL and python-cryptography.
70
+ **Built on jose.** Lacewing does not reimplement cryptography. It is a hardened policy layer over [jose](https://github.com/panva/jose) (the audited, maintained, runtime-portable JOSE implementation), so the novel code is the policy, not the crypto. Tokens Lacewing signs or encrypts are standard JWTs/JWEs that jose (and any other conforming implementation) can consume, and the conformance suite proves it against the RFC 7515/7516/7519 worked examples plus tokens produced by OpenSSL and python-cryptography.
66
71
 
67
72
  ## Usage
68
73
 
@@ -82,16 +87,85 @@ const accessToken = defineProfile({
82
87
  maxTokenAge: "10m",
83
88
  });
84
89
 
85
- const { payload } = await jwtVerify(token, accessToken); // VerifiedJwt - every check passed
90
+ const { payload } = await jwtVerify(token, accessToken); // VerifiedJwt: every check passed
91
+ ```
92
+
93
+ **When the endpoint goes down**, the last good keys keep serving for a bounded
94
+ window (1h by default) and then verification **fails closed**. The bound is the
95
+ whole point: keys that go stale forever mean a key you rotated out *because it
96
+ was compromised* stays trusted for exactly as long as an attacker can keep your
97
+ JWKS unreachable. Trade availability against freshness explicitly:
98
+
99
+ ```ts
100
+ createRemoteJWKSet("https://auth.example.com/jwks", {
101
+ cacheTtlSeconds: 300, // fallback when the response sends no Cache-Control
102
+ cooldownSeconds: 30, // floor between fetch attempts
103
+ staleWhileErrorSeconds: 3600, // how long a dead endpoint may coast; 0 = never
104
+ });
105
+ ```
106
+
107
+ Two more things that endpoint does not get to do. The response is read under a
108
+ hard byte budget, checked against `Content-Length` and again on every chunk, so
109
+ a hostile or broken endpoint cannot stream you out of memory. And symmetric
110
+ (`oct`) entries are refused from a *remote* set: a JWKS endpoint is public, so a
111
+ secret published there is one every reader could **mint** with, not merely
112
+ verify with. A local JWKS may still carry them.
113
+
114
+ ### Verify: keys you already have
115
+
116
+ Not everyone has a JWKS endpoint to point at. If the issuer handed you a raw
117
+ public key, or you're pinning a single known one, the same `keys` field takes it
118
+ directly. No URL, no fetch. `importKey` reads a PEM (SPKI public key), a JWK, or
119
+ a `CryptoKey`, and binds it to exactly one algorithm:
120
+
121
+ ```ts
122
+ import { defineProfile, importKey } from "lacewing";
123
+
124
+ const publicKey = await importKey(spkiPem, "ES256"); // PEM string, JWK, or CryptoKey
125
+
126
+ const profile = defineProfile({
127
+ typ: "at+jwt",
128
+ issuer: "https://auth.example.com",
129
+ audience: "https://api.example.com",
130
+ algorithms: ["ES256"],
131
+ keys: publicKey, // one pinned key, nothing to fetch
132
+ maxTokenAge: "10m",
133
+ });
86
134
  ```
87
135
 
136
+ Hand it a private key by mistake and it refuses: profiles verify, they don't
137
+ sign, and the error says exactly that. The key's algorithm has to be one you
138
+ allowlisted too, so a key you imported for `ES256` won't quietly slip into an
139
+ `EdDSA`-only profile.
140
+
141
+ Got a handful of keys but still no endpoint to serve them from? Pass a JWKS
142
+ document straight in, the same shape a `/jwks` URL would return, just inlined:
143
+
144
+ ```ts
145
+ keys: {
146
+ keys: [
147
+ { kty: "OKP", crv: "Ed25519", x: "...", kid: "2024-key" },
148
+ { kty: "EC", crv: "P-256", x: "...", y: "...", kid: "2025-key" },
149
+ ],
150
+ },
151
+ ```
152
+
153
+ Selection is exact: the token's `kid` (and its algorithm's key type and curve)
154
+ has to single out one key. Two keys that both match with no `kid` to tell them
155
+ apart is treated as your mistake, not something to guess at.
156
+
157
+ And if your keys live somewhere less ordinary (an HSM, a secrets manager, a
158
+ database), anything with a `getVerificationKey(header)` method is a valid `keys`
159
+ value. That's the escape hatch; the two built-in sources are just the ones
160
+ you'll reach for most.
161
+
88
162
  ### Sign
89
163
 
90
164
  `typ` is a constructor argument, and `.sign()` refuses to run without
91
165
  `issuer`, `audience` and `expiresIn` (waivable only via grep-loud
92
166
  `unsafeAllowMissing*` calls). Every token gets a unique `jti`, lifetimes are
93
167
  capped (default 1h), and a hygiene scanner rejects payloads that contain
94
- things like passwords, card numbers, PEM keys, or other JWTs - because **a
168
+ things like passwords, card numbers, PEM keys, or other JWTs, because **a
95
169
  JWS payload is base64url-encoded plaintext, readable by anyone who holds the
96
170
  token**. Never put secrets in it.
97
171
 
@@ -112,28 +186,50 @@ const token = await new SignJWT("at+jwt")
112
186
  A note on HMAC (`HS256/384/512`): it is supported, but every verifier of an
113
187
  HMAC token holds the same secret and can therefore also **mint** them. For
114
188
  anything beyond a single service, use asymmetric keys + JWKS. HMAC secrets
115
- are entropy-checked at import - `"my-secret"` will not import, ever;
116
- `generateSecret()` gives you a proper one.
189
+ must meet the RFC 7518 length floor and are screened by an entropy
190
+ heuristic, so `"my-secret"` will not import, ever. The heuristic catches
191
+ short and low-entropy secrets, not every bad one a human can invent. PLEASE use
192
+ `generateSecret()` and don't hand-write key material. Symmetric keys are
193
+ also refused from a *remote* JWKS: that endpoint is public, so a secret
194
+ published there is one anyone can mint with.
117
195
 
118
196
  ### Transport: the paved road
119
197
 
120
- Do **not** put tokens in `localStorage` or `sessionStorage` - both are
198
+ Do **not** put tokens in `localStorage` or `sessionStorage`: both are
121
199
  readable by any script on the page, so one XSS means token theft. Use an
122
200
  `HttpOnly` cookie (browsers) or an in-memory bearer token (services):
123
201
 
124
202
  ```ts
125
203
  import { setTokenCookie, readTokenCookie, parseBearer } from "lacewing";
126
204
 
127
- setTokenCookie(response.headers, token); // always HttpOnly; Secure; SameSite - weaker is unrepresentable
205
+ setTokenCookie(response.headers, token); // always HttpOnly; Secure; SameSite, weaker is unrepresentable
128
206
  const fromCookie = readTokenCookie(request);
129
207
  const fromHeader = parseBearer(request); // strict RFC 6750; no query-string tokens, ever
130
208
  ```
131
209
 
210
+ Both readers refuse ambiguity rather than resolving it. `parseBearer` rejects
211
+ the `", "`-joined value `Headers` produces when a request carried two
212
+ `Authorization` headers, and `readTokenCookie` returns `undefined` when the
213
+ cookie name appears twice, the servers disagree about whether the first or the
214
+ last duplicate wins, and cookie shadowing is built on that disagreement.
215
+
216
+ Both readers take WHATWG `Headers` (or anything carrying them, like `Request`),
217
+ or the raw header string. Node-style frameworks - Express, Fastify, Koa - hand
218
+ you a plain object instead, so convert once at the edge; a shape the readers
219
+ don't accept throws rather than reading as "no token":
220
+
221
+ ```ts
222
+ const headers = new Headers(req.headers as Record<string, string>);
223
+ const fromCookie = readTokenCookie(headers);
224
+
225
+ res.setHeader("Set-Cookie", buildTokenCookie(token)); // the response side is just a string
226
+ ```
227
+
132
228
  ### Access vs refresh (the cookbook)
133
229
 
134
230
  The single most common token-confusion bug is letting a refresh token buy API
135
231
  access, or an access token mint new sessions. Lacewing **ships** the two
136
- profiles rather than leaving them as homework - they are mutually exclusive by
232
+ profiles rather than leaving them as homework. They are mutually exclusive by
137
233
  `typ` (RFC 8725 §3.12), so presenting one where the other is expected fails,
138
234
  even with identical keys, claims and audience:
139
235
 
@@ -182,10 +278,37 @@ errors fail closed.
182
278
 
183
279
  ## Advanced
184
280
 
281
+ ### Age vs lifetime: two different caps
282
+
283
+ `maxTokenAge` and `maxTokenLifetime` sound alike and do different jobs.
284
+
285
+ **`maxTokenAge`** (required) bounds how long after issuance a token still works
286
+ *here* (`now - iat`). It is enforced independently of `exp`, so a token minted
287
+ with a decade-long `exp` stops being accepted once it is older than your cap,
288
+ no matter what the issuer wrote.
289
+
290
+ **`maxTokenLifetime`** (optional) bounds the span the issuer *declared*
291
+ (`exp - iat`). Note what `maxTokenAge` alone does with that decade-long token:
292
+ it accepts it, right up until the age window closes. That is usually fine, since
293
+ the age window is what actually governs. But an `exp` wildly beyond your own
294
+ policy is a misissuance signal, and this refuses it on sight instead of
295
+ tolerating it for the length of the window:
296
+
297
+ ```ts
298
+ const profile = defineProfile({
299
+ /* ...as above... */
300
+ maxTokenAge: "10m", // unusable 10m after iat, whatever exp claims
301
+ maxTokenLifetime: "1h", // and refuse anything declaring a span beyond 1h
302
+ });
303
+ ```
304
+
305
+ It is opt-in because plenty of legitimate issuers set a generous `exp` and
306
+ expect the verifier's own window to do the limiting.
307
+
185
308
  ### Revocation is not replay protection
186
309
 
187
310
  Be precise about what revocation buys you: it lets you kill a
188
- token you *know about* (logout, compromise). It does **not** stop replay - a
311
+ token you *know about* (logout, compromise). It does **not** stop replay: a
189
312
  valid, non-revoked token that leaks can be replayed by anyone who holds it
190
313
  until `exp`. That is the deliberate cost of stateless verification, and no
191
314
  JWT library can remove it without becoming a session store.
@@ -225,10 +348,10 @@ Your levers, in order of cheapness:
225
348
  ### Encrypting (JWE): when the payload really is a secret
226
349
 
227
350
  A signed JWT is readable plaintext. When you genuinely need the payload
228
- *hidden* - not just tamper-evident - use an encrypted JWT. Same discipline as
351
+ *hidden*, not just tamper-evident, use an encrypted JWT. Same discipline as
229
352
  signing (mandatory `typ`, required `iss`/`aud`/`exp`, unique `jti`, capped
230
353
  lifetime), but with a curated set of encryption algorithms and no `none`. The
231
- sign-time hygiene scanner is deliberately off here - hiding secrets is the
354
+ sign-time hygiene scanner is deliberately off here, since hiding secrets is the
232
355
  whole point:
233
356
 
234
357
  ```ts
@@ -242,7 +365,7 @@ const token = await new EncryptJWT("at+jwt", { contentEncryption: "A256GCM" })
242
365
  .issuer("https://auth.example.com")
243
366
  .audience("https://api.example.com")
244
367
  .subject("user-42")
245
- .claim("ssn", "123-45-6789") // fine - it's encrypted, not just encoded
368
+ .claim("ssn", "123-45-6789") // fine: it's encrypted, not just encoded
246
369
  .expiresIn("10m")
247
370
  .encrypt(publicKey);
248
371
 
@@ -256,24 +379,24 @@ const profile = defineDecryptionProfile({
256
379
  maxTokenAge: "10m",
257
380
  });
258
381
 
259
- const { payload } = await jwtDecrypt(token, profile); // DecryptedJwt - distinct from VerifiedJwt
382
+ const { payload } = await jwtDecrypt(token, profile); // DecryptedJwt, distinct from VerifiedJwt
260
383
  ```
261
384
 
262
385
  `none`, `RSA1_5`, `RSA-OAEP` (SHA-1) and the password-based `PBES2*` family are
263
386
  absent by construction. A JWS handed to `jwtDecrypt` (or a JWE handed to
264
- `jwtVerify`) is rejected - the two formats never cross over.
387
+ `jwtVerify`) is rejected: the two formats never cross over.
265
388
 
266
- **Portability caveat - 192-bit AES:** the registry includes the `A192*`
389
+ **Portability caveat (192-bit AES):** the registry includes the `A192*`
267
390
  algorithms (`A192KW`, `A192GCMKW`, `A192GCM`, `A192CBC-HS384`) for JOSE
268
391
  completeness, and they work on Node ≥ 24 (Lacewing's floor). But WebCrypto
269
392
  implementations in browsers and some edge runtimes do not implement 192-bit
270
393
  AES at all. If tokens must be decrypted outside Node, stick to the `A128*` /
271
- `A256*` variants - there is no security reason to prefer 192-bit anyway.
394
+ `A256*` variants. There is no security reason to prefer 192-bit anyway.
272
395
 
273
396
  ### Debugging
274
397
 
275
398
  `unsafeDecode(token)` parses without verifying and returns an `UntrustedJwt`
276
- that is type-incompatible with `VerifiedJwt` - useful for inspecting expired
399
+ that is type-incompatible with `VerifiedJwt`: useful for inspecting expired
277
400
  tokens, useless (by design) for auth logic.
278
401
 
279
402
  ### Legacy interop
@@ -304,9 +427,9 @@ The other reason is irritation. Generic JWT libraries are fine, but they leave e
304
427
  - No way to revoke stateless JWTs
305
428
  - Storing tokens in `localStorage` or `sessionStorage`
306
429
  - Not configuring `iss` or `aud`
307
- - Over-caching a JWKS when an endpoint goes down
430
+ - Over-caching a JWKS when an endpoint goes down (stale keys serve for a bounded window, then verification fails closed)
308
431
 
309
- Insecure usage is still *possible* - but only unmistakably deliberate, through `unsafe*`-prefixed escape hatches that are easy to grep for in code review. Secure usage is the path of least resistance.
432
+ Insecure usage is still *possible*, but only unmistakably deliberate, through `unsafe*`-prefixed escape hatches that are easy to grep for in code review. Secure usage is the path of least resistance.
310
433
 
311
434
  ## Attribution
312
435
 
package/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Use this section to tell people about which versions of your project are
6
+ currently being supported with security updates.
7
+
8
+ | Version | Supported |
9
+ | ------- | ------------------ |
10
+ | 5.1.x | :white_check_mark: |
11
+ | 5.0.x | :x: |
12
+ | 4.0.x | :white_check_mark: |
13
+ | < 4.0 | :x: |
14
+
15
+ ## Reporting a Vulnerability
16
+
17
+ Use this section to tell people how to report a vulnerability.
18
+
19
+ Tell them where to go, how often they can expect to get an update on a
20
+ reported vulnerability, what to expect if the vulnerability is accepted or
21
+ declined, etc.
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ export { MemoryRevocationStore } from "./src/revocation/memory.js";
31
31
  // HTTP transport helpers (the paved road: no localStorage, ever)
32
32
  export { buildTokenCookie, setTokenCookie, clearTokenCookie, readTokenCookie, } from "./src/http/cookies.js";
33
33
  export { parseBearer } from "./src/http/bearer.js";
34
+ export {} from "./src/http/source.js";
34
35
  // Debugging escape hatch - branded untrusted, incompatible with VerifiedJwt
35
36
  export { unsafeDecode } from "./src/util/unsafe-decode.js";
36
37
  // Typed errors
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oDAAoD;AACpD,OAAO,EAAE,aAAa,EAAuB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,UAAU;AACV,OAAO,EAAE,OAAO,EAAuB,MAAM,mBAAmB,CAAC;AAEjE,4DAA4D;AAC5D,OAAO,EAAE,UAAU,EAA0B,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EACN,uBAAuB,GAEvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACN,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,GAGnB,MAAM,yBAAyB,CAAC;AAEjC,8EAA8E;AAC9E,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAEjB,MAAM,kCAAkC,CAAC;AAE1C,OAAO;AACP,OAAO,EAAE,SAAS,EAAoB,MAAM,qBAAqB,CAAC;AAClE,OAAO,EACN,eAAe,EACf,cAAc,GAEd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEjE,mBAAmB;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAA4B,MAAM,sBAAsB,CAAC;AAEpF,aAAa;AACb,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,iEAAiE;AACjE,OAAO,EACN,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,eAAe,GAEf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,4EAA4E;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,eAAe;AACf,OAAO,EACN,QAAQ,EACR,UAAU,EACV,UAAU,EACV,wBAAwB,EACxB,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,GACjB,MAAM,sBAAsB,CAAC;AAE9B,eAAe;AACf,OAAO,EACN,SAAS,GAuBT,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oDAAoD;AACpD,OAAO,EAAE,aAAa,EAAuB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,UAAU;AACV,OAAO,EAAE,OAAO,EAAuB,MAAM,mBAAmB,CAAC;AAEjE,4DAA4D;AAC5D,OAAO,EAAE,UAAU,EAA0B,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EACN,uBAAuB,GAEvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACN,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,GAGnB,MAAM,yBAAyB,CAAC;AAEjC,8EAA8E;AAC9E,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAEjB,MAAM,kCAAkC,CAAC;AAE1C,OAAO;AACP,OAAO,EAAE,SAAS,EAAoB,MAAM,qBAAqB,CAAC;AAClE,OAAO,EACN,eAAe,EACf,cAAc,GAEd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEjE,mBAAmB;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAA4B,MAAM,sBAAsB,CAAC;AAEpF,aAAa;AACb,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,iEAAiE;AACjE,OAAO,EACN,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,eAAe,GAEf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAqB,MAAM,sBAAsB,CAAC;AAEzD,4EAA4E;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,eAAe;AACf,OAAO,EACN,QAAQ,EACR,UAAU,EACV,UAAU,EACV,wBAAwB,EACxB,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,GACjB,MAAM,sBAAsB,CAAC;AAE9B,eAAe;AACf,OAAO,EACN,SAAS,GAuBT,MAAM,gBAAgB,CAAC"}
@@ -6,6 +6,7 @@
6
6
  * whitespace tricks. One header, one scheme, one token.
7
7
  */
8
8
  import { BearerParseFailed } from "../util/errors.js";
9
+ import { readHeaderValue } from "./source.js";
9
10
  // RFC 6750 credentials: exact-case scheme, a single SP, one token68.
10
11
  const BEARER = /^Bearer ([A-Za-z0-9\-._~+/]+=*)$/;
11
12
  const MAX_HEADER_LENGTH = 16384;
@@ -17,18 +18,13 @@ const MAX_HEADER_LENGTH = 16384;
17
18
  *
18
19
  * Note: when multiple `Authorization` headers were sent, `Headers`
19
20
  * joins them with `", "` - which this parser rejects, by design.
21
+ *
22
+ * A shape this library does not accept - notably a Node/Express
23
+ * request, whose `headers` is a plain record - throws {@link TypeError}
24
+ * rather than the misleading "missing header" it used to report.
20
25
  */
21
26
  export function parseBearer(source) {
22
- let value;
23
- if (typeof source === "string") {
24
- value = source;
25
- }
26
- else if (source instanceof Headers) {
27
- value = source.get("authorization");
28
- }
29
- else if (typeof source === "object" && source !== null && source.headers instanceof Headers) {
30
- value = source.headers.get("authorization");
31
- }
27
+ const value = readHeaderValue(source, "authorization", "parseBearer");
32
28
  if (typeof value !== "string" || value.length === 0) {
33
29
  throw new BearerParseFailed("Missing Authorization header");
34
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bearer.js","sourceRoot":"","sources":["../../../src/http/bearer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,qEAAqE;AACrE,MAAM,MAAM,GAAG,kCAAkC,CAAC;AAClD,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAC1B,MAAkE;IAElE,IAAI,KAAgC,CAAC;IACrC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChC,KAAK,GAAG,MAAM,CAAC;IAChB,CAAC;SAAM,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;QACtC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,YAAY,OAAO,EAAE,CAAC;QAC/F,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,iBAAiB,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACtC,MAAM,IAAI,iBAAiB,CAAC,iDAAiD,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,iBAAiB,CAC1B,yFAAyF,CACzF,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAW,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"bearer.js","sourceRoot":"","sources":["../../../src/http/bearer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAqB,MAAM,aAAa,CAAC;AAEjE,qEAAqE;AACrE,MAAM,MAAM,GAAG,kCAAkC,CAAC;AAClD,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,MAAoB;IAC/C,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;IACtE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,iBAAiB,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACtC,MAAM,IAAI,iBAAiB,CAAC,iDAAiD,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,iBAAiB,CAC1B,yFAAyF,CACzF,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAW,CAAC;AAC3B,CAAC"}
@@ -9,6 +9,7 @@
9
9
  * Framework-agnostic: works on WHATWG `Headers` (and anything exposing
10
10
  * them, e.g. `Request`/`Response`).
11
11
  */
12
+ import { readHeaderValue } from "./source.js";
12
13
  // RFC 6265 cookie-name token; value charset covers compact JWTs.
13
14
  const COOKIE_NAME = /^[!#$%&'*+\-.^_`|~A-Za-z0-9]+$/;
14
15
  const COOKIE_VALUE = /^[A-Za-z0-9._~+/=-]*$/;
@@ -67,30 +68,32 @@ export function clearTokenCookie(headers, options = {}) {
67
68
  * Read a token cookie from a request. Accepts a `Headers` object,
68
69
  * anything with a `.headers` (e.g. `Request`), or the raw `Cookie`
69
70
  * header string. Returns `undefined` when absent or malformed.
71
+ *
72
+ * Throws {@link TypeError} on a shape it does not accept - notably a
73
+ * Node/Express request, whose `headers` is a plain record. "I was
74
+ * handed something I don't understand" must not be indistinguishable
75
+ * from "this request carried no token".
70
76
  */
71
77
  export function readTokenCookie(source, name = DEFAULT_COOKIE_NAME) {
72
- let cookieHeader;
73
- if (typeof source === "string") {
74
- cookieHeader = source;
75
- }
76
- else if (source instanceof Headers) {
77
- cookieHeader = source.get("cookie");
78
- }
79
- else if (typeof source === "object" && source !== null && source.headers instanceof Headers) {
80
- cookieHeader = source.headers.get("cookie");
81
- }
78
+ const cookieHeader = readHeaderValue(source, "cookie", "readTokenCookie");
82
79
  if (typeof cookieHeader !== "string" || cookieHeader.length > 16384) {
83
80
  return undefined;
84
81
  }
82
+ let found;
85
83
  for (const pair of cookieHeader.split(";")) {
86
84
  const eq = pair.indexOf("=");
87
85
  if (eq === -1)
88
86
  continue;
89
87
  if (pair.slice(0, eq).trim() !== name)
90
88
  continue;
89
+ // Cookie shadowing: servers disagree about whether the first or last
90
+ // duplicate wins, so a header carrying the name twice is ambiguous.
91
+ // Ambiguous input is refused rather than guessed at.
92
+ if (found !== undefined)
93
+ return undefined;
91
94
  const value = pair.slice(eq + 1).trim();
92
- return COOKIE_VALUE.test(value) && value.length > 0 ? value : undefined;
95
+ found = COOKIE_VALUE.test(value) && value.length > 0 ? value : "";
93
96
  }
94
- return undefined;
97
+ return found === undefined || found === "" ? undefined : found;
95
98
  }
96
99
  //# sourceMappingURL=cookies.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../../src/http/cookies.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../../src/http/cookies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Header-source narrowing shared by the cookie and bearer readers.
3
+ *
4
+ * The contract is deliberately narrow: WHATWG `Headers` (or anything
5
+ * carrying them, e.g. `Request`) or the raw header string. Node's
6
+ * `IncomingMessage.headers` is a plain record, not `Headers`, and is
7
+ * *not* accepted - callers on Express/Fastify/Koa convert once at the
8
+ * edge rather than making every reader here carry a second shape.
9
+ *
10
+ * What matters is that an unrecognized shape throws instead of reading
11
+ * as "header absent". A helper that silently returns nothing for an
12
+ * argument it never understood is one people route around, and the
13
+ * hand-rolled replacement has none of the ambiguity refusals below.
14
+ */
15
+ // Duck-typed rather than `instanceof`: a `Headers` from another realm
16
+ // (undici's own copy, a vm context, an edge/node boundary) is still a
17
+ // `Headers`, and `instanceof` would reject it. This widens nothing -
18
+ // a plain Node header record has no `.get` and still fails.
19
+ function isHeadersLike(value) {
20
+ return (typeof value === "object" && value !== null && typeof value.get === "function");
21
+ }
22
+ /**
23
+ * Read one header from an accepted source. Returns `undefined` when the
24
+ * header is absent; throws {@link TypeError} when `source` is a shape
25
+ * this library does not accept.
26
+ *
27
+ * @param helper - calling helper's name, used in the error message.
28
+ */
29
+ export function readHeaderValue(source, header, helper) {
30
+ // An explicit nothing - including the `null` that `Headers.get` itself
31
+ // returns - is a legitimate "no such header", not a caller mistake.
32
+ if (source === null || source === undefined)
33
+ return undefined;
34
+ if (typeof source === "string")
35
+ return source;
36
+ if (isHeadersLike(source)) {
37
+ const value = source.get(header);
38
+ return typeof value === "string" ? value : undefined;
39
+ }
40
+ if (typeof source === "object" && isHeadersLike(source.headers)) {
41
+ const value = source.headers.get(header);
42
+ return typeof value === "string" ? value : undefined;
43
+ }
44
+ throw new TypeError(`${helper}() expects a Headers, an object with a .headers (e.g. Request), a raw ` +
45
+ "header string, or null/undefined. Node-style requests (Express, Fastify, Koa) " +
46
+ "expose a plain object instead - convert it first: " +
47
+ "new Headers(req.headers as Record<string, string>)");
48
+ }
49
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../../src/http/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,sEAAsE;AACtE,sEAAsE;AACtE,qEAAqE;AACrE,4DAA4D;AAC5D,SAAS,aAAa,CAAC,KAAc;IACpC,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAQ,KAAiB,CAAC,GAAG,KAAK,UAAU,CAC3F,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC9B,MAAoB,EACpB,MAAc,EACd,MAAc;IAEd,uEAAuE;IACvE,oEAAoE;IACpE,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC9D,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,aAAa,CAAE,MAAgC,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5F,MAAM,KAAK,GAAI,MAA+B,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnE,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IACD,MAAM,IAAI,SAAS,CAClB,GAAG,MAAM,wEAAwE;QAChF,gFAAgF;QAChF,oDAAoD;QACpD,oDAAoD,CACrD,CAAC;AACH,CAAC"}
@@ -26,8 +26,9 @@ function isCandidate(jwk, header) {
26
26
  return true;
27
27
  }
28
28
  /** Shared selection + import used by both local and remote sets. */
29
- export async function resolveFromJwks(keys, header) {
30
- const candidates = keys.filter((jwk) => isCandidate(jwk, header));
29
+ export async function resolveFromJwks(keys, header, options = {}) {
30
+ const allowSymmetric = options.allowSymmetric ?? true;
31
+ const candidates = keys.filter((jwk) => isCandidate(jwk, header) && (allowSymmetric || jwk.kty !== "oct"));
31
32
  if (candidates.length === 0) {
32
33
  throw new JWKSNoMatchingKey("No key in the JWKS matches this token");
33
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/jwks/local.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/jwks/local.ts"],"names":[],"mappings":"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"}