lacewing 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -0
  3. package/NOTICE.md +1 -0
  4. package/README.md +313 -0
  5. package/dist/index.js +40 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/cookbook/access-refresh.js +86 -0
  8. package/dist/src/cookbook/access-refresh.js.map +1 -0
  9. package/dist/src/http/bearer.js +44 -0
  10. package/dist/src/http/bearer.js.map +1 -0
  11. package/dist/src/http/cookies.js +96 -0
  12. package/dist/src/http/cookies.js.map +1 -0
  13. package/dist/src/jwks/local.js +85 -0
  14. package/dist/src/jwks/local.js.map +1 -0
  15. package/dist/src/jwks/remote.js +121 -0
  16. package/dist/src/jwks/remote.js.map +1 -0
  17. package/dist/src/jwt/decrypt.js +126 -0
  18. package/dist/src/jwt/decrypt.js.map +1 -0
  19. package/dist/src/jwt/decryption_profile.js +78 -0
  20. package/dist/src/jwt/decryption_profile.js.map +1 -0
  21. package/dist/src/jwt/encrypt.js +149 -0
  22. package/dist/src/jwt/encrypt.js.map +1 -0
  23. package/dist/src/jwt/profile.js +102 -0
  24. package/dist/src/jwt/profile.js.map +1 -0
  25. package/dist/src/jwt/sign.js +173 -0
  26. package/dist/src/jwt/sign.js.map +1 -0
  27. package/dist/src/jwt/verify.js +118 -0
  28. package/dist/src/jwt/verify.js.map +1 -0
  29. package/dist/src/key/encryption.js +132 -0
  30. package/dist/src/key/encryption.js.map +1 -0
  31. package/dist/src/key/export.js +46 -0
  32. package/dist/src/key/export.js.map +1 -0
  33. package/dist/src/key/generate.js +47 -0
  34. package/dist/src/key/generate.js.map +1 -0
  35. package/dist/src/key/import.js +136 -0
  36. package/dist/src/key/import.js.map +1 -0
  37. package/dist/src/legacy/rs256.js +11 -0
  38. package/dist/src/legacy/rs256.js.map +1 -0
  39. package/dist/src/legacy/rsa.js +37 -0
  40. package/dist/src/legacy/rsa.js.map +1 -0
  41. package/dist/src/lib/algorithms.js +60 -0
  42. package/dist/src/lib/algorithms.js.map +1 -0
  43. package/dist/src/lib/base64url.js +28 -0
  44. package/dist/src/lib/base64url.js.map +1 -0
  45. package/dist/src/lib/claims.js +111 -0
  46. package/dist/src/lib/claims.js.map +1 -0
  47. package/dist/src/lib/duration.js +24 -0
  48. package/dist/src/lib/duration.js.map +1 -0
  49. package/dist/src/lib/entropy.js +70 -0
  50. package/dist/src/lib/entropy.js.map +1 -0
  51. package/dist/src/lib/headers.js +75 -0
  52. package/dist/src/lib/headers.js.map +1 -0
  53. package/dist/src/lib/invariants.js +12 -0
  54. package/dist/src/lib/invariants.js.map +1 -0
  55. package/dist/src/lib/json.js +140 -0
  56. package/dist/src/lib/json.js.map +1 -0
  57. package/dist/src/lib/jwe_algorithms.js +81 -0
  58. package/dist/src/lib/jwe_algorithms.js.map +1 -0
  59. package/dist/src/lib/payload_hygiene.js +116 -0
  60. package/dist/src/lib/payload_hygiene.js.map +1 -0
  61. package/dist/src/lib/utf8.js +27 -0
  62. package/dist/src/lib/utf8.js.map +1 -0
  63. package/dist/src/revocation/memory.js +67 -0
  64. package/dist/src/revocation/memory.js.map +1 -0
  65. package/dist/src/revocation/store.js +25 -0
  66. package/dist/src/revocation/store.js.map +1 -0
  67. package/dist/src/types.js +51 -0
  68. package/dist/src/types.js.map +1 -0
  69. package/dist/src/util/errors.js +96 -0
  70. package/dist/src/util/errors.js.map +1 -0
  71. package/dist/src/util/unsafe-decode.js +45 -0
  72. package/dist/src/util/unsafe-decode.js.map +1 -0
  73. package/dist/types/index.d.ts +29 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/dist/types/src/cookbook/access-refresh.d.ts +77 -0
  76. package/dist/types/src/cookbook/access-refresh.d.ts.map +1 -0
  77. package/dist/types/src/http/bearer.d.ts +20 -0
  78. package/dist/types/src/http/bearer.d.ts.map +1 -0
  79. package/dist/types/src/http/cookies.d.ts +38 -0
  80. package/dist/types/src/http/cookies.d.ts.map +1 -0
  81. package/dist/types/src/jwks/local.d.ts +16 -0
  82. package/dist/types/src/jwks/local.d.ts.map +1 -0
  83. package/dist/types/src/jwks/remote.d.ts +23 -0
  84. package/dist/types/src/jwks/remote.d.ts.map +1 -0
  85. package/dist/types/src/jwt/decrypt.d.ts +23 -0
  86. package/dist/types/src/jwt/decrypt.d.ts.map +1 -0
  87. package/dist/types/src/jwt/decryption_profile.d.ts +32 -0
  88. package/dist/types/src/jwt/decryption_profile.d.ts.map +1 -0
  89. package/dist/types/src/jwt/encrypt.d.ts +34 -0
  90. package/dist/types/src/jwt/encrypt.d.ts.map +1 -0
  91. package/dist/types/src/jwt/profile.d.ts +37 -0
  92. package/dist/types/src/jwt/profile.d.ts.map +1 -0
  93. package/dist/types/src/jwt/sign.d.ts +59 -0
  94. package/dist/types/src/jwt/sign.d.ts.map +1 -0
  95. package/dist/types/src/jwt/verify.d.ts +41 -0
  96. package/dist/types/src/jwt/verify.d.ts.map +1 -0
  97. package/dist/types/src/key/encryption.d.ts +37 -0
  98. package/dist/types/src/key/encryption.d.ts.map +1 -0
  99. package/dist/types/src/key/export.d.ts +10 -0
  100. package/dist/types/src/key/export.d.ts.map +1 -0
  101. package/dist/types/src/key/generate.d.ts +27 -0
  102. package/dist/types/src/key/generate.d.ts.map +1 -0
  103. package/dist/types/src/key/import.d.ts +20 -0
  104. package/dist/types/src/key/import.d.ts.map +1 -0
  105. package/dist/types/src/legacy/rs256.d.ts +11 -0
  106. package/dist/types/src/legacy/rs256.d.ts.map +1 -0
  107. package/dist/types/src/legacy/rsa.d.ts +25 -0
  108. package/dist/types/src/legacy/rsa.d.ts.map +1 -0
  109. package/dist/types/src/lib/algorithms.d.ts +34 -0
  110. package/dist/types/src/lib/algorithms.d.ts.map +1 -0
  111. package/dist/types/src/lib/base64url.d.ts +8 -0
  112. package/dist/types/src/lib/base64url.d.ts.map +1 -0
  113. package/dist/types/src/lib/claims.d.ts +15 -0
  114. package/dist/types/src/lib/claims.d.ts.map +1 -0
  115. package/dist/types/src/lib/duration.d.ts +7 -0
  116. package/dist/types/src/lib/duration.d.ts.map +1 -0
  117. package/dist/types/src/lib/entropy.d.ts +21 -0
  118. package/dist/types/src/lib/entropy.d.ts.map +1 -0
  119. package/dist/types/src/lib/headers.d.ts +19 -0
  120. package/dist/types/src/lib/headers.d.ts.map +1 -0
  121. package/dist/types/src/lib/invariants.d.ts +6 -0
  122. package/dist/types/src/lib/invariants.d.ts.map +1 -0
  123. package/dist/types/src/lib/json.d.ts +24 -0
  124. package/dist/types/src/lib/json.d.ts.map +1 -0
  125. package/dist/types/src/lib/jwe_algorithms.d.ts +46 -0
  126. package/dist/types/src/lib/jwe_algorithms.d.ts.map +1 -0
  127. package/dist/types/src/lib/payload_hygiene.d.ts +20 -0
  128. package/dist/types/src/lib/payload_hygiene.d.ts.map +1 -0
  129. package/dist/types/src/lib/utf8.d.ts +7 -0
  130. package/dist/types/src/lib/utf8.d.ts.map +1 -0
  131. package/dist/types/src/revocation/memory.d.ts +22 -0
  132. package/dist/types/src/revocation/memory.d.ts.map +1 -0
  133. package/dist/types/src/revocation/store.d.ts +17 -0
  134. package/dist/types/src/revocation/store.d.ts.map +1 -0
  135. package/dist/types/src/types.d.ts +196 -0
  136. package/dist/types/src/types.d.ts.map +1 -0
  137. package/dist/types/src/util/errors.d.ts +89 -0
  138. package/dist/types/src/util/errors.d.ts.map +1 -0
  139. package/dist/types/src/util/unsafe-decode.d.ts +21 -0
  140. package/dist/types/src/util/unsafe-decode.d.ts.map +1 -0
  141. package/package.json +70 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.0] - 2026-07-12
6
+
7
+ ### Miscellaneous Tasks
8
+
9
+ - Corrected url post migration from old repo
10
+
11
+ <!-- generated by git-cliff -->
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Smiduweorc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1 @@
1
+ This project includes code adapted from the [jose](https://github.com/panva/jose) repository, created by Panva. We are grateful for their work!
package/README.md ADDED
@@ -0,0 +1,313 @@
1
+ # Lacewing
2
+ ![Logo](./assets/logo.png)
3
+
4
+ **Lacewing is an opinionated JWT library that makes common JWT security mistakes impossible by construction.**
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.
7
+
8
+ **Batteries loaded (RFC 8725).** Out of the box:
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
15
+ - **Encrypted JWTs (JWE)** with the same profile discipline
16
+ - **Payload hygiene scanning** at sign time (passwords, card numbers, PEM keys never leave in plaintext)
17
+ - **Typed errors** with machine-readable codes and no attacker-facing oracle
18
+
19
+ ## Requirements
20
+
21
+ - Node **>= 24**
22
+ - **ESM** only
23
+ - **TypeScript-first** (full types shipped; the branded `VerifiedJwt`/`UntrustedJwt` types are part of the security model)
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install lacewing
29
+ ```
30
+
31
+ ## Quick start
32
+
33
+ The complete lifecycle - generate a key, sign, define a profile once, verify everywhere:
34
+
35
+ ```ts
36
+ import { SignJWT, defineProfile, jwtVerify, generateKeyPair } from "lacewing";
37
+
38
+ const { publicKey, privateKey } = await generateKeyPair(); // EdDSA by default
39
+
40
+ const token = await new SignJWT("at+jwt")
41
+ .issuer("https://auth.example.com")
42
+ .audience("https://api.example.com")
43
+ .subject("user-42")
44
+ .expiresIn("10m")
45
+ .sign(privateKey);
46
+
47
+ const profile = defineProfile({
48
+ typ: "at+jwt",
49
+ issuer: "https://auth.example.com",
50
+ audience: "https://api.example.com",
51
+ algorithms: ["EdDSA"],
52
+ keys: publicKey,
53
+ maxTokenAge: "10m",
54
+ });
55
+
56
+ const { payload } = await jwtVerify(token, profile); // VerifiedJwt - every check passed
57
+ ```
58
+
59
+ A runnable version of everything in this README ships with the repo: `npm run demo`.
60
+
61
+ ## Concepts
62
+
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.
64
+
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.
66
+
67
+ ## Usage
68
+
69
+ ### Verify: a profile backed by a remote JWKS
70
+
71
+ This is what real deployments should look like, so it's the first example:
72
+
73
+ ```ts
74
+ import { defineProfile, jwtVerify, createRemoteJWKSet } from "lacewing";
75
+
76
+ const accessToken = defineProfile({
77
+ typ: "at+jwt",
78
+ issuer: "https://auth.example.com",
79
+ audience: "https://api.example.com",
80
+ algorithms: ["EdDSA"],
81
+ keys: createRemoteJWKSet("https://auth.example.com/jwks"), // cached, rate-limited, rotation-aware, HTTPS-only
82
+ maxTokenAge: "10m",
83
+ });
84
+
85
+ const { payload } = await jwtVerify(token, accessToken); // VerifiedJwt - every check passed
86
+ ```
87
+
88
+ ### Sign
89
+
90
+ `typ` is a constructor argument, and `.sign()` refuses to run without
91
+ `issuer`, `audience` and `expiresIn` (waivable only via grep-loud
92
+ `unsafeAllowMissing*` calls). Every token gets a unique `jti`, lifetimes are
93
+ capped (default 1h), and a hygiene scanner rejects payloads that contain
94
+ things like passwords, card numbers, PEM keys, or other JWTs - because **a
95
+ JWS payload is base64url-encoded plaintext, readable by anyone who holds the
96
+ token**. Never put secrets in it.
97
+
98
+ ```ts
99
+ import { SignJWT, generateKeyPair } from "lacewing";
100
+
101
+ const { publicKey, privateKey } = await generateKeyPair(); // EdDSA by default
102
+
103
+ const token = await new SignJWT("at+jwt")
104
+ .issuer("https://auth.example.com")
105
+ .audience("https://api.example.com")
106
+ .subject("user-42")
107
+ .claim("scope", "read")
108
+ .expiresIn("10m")
109
+ .sign(privateKey);
110
+ ```
111
+
112
+ A note on HMAC (`HS256/384/512`): it is supported, but every verifier of an
113
+ HMAC token holds the same secret and can therefore also **mint** them. For
114
+ 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.
117
+
118
+ ### Transport: the paved road
119
+
120
+ Do **not** put tokens in `localStorage` or `sessionStorage` - both are
121
+ readable by any script on the page, so one XSS means token theft. Use an
122
+ `HttpOnly` cookie (browsers) or an in-memory bearer token (services):
123
+
124
+ ```ts
125
+ import { setTokenCookie, readTokenCookie, parseBearer } from "lacewing";
126
+
127
+ setTokenCookie(response.headers, token); // always HttpOnly; Secure; SameSite - weaker is unrepresentable
128
+ const fromCookie = readTokenCookie(request);
129
+ const fromHeader = parseBearer(request); // strict RFC 6750; no query-string tokens, ever
130
+ ```
131
+
132
+ ### Access vs refresh (the cookbook)
133
+
134
+ The single most common token-confusion bug is letting a refresh token buy API
135
+ 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
137
+ `typ` (RFC 8725 §3.12), so presenting one where the other is expected fails,
138
+ even with identical keys, claims and audience:
139
+
140
+ ```ts
141
+ import { accessTokenProfile, refreshTokenProfile, newAccessToken } from "lacewing";
142
+
143
+ const api = accessTokenProfile({ // typ: "at+jwt", 10m default cap
144
+ issuer: "https://auth.example.com",
145
+ audience: "https://api.example.com", // the API
146
+ algorithms: ["EdDSA"],
147
+ keys: { jwksUri: "https://auth.example.com/jwks" },
148
+ });
149
+
150
+ const refresh = refreshTokenProfile({ // typ: "rt+jwt", 30d default cap
151
+ issuer: "https://auth.example.com",
152
+ audience: "https://auth.example.com/token", // the auth server, never the API
153
+ algorithms: ["EdDSA"],
154
+ keys: privateJwks,
155
+ revocation, // long-lived tokens must be revocable
156
+ });
157
+
158
+ const token = await newAccessToken()
159
+ .issuer("https://auth.example.com")
160
+ .audience("https://api.example.com")
161
+ .subject("user-42")
162
+ .expiresIn("10m")
163
+ .sign(privateKey);
164
+
165
+ await jwtVerify(token, refresh); // -> JWTClaimValidationFailed (wrong typ)
166
+ ```
167
+
168
+ ### Revocation is built-in, not homework
169
+
170
+ ```ts
171
+ import { MemoryRevocationStore } from "lacewing";
172
+
173
+ const revocation = new MemoryRevocationStore(); // or your Redis/DB adapter (RevocationStore)
174
+ const profile = defineProfile({ /* ...as above... */ revocation });
175
+
176
+ revocation.revoke(payload.jti, payload.exp); // e.g. on logout
177
+ await jwtVerify(token, profile); // -> JWTRevoked
178
+ ```
179
+
180
+ The store is consulted only *after* signature and claims pass, and store
181
+ errors fail closed.
182
+
183
+ ## Advanced
184
+
185
+ ### Revocation is not replay protection
186
+
187
+ 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
189
+ valid, non-revoked token that leaks can be replayed by anyone who holds it
190
+ until `exp`. That is the deliberate cost of stateless verification, and no
191
+ JWT library can remove it without becoming a session store.
192
+
193
+ Your levers, in order of cheapness:
194
+
195
+ - **Short lifetimes.** The default caps (10m access tokens) exist exactly to
196
+ shrink the replay window. Prefer shortening `exp` over building state.
197
+ - **Revocation on every logout/refresh**, so a stolen token dies with the
198
+ session it came from.
199
+ - **A jti-seen cache** when an endpoint must be strictly once-only (e.g. a
200
+ password-reset action token): every Lacewing token carries a unique `jti`,
201
+ so you can reject repeats with a `claimValidators` entry backed by a
202
+ store with the token's remaining TTL:
203
+
204
+ ```ts
205
+ const profile = defineProfile({
206
+ /* ...as above... */
207
+ claimValidators: {
208
+ jti: async (jti) => {
209
+ // setnx-style: returns false if the jti was already seen
210
+ if (!(await seenCache.addIfAbsent(String(jti), remainingTtl))) {
211
+ throw new Error("token replayed");
212
+ }
213
+ },
214
+ },
215
+ });
216
+ ```
217
+
218
+ This is intentionally not built in: a once-only cache is a distributed-state
219
+ decision (which store, which TTL, what happens when it's down) that your
220
+ deployment has to own, not something a library should silently half-do.
221
+ - **Proof-of-possession** (mTLS-bound or DPoP-bound tokens) when replay by a
222
+ network eavesdropper is in your threat model. That is beyond Lacewing's
223
+ bearer-token scope today.
224
+
225
+ ### Encrypting (JWE): when the payload really is a secret
226
+
227
+ 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
229
+ signing (mandatory `typ`, required `iss`/`aud`/`exp`, unique `jti`, capped
230
+ 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
232
+ whole point:
233
+
234
+ ```ts
235
+ import {
236
+ EncryptJWT, jwtDecrypt, defineDecryptionProfile, generateEncryptionKeyPair,
237
+ } from "lacewing";
238
+
239
+ const { publicKey, privateKey } = await generateEncryptionKeyPair("ECDH-ES+A256KW");
240
+
241
+ const token = await new EncryptJWT("at+jwt", { contentEncryption: "A256GCM" })
242
+ .issuer("https://auth.example.com")
243
+ .audience("https://api.example.com")
244
+ .subject("user-42")
245
+ .claim("ssn", "123-45-6789") // fine - it's encrypted, not just encoded
246
+ .expiresIn("10m")
247
+ .encrypt(publicKey);
248
+
249
+ const profile = defineDecryptionProfile({
250
+ typ: "at+jwt",
251
+ issuer: "https://auth.example.com",
252
+ audience: "https://api.example.com",
253
+ keyManagementAlgorithms: ["ECDH-ES+A256KW"], // the header never chooses
254
+ contentEncryptionAlgorithms: ["A256GCM"],
255
+ key: privateKey,
256
+ maxTokenAge: "10m",
257
+ });
258
+
259
+ const { payload } = await jwtDecrypt(token, profile); // DecryptedJwt - distinct from VerifiedJwt
260
+ ```
261
+
262
+ `none`, `RSA1_5`, `RSA-OAEP` (SHA-1) and the password-based `PBES2*` family are
263
+ absent by construction. A JWS handed to `jwtDecrypt` (or a JWE handed to
264
+ `jwtVerify`) is rejected - the two formats never cross over.
265
+
266
+ **Portability caveat - 192-bit AES:** the registry includes the `A192*`
267
+ algorithms (`A192KW`, `A192GCMKW`, `A192GCM`, `A192CBC-HS384`) for JOSE
268
+ completeness, and they work on Node ≥ 24 (Lacewing's floor). But WebCrypto
269
+ implementations in browsers and some edge runtimes do not implement 192-bit
270
+ 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.
272
+
273
+ ### Debugging
274
+
275
+ `unsafeDecode(token)` parses without verifying and returns an `UntrustedJwt`
276
+ that is type-incompatible with `VerifiedJwt` - useful for inspecting expired
277
+ tokens, useless (by design) for auth logic.
278
+
279
+ ### Legacy interop
280
+
281
+ The `RS*` (RSASSA-PKCS1-v1_5) family exists only behind an explicit import, for
282
+ verifying tokens from issuers you don't control and can't move off PKCS#1 v1.5:
283
+
284
+ ```ts
285
+ import { enableLegacyRS256 } from "lacewing/legacy/rs256";
286
+ enableLegacyRS256(); // grep for this in code review
287
+
288
+ // Or, for an issuer that rotates across hash sizes:
289
+ import { enableLegacyRSA } from "lacewing/legacy/rsa"; // RS256 + RS384 + RS512
290
+ ```
291
+
292
+ Prefer `PS256`/`EdDSA` everywhere you control both sides.
293
+
294
+ ## Why Lacewing exists
295
+
296
+ Lacewing is not trying to compete with general-purpose JWT libraries. It started as the hardened setup I kept rebuilding for my own projects, packaged so I'd stop rebuilding it.
297
+
298
+ The other reason is irritation. Generic JWT libraries are fine, but they leave enough rope for misinformed developers (and lazy AI slop) to ship something unsafe. It pisses me off when people implement something genuinely safe wrongly and then blame the technology instead of accepting operator error. These are the mistakes Lacewing makes unrepresentable or loudly explicit:
299
+
300
+ - Decoding instead of verifying
301
+ - Accepting `"none"` for the algorithm
302
+ - No explicit allowlist on the server that enforces expected algorithms
303
+ - Putting sensitive data in the token payload
304
+ - No way to revoke stateless JWTs
305
+ - Storing tokens in `localStorage` or `sessionStorage`
306
+ - Not configuring `iss` or `aud`
307
+ - Over-caching a JWKS when an endpoint goes down
308
+
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.
310
+
311
+ ## Attribution
312
+
313
+ Lacewing depends on and includes code adapted from the [jose](https://github.com/panva/jose) library, created by Filip Skokan (panva). We are grateful for their work!
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Lacewing - an opinionated JWT library where RFC 8725 (JWT Best Current
3
+ * Practices) is the default behavior, not an optional configuration.
4
+ *
5
+ * This barrel is the public API. Internals under `src/lib/` are policy
6
+ * machinery and stay private; legacy interop algorithms live behind the
7
+ * explicit `lacewing/legacy/*` imports.
8
+ */
9
+ // Verification profiles + the one verification path
10
+ export { defineProfile } from "./src/jwt/profile.js";
11
+ export { jwtVerify } from "./src/jwt/verify.js";
12
+ // Signing
13
+ export { SignJWT } from "./src/jwt/sign.js";
14
+ // Encryption (JWE) - same profile discipline as sign/verify
15
+ export { EncryptJWT } from "./src/jwt/encrypt.js";
16
+ export { jwtDecrypt } from "./src/jwt/decrypt.js";
17
+ export { defineDecryptionProfile, } from "./src/jwt/decryption_profile.js";
18
+ export { generateEncryptionKeyPair, generateEncryptionSecret, generateDirectKey, importEncryptionKey, } from "./src/key/encryption.js";
19
+ // Cookbook: the access-vs-refresh split, shipped rather than left as homework
20
+ export { accessTokenProfile, refreshTokenProfile, newAccessToken, newRefreshToken, ACCESS_TOKEN_TYP, REFRESH_TOKEN_TYP, } from "./src/cookbook/access-refresh.js";
21
+ // Keys
22
+ export { importKey } from "./src/key/import.js";
23
+ export { generateKeyPair, generateSecret, } from "./src/key/generate.js";
24
+ export { exportKeyJWK, exportKeyPEM } from "./src/key/export.js";
25
+ // JWKS key sources
26
+ export { createLocalJWKSet } from "./src/jwks/local.js";
27
+ export { createRemoteJWKSet } from "./src/jwks/remote.js";
28
+ // Revocation
29
+ export { buildRevocationContext } from "./src/revocation/store.js";
30
+ export { MemoryRevocationStore } from "./src/revocation/memory.js";
31
+ // HTTP transport helpers (the paved road: no localStorage, ever)
32
+ export { buildTokenCookie, setTokenCookie, clearTokenCookie, readTokenCookie, } from "./src/http/cookies.js";
33
+ export { parseBearer } from "./src/http/bearer.js";
34
+ // Debugging escape hatch - branded untrusted, incompatible with VerifiedJwt
35
+ export { unsafeDecode } from "./src/util/unsafe-decode.js";
36
+ // Typed errors
37
+ export { JWTError, JWTInvalid, JWTExpired, JWTClaimValidationFailed, MissingClaim, AlgorithmNotAllowed, KeyImportFailed, KeyExportFailed, KeyTypeMismatch, EntropyCheckFailed, JWKSFetchFailed, JWKSNoMatchingKey, JWTRevoked, RevocationCheckFailed, PayloadHygieneViolation, MaxLifetimeExceeded, BearerParseFailed, } from "./src/util/errors.js";
38
+ // Public types
39
+ export { toSeconds, } from "./src/types.js";
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Cookbook: access tokens vs refresh tokens (LW-kind.1).
3
+ *
4
+ * The single most common token-confusion bug is letting a refresh token buy
5
+ * access to an API (or letting a stolen access token mint new sessions). RFC
6
+ * 8725 §3.12 says the validation rules for different kinds of JWT must be
7
+ * mutually exclusive; Lacewing makes that concrete by shipping the two
8
+ * profiles rather than leaving them as an exercise.
9
+ *
10
+ * The separation is enforced on three axes at once:
11
+ *
12
+ * | | access | refresh |
13
+ * |---|---|---|
14
+ * | `typ` | `at+jwt` | `rt+jwt` |
15
+ * | audience | the API | the auth server's token endpoint |
16
+ * | lifetime | minutes (default 10m) | days (default 30d) |
17
+ * | key source | usually a public JWKS | usually a private, server-only key |
18
+ * | revocation | optional | **strongly recommended** |
19
+ *
20
+ * `typ` alone is load-bearing: even with identical keys, claims and audience,
21
+ * each profile refuses the other's tokens. The rest is defense in depth.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { accessTokenProfile, refreshTokenProfile, newAccessToken } from "lacewing";
26
+ *
27
+ * const api = accessTokenProfile({
28
+ * issuer: "https://auth.example.com",
29
+ * audience: "https://api.example.com",
30
+ * algorithms: ["EdDSA"],
31
+ * keys: { jwksUri: "https://auth.example.com/jwks" },
32
+ * });
33
+ *
34
+ * const token = await newAccessToken()
35
+ * .issuer("https://auth.example.com")
36
+ * .audience("https://api.example.com")
37
+ * .subject("user-42")
38
+ * .expiresIn("10m")
39
+ * .sign(privateKey);
40
+ * ```
41
+ */
42
+ import { defineProfile } from "../jwt/profile.js";
43
+ import { SignJWT } from "../jwt/sign.js";
44
+ /** The `typ` of an OAuth 2.0 JWT access token (RFC 9068). */
45
+ export const ACCESS_TOKEN_TYP = "at+jwt";
46
+ /** The `typ` Lacewing uses for refresh tokens - anything but the access `typ`. */
47
+ export const REFRESH_TOKEN_TYP = "rt+jwt";
48
+ const DEFAULT_ACCESS_MAX_AGE = "10m";
49
+ const DEFAULT_REFRESH_MAX_AGE = "30d";
50
+ /**
51
+ * An access-token profile: short-lived, audience-scoped to your API, and
52
+ * pinned to `typ: "at+jwt"`. Verifies tokens presented by clients.
53
+ */
54
+ export function accessTokenProfile(options) {
55
+ return defineProfile({
56
+ ...options,
57
+ maxTokenAge: options.maxTokenAge ?? DEFAULT_ACCESS_MAX_AGE,
58
+ typ: ACCESS_TOKEN_TYP,
59
+ });
60
+ }
61
+ /**
62
+ * A refresh-token profile: long-lived, audience-scoped to the *auth server*
63
+ * (never the API), and pinned to `typ: "rt+jwt"`. Pass a `revocation` store -
64
+ * a long-lived token you cannot revoke is a long-lived incident.
65
+ */
66
+ export function refreshTokenProfile(options) {
67
+ return defineProfile({
68
+ ...options,
69
+ maxTokenAge: options.maxTokenAge ?? DEFAULT_REFRESH_MAX_AGE,
70
+ typ: REFRESH_TOKEN_TYP,
71
+ });
72
+ }
73
+ /** A {@link SignJWT} builder pre-set to the access-token `typ` and a 1h cap. */
74
+ export function newAccessToken() {
75
+ return new SignJWT(ACCESS_TOKEN_TYP, { maxLifetime: "1h" });
76
+ }
77
+ /**
78
+ * A {@link SignJWT} builder pre-set to the refresh-token `typ`. The cap is
79
+ * raised to 90d because that is the point of a refresh token - but the
80
+ * lifetime you actually pass to `.expiresIn()` should be as short as your UX
81
+ * tolerates, and the token should be revocable.
82
+ */
83
+ export function newRefreshToken() {
84
+ return new SignJWT(REFRESH_TOKEN_TYP, { maxLifetime: "90d" });
85
+ }
86
+ //# sourceMappingURL=access-refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-refresh.js","sourceRoot":"","sources":["../../../src/cookbook/access-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,aAAa,EAAuB,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAGzC,6DAA6D;AAC7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAC;AACzC,kFAAkF;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAE1C,MAAM,sBAAsB,GAAG,KAAK,CAAC;AACrC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAWtC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA4B;IAC9D,OAAO,aAAa,CAAC;QACpB,GAAG,OAAO;QACV,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,sBAAsB;QAC1D,GAAG,EAAE,gBAAgB;KACrB,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA4B;IAC/D,OAAO,aAAa,CAAC;QACpB,GAAG,OAAO;QACV,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,uBAAuB;QAC3D,GAAG,EAAE,iBAAiB;KACtB,CAAC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc;IAC7B,OAAO,IAAI,OAAO,CAAC,gBAAgB,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC9B,OAAO,IAAI,OAAO,CAAC,iBAAiB,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Strict `Authorization: Bearer` parsing (RFC 6750 §2.1, LW-http.2).
3
+ *
4
+ * Deliberately unsupported: tokens in query strings (they leak through
5
+ * logs and the Referer header), multiple tokens, alternate casings and
6
+ * whitespace tricks. One header, one scheme, one token.
7
+ */
8
+ import { BearerParseFailed } from "../util/errors.js";
9
+ // RFC 6750 credentials: exact-case scheme, a single SP, one token68.
10
+ const BEARER = /^Bearer ([A-Za-z0-9\-._~+/]+=*)$/;
11
+ const MAX_HEADER_LENGTH = 16384;
12
+ /**
13
+ * Extract the bearer token from an `Authorization` header. Accepts a
14
+ * `Headers` object, anything with a `.headers` (e.g. `Request`), or the
15
+ * raw header value. Throws {@link BearerParseFailed} on anything that
16
+ * isn't exactly one well-formed `Bearer <token>`.
17
+ *
18
+ * Note: when multiple `Authorization` headers were sent, `Headers`
19
+ * joins them with `", "` - which this parser rejects, by design.
20
+ */
21
+ 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
+ }
32
+ if (typeof value !== "string" || value.length === 0) {
33
+ throw new BearerParseFailed("Missing Authorization header");
34
+ }
35
+ if (value.length > MAX_HEADER_LENGTH) {
36
+ throw new BearerParseFailed("Authorization header exceeds the maximum length");
37
+ }
38
+ const match = BEARER.exec(value);
39
+ if (match === null) {
40
+ throw new BearerParseFailed("Authorization header failed strict Bearer parsing (expected exactly \"Bearer <token>\")");
41
+ }
42
+ return match[1];
43
+ }
44
+ //# sourceMappingURL=bearer.js.map
@@ -0,0 +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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Cookie transport helpers (LW-http.1).
3
+ *
4
+ * The paved road for browser token storage: `HttpOnly; Secure; SameSite`
5
+ * are always emitted and cannot be configured away - weaker cookies are
6
+ * unrepresentable. `localStorage`/`sessionStorage` are script-readable
7
+ * and therefore one XSS away from token theft; don't put tokens there.
8
+ *
9
+ * Framework-agnostic: works on WHATWG `Headers` (and anything exposing
10
+ * them, e.g. `Request`/`Response`).
11
+ */
12
+ // RFC 6265 cookie-name token; value charset covers compact JWTs.
13
+ const COOKIE_NAME = /^[!#$%&'*+\-.^_`|~A-Za-z0-9]+$/;
14
+ const COOKIE_VALUE = /^[A-Za-z0-9._~+/=-]*$/;
15
+ const DEFAULT_COOKIE_NAME = "__Host-token";
16
+ /** Build a hardened `Set-Cookie` value. Prefer {@link setTokenCookie}. */
17
+ export function buildTokenCookie(token, options = {}) {
18
+ if (typeof token !== "string" || !COOKIE_VALUE.test(token) || token.length === 0) {
19
+ throw new TypeError("Token contains characters that cannot be stored in a cookie");
20
+ }
21
+ const name = options.name ?? DEFAULT_COOKIE_NAME;
22
+ if (!COOKIE_NAME.test(name)) {
23
+ throw new TypeError("Invalid cookie name");
24
+ }
25
+ const sameSite = options.sameSite ?? "Lax";
26
+ if (sameSite !== "Lax" && sameSite !== "Strict") {
27
+ throw new TypeError("SameSite must be \"Lax\" or \"Strict\" - \"None\" is not supported");
28
+ }
29
+ const path = options.path ?? "/";
30
+ if (path.includes(";") || !path.startsWith("/")) {
31
+ throw new TypeError("Invalid cookie path");
32
+ }
33
+ if (name.startsWith("__Host-")) {
34
+ if (options.domain !== undefined) {
35
+ throw new TypeError("__Host- cookies must not set a Domain");
36
+ }
37
+ if (path !== "/") {
38
+ throw new TypeError("__Host- cookies require Path=/");
39
+ }
40
+ }
41
+ const parts = [`${name}=${token}`, `Path=${path}`];
42
+ if (options.domain !== undefined) {
43
+ if (!/^[A-Za-z0-9.-]+$/.test(options.domain)) {
44
+ throw new TypeError("Invalid cookie domain");
45
+ }
46
+ parts.push(`Domain=${options.domain}`);
47
+ }
48
+ if (options.maxAgeSeconds !== undefined) {
49
+ if (!Number.isInteger(options.maxAgeSeconds) || options.maxAgeSeconds < 0) {
50
+ throw new TypeError("maxAgeSeconds must be a non-negative integer");
51
+ }
52
+ parts.push(`Max-Age=${options.maxAgeSeconds}`);
53
+ }
54
+ // Non-negotiable (LW-http.1). There is no option to remove these.
55
+ parts.push("HttpOnly", "Secure", `SameSite=${sameSite}`);
56
+ return parts.join("; ");
57
+ }
58
+ /** Append a hardened token cookie to a response's headers. */
59
+ export function setTokenCookie(headers, token, options = {}) {
60
+ headers.append("Set-Cookie", buildTokenCookie(token, options));
61
+ }
62
+ /** Expire a previously set token cookie. */
63
+ export function clearTokenCookie(headers, options = {}) {
64
+ headers.append("Set-Cookie", buildTokenCookie("x", { ...options, maxAgeSeconds: 0 }).replace(/^([^=]+)=x;/, "$1=;"));
65
+ }
66
+ /**
67
+ * Read a token cookie from a request. Accepts a `Headers` object,
68
+ * anything with a `.headers` (e.g. `Request`), or the raw `Cookie`
69
+ * header string. Returns `undefined` when absent or malformed.
70
+ */
71
+ 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
+ }
82
+ if (typeof cookieHeader !== "string" || cookieHeader.length > 16384) {
83
+ return undefined;
84
+ }
85
+ for (const pair of cookieHeader.split(";")) {
86
+ const eq = pair.indexOf("=");
87
+ if (eq === -1)
88
+ continue;
89
+ if (pair.slice(0, eq).trim() !== name)
90
+ continue;
91
+ const value = pair.slice(eq + 1).trim();
92
+ return COOKIE_VALUE.test(value) && value.length > 0 ? value : undefined;
93
+ }
94
+ return undefined;
95
+ }
96
+ //# sourceMappingURL=cookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../../src/http/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,iEAAiE;AACjE,MAAM,WAAW,GAAG,gCAAgC,CAAC;AACrD,MAAM,YAAY,GAAG,uBAAuB,CAAC;AAE7C,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAe3C,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,UAA8B,EAAE;IAC/E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,SAAS,CAAC,6DAA6D,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,mBAAmB,CAAC;IACjD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC3C,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjD,MAAM,IAAI,SAAS,CAAC,oEAAoE,CAAC,CAAC;IAC3F,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC;IACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAClC,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YAClB,MAAM,IAAI,SAAS,CAAC,gCAAgC,CAAC,CAAC;QACvD,CAAC;IACF,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;IACnD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAClC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,SAAS,CAAC,uBAAuB,CAAC,CAAC;QAC9C,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,OAAO,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YAC3E,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;QACrE,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,kEAAkE;IAClE,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,YAAY,QAAQ,EAAE,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,cAAc,CAC7B,OAAgB,EAChB,KAAa,EACb,UAA8B,EAAE;IAEhC,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,gBAAgB,CAC/B,OAAgB,EAChB,UAAgE,EAAE;IAElE,OAAO,CAAC,MAAM,CACb,YAAY,EACZ,gBAAgB,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CACtF,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC9B,MAAkE,EAClE,OAAe,mBAAmB;IAElC,IAAI,YAAuC,CAAC;IAC5C,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChC,YAAY,GAAG,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;QACtC,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,YAAY,OAAO,EAAE,CAAC;QAC/F,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;QACrE,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,SAAS;QACxB,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI;YAAE,SAAS;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACzE,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC"}