@molecule/api-jwt 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +399 -0
  2. package/package.json +5 -4
package/README.md ADDED
@@ -0,0 +1,399 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:48:23.941Z
7
+ -->
8
+
9
+ # @molecule/api-jwt
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ JWT interface for molecule.dev.
16
+
17
+ Provides an abstract JWT interface that can be backed by any JWT library.
18
+ Use `setProvider` to provide a concrete implementation
19
+ such as `@molecule/api-jwt-jsonwebtoken`.
20
+
21
+ ## Quick Start
22
+
23
+ ```ts
24
+ import { sign, verify, decode } from '@molecule/api-jwt'
25
+
26
+ const token = sign({ userId }, { expiresIn: '15m' }) // server-side; expiry set
27
+
28
+ try {
29
+ const claims = verify(token) as JwtPayload // signature CHECKED — safe to trust
30
+ grantAccess(claims.userId)
31
+ } catch {
32
+ res.status(401).json({ error: 'Invalid or expired token.' })
33
+ }
34
+
35
+ decode(token) // NOT verified — never use its output for an auth decision
36
+ ```
37
+
38
+ ## Type
39
+
40
+ `core`
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install @molecule/api-jwt @molecule/api-bond
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### Interfaces
51
+
52
+ #### `JwtDecodeOptions`
53
+
54
+ Options for decoding a JWT (without verification).
55
+
56
+ ```typescript
57
+ interface JwtDecodeOptions {
58
+ complete?: boolean
59
+ json?: boolean
60
+ }
61
+ ```
62
+
63
+ #### `JwtPayload`
64
+
65
+ Decoded JWT payload.
66
+
67
+ ```typescript
68
+ interface JwtPayload {
69
+ [key: string]: unknown
70
+ iss?: string
71
+ sub?: string
72
+ aud?: string | string[]
73
+ exp?: number
74
+ nbf?: number
75
+ iat?: number
76
+ jti?: string
77
+ }
78
+ ```
79
+
80
+ #### `JwtProvider`
81
+
82
+ JWT provider interface that all JWT bond packages must implement.
83
+
84
+ Provides `sign`, `verify`, and `decode` operations. Key management
85
+ and algorithm configuration are handled by the core package.
86
+
87
+ ```typescript
88
+ interface JwtProvider {
89
+ sign(payload: JSONObject, options?: JwtSignOptions, privateKey?: string | Buffer): string
90
+
91
+ verify(
92
+ token: string,
93
+ options?: JwtVerifyOptions,
94
+ publicKey?: string | Buffer,
95
+ ): string | JwtPayload
96
+
97
+ decode(token: string, options?: JwtDecodeOptions): string | JwtPayload | null
98
+ }
99
+ ```
100
+
101
+ #### `JwtSignOptions`
102
+
103
+ Options for signing a JWT.
104
+
105
+ ```typescript
106
+ interface JwtSignOptions {
107
+ algorithm?: JwtAlgorithm
108
+ expiresIn?: number | string
109
+ notBefore?: number | string
110
+ audience?: string | string[]
111
+ issuer?: string
112
+ subject?: string
113
+ jwtid?: string
114
+ keyid?: string
115
+ header?: Record<string, unknown>
116
+ }
117
+ ```
118
+
119
+ #### `JwtVerifyOptions`
120
+
121
+ Options for verifying a JWT.
122
+
123
+ Note: security-hardened bonds (e.g. `@molecule/api-jwt-jsonwebtoken`)
124
+ REFUSE to honor `ignoreExpiration`/`ignoreNotBefore` — an expired token
125
+ always fails verification regardless of these flags. To tolerate clock
126
+ skew or slow flows, use `clockTolerance` (seconds) instead.
127
+
128
+ ```typescript
129
+ interface JwtVerifyOptions {
130
+ algorithms?: JwtAlgorithm[]
131
+ audience?: string | string[]
132
+ issuer?: string | string[]
133
+ subject?: string
134
+ clockTolerance?: number
135
+ maxAge?: string | number
136
+ complete?: boolean
137
+ ignoreExpiration?: boolean
138
+ ignoreNotBefore?: boolean
139
+ }
140
+ ```
141
+
142
+ ### Types
143
+
144
+ #### `JSONObject`
145
+
146
+ A plain JSON object whose values are `JSONValue`s. Used as the payload
147
+ type for JWT signing operations.
148
+
149
+ ```typescript
150
+ type JSONObject = { [key: string]: JSONValue }
151
+ ```
152
+
153
+ #### `JSONValue`
154
+
155
+ Recursive JSON value type representing any valid JSON primitive, array, or object.
156
+
157
+ ```typescript
158
+ type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }
159
+ ```
160
+
161
+ #### `JwtAlgorithm`
162
+
163
+ Supported JWT signing algorithms.
164
+
165
+ ```typescript
166
+ type JwtAlgorithm =
167
+ | 'RS256'
168
+ | 'RS384'
169
+ | 'RS512'
170
+ | 'HS256'
171
+ | 'HS384'
172
+ | 'HS512'
173
+ | 'ES256'
174
+ | 'ES384'
175
+ | 'ES512'
176
+ | 'PS256'
177
+ | 'PS384'
178
+ | 'PS512'
179
+ ```
180
+
181
+ ### Functions
182
+
183
+ #### `decode(token, options)`
184
+
185
+ Decodes a JWT string without verifying its signature. Useful for inspecting
186
+ token contents when verification is handled elsewhere.
187
+
188
+ ```typescript
189
+ function decode(token: string, options?: JwtDecodeOptions): string | JwtPayload | null
190
+ ```
191
+
192
+ - `token` — The JWT string to decode.
193
+ - `options` — Decode options such as `complete` for full header+payload output.
194
+
195
+ **Returns:** The decoded payload, or `null` if the token cannot be decoded.
196
+
197
+ #### `generateKeyPairSync()`
198
+
199
+ Generates an RSA-2048 key pair in PEM format for JWT signing and verification.
200
+
201
+ ```typescript
202
+ function generateKeyPairSync(): { publicKey: string; privateKey: string }
203
+ ```
204
+
205
+ **Returns:** An object containing `publicKey` and `privateKey` as PEM strings.
206
+
207
+ #### `getProvider()`
208
+
209
+ Retrieves the bonded JWT provider, throwing if none is configured.
210
+
211
+ ```typescript
212
+ function getProvider(): JwtProvider
213
+ ```
214
+
215
+ **Returns:** The bonded JWT provider.
216
+
217
+ #### `hasProvider()`
218
+
219
+ Checks whether a JWT provider is currently bonded.
220
+
221
+ ```typescript
222
+ function hasProvider(): boolean
223
+ ```
224
+
225
+ **Returns:** `true` if a JWT provider is bonded.
226
+
227
+ #### `setProvider(provider)`
228
+
229
+ Registers a JWT provider as the active singleton. Called by bond
230
+ packages during application startup.
231
+
232
+ ```typescript
233
+ function setProvider(provider: JwtProvider): void
234
+ ```
235
+
236
+ - `provider` — The JWT provider implementation to bond.
237
+
238
+ #### `sign(object, options, privateKey)`
239
+
240
+ Signs a payload into a JWT string using the bonded provider. Uses the
241
+ configured algorithm, expiry, and private key as defaults.
242
+
243
+ ```typescript
244
+ function sign(
245
+ object: JSONObject,
246
+ { algorithm = JWT_ALGORITHM, expiresIn = JWT_EXPIRES_TIME, ...rest }?: JwtSignOptions,
247
+ privateKey?: string | Buffer<ArrayBufferLike>,
248
+ ): string
249
+ ```
250
+
251
+ - `object` — The JSON payload to sign.
252
+ - `options` — Signing options; `algorithm` defaults to `JWT_ALGORITHM`, `expiresIn` defaults to `JWT_EXPIRES_TIME`.
253
+ - `options.algorithm` — The signing algorithm (e.g. `RS256`, `HS256`).
254
+ - `options.expiresIn` — Token lifetime in seconds.
255
+ - `privateKey` — The private key for signing; defaults to `JWT_PRIVATE_KEY`.
256
+
257
+ **Returns:** The signed JWT string.
258
+
259
+ #### `verify(token, options, publicKey)`
260
+
261
+ Verifies a JWT string and returns the decoded payload. Uses the configured
262
+ algorithm and public key as defaults.
263
+
264
+ ```typescript
265
+ function verify(
266
+ token: string,
267
+ { algorithms = [JWT_ALGORITHM], ...rest }?: JwtVerifyOptions,
268
+ publicKey?: string | Buffer<ArrayBufferLike>,
269
+ ): string | JwtPayload
270
+ ```
271
+
272
+ - `token` — The JWT string to verify.
273
+ - `options` — Verification options; `algorithms` defaults to `[JWT_ALGORITHM]`.
274
+ - `options.algorithms` — The allowed signing algorithms for verification.
275
+ - `publicKey` — The public key for verification; defaults to `JWT_PUBLIC_KEY`.
276
+
277
+ **Returns:** The decoded payload string or object.
278
+
279
+ #### `writeKeys(outputPath)`
280
+
281
+ Writes a freshly generated RSA key pair to disk as PEM files. Creates
282
+ the output directory if it does not exist.
283
+
284
+ ```typescript
285
+ function writeKeys(outputPath?: string): void
286
+ ```
287
+
288
+ - `outputPath` — Directory to write the PEM files into; defaults to `{JWT_KEYS_DIR}/{NODE_ENV}/`.
289
+
290
+ ### Constants
291
+
292
+ #### `JWT_ALGORITHM`
293
+
294
+ The signing algorithm used for JWT operations. Read from the `JWT_ALGORITHM`
295
+ environment variable, defaulting to `RS256`. An unrecognized value logs an
296
+ actionable warning at module load and falls back to `RS256` rather than
297
+ failing every `sign()`/`verify()` call later with an unexplained
298
+ "invalid algorithm" error.
299
+
300
+ ```typescript
301
+ const JWT_ALGORITHM: JwtAlgorithm
302
+ ```
303
+
304
+ #### `JWT_EXPIRES_TIME`
305
+
306
+ Token lifetime in seconds. Read from the `JWT_EXPIRES_TIME` environment
307
+ variable, defaulting to 604800 (1 week).
308
+
309
+ ```typescript
310
+ const JWT_EXPIRES_TIME: number
311
+ ```
312
+
313
+ #### `JWT_PRIVATE_KEY`
314
+
315
+ The RSA private key for signing JWTs. Read from the `JWT_PRIVATE_KEY`
316
+ environment variable, or loaded from the PEM file on disk.
317
+
318
+ Throws at startup if neither source provides a key — running with an
319
+ empty secret would allow anyone to forge valid JWTs.
320
+
321
+ ```typescript
322
+ const JWT_PRIVATE_KEY: string | Buffer<ArrayBufferLike>
323
+ ```
324
+
325
+ #### `JWT_PUBLIC_KEY`
326
+
327
+ The RSA public key for verifying JWTs. Read from the `JWT_PUBLIC_KEY`
328
+ environment variable; when only `JWT_PRIVATE_KEY` is set, the matching
329
+ public key is DERIVED from it (a disk fallback could not match an
330
+ env-provided private key and would make every signed token fail
331
+ verification); otherwise loaded from the PEM file on disk.
332
+
333
+ Throws at startup if no source provides a key.
334
+
335
+ ```typescript
336
+ const JWT_PUBLIC_KEY: string | Buffer<ArrayBufferLike>
337
+ ```
338
+
339
+ #### `JWT_REFRESH_TIME`
340
+
341
+ Refresh window in seconds — tokens are refreshed if they will expire within
342
+ this period. Read from the `JWT_REFRESH_TIME` environment variable,
343
+ defaulting to 3600 (1 hour).
344
+
345
+ ```typescript
346
+ const JWT_REFRESH_TIME: number
347
+ ```
348
+
349
+ ## Available Providers
350
+
351
+ | Provider | Package |
352
+ | ---------------- | -------------------------------- |
353
+ | JWT signing keys | `@molecule/api-jwt-jsonwebtoken` |
354
+
355
+ ## Injection Notes
356
+
357
+ ### Requirements
358
+
359
+ Peer dependencies:
360
+
361
+ - `@molecule/api-bond` ^1.0.1
362
+
363
+ ### Runtime Dependencies
364
+
365
+ - `@molecule/api-bond`
366
+
367
+ **{@link verify} is the ONLY way to trust a token — {@link decode} does NOT check the
368
+ signature.** Never make an auth decision from `decode()`: an attacker can forge any
369
+ payload that `decode()` will happily return. Use `verify()` (it throws — catch it) for
370
+ anything security-relevant; `decode()` is only for reading a token you do NOT trust.
371
+
372
+ - The signing key (private key / secret) is SERVER-SIDE only — never ship it to the
373
+ browser. Only an asymmetric PUBLIC key may be published.
374
+ - A JWT payload is READABLE by anyone (base64, not encrypted) — never put a password,
375
+ secret, or sensitive PII in it.
376
+ - Always set + honor expiry ({@link JWT_EXPIRES_TIME}); a non-expiring token can't be
377
+ revoked.
378
+ - In a molecule app auth is ALREADY wired: the global `verifyMiddleware` verifies the JWT
379
+ and populates `res.locals.session`, so a handler calls `getUserId(res)` — do NOT call
380
+ `verify()`/`sign()` by hand for the session (see the `auth` skill). Use these directly
381
+ only for a CUSTOM token, e.g. a signed email/reset link.
382
+ - **Re-signing decoded claims (refresh flows): strip `exp`/`iat` first.** `sign()`
383
+ always sets `expiresIn` (default {@link JWT_EXPIRES_TIME}), and the underlying library
384
+ throws (`Bad "options.expiresIn" option the payload already has an "exp" property`)
385
+ when the payload still carries the old `exp` — so `const { exp, iat, ...claims } =
386
+ verify(oldToken) as JwtPayload; sign(claims)` is the correct refresh shape.
387
+ - Set `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` together (or neither). If only the private
388
+ key is set, the matching public key is DERIVED from it automatically; setting only the
389
+ public key is for verify-only deployments.
390
+ - When neither key env var is set, a key pair is auto-generated on disk at
391
+ `{JWT_KEYS_DIR}/{NODE_ENV}/` — default `JWT_KEYS_DIR`: `process.cwd() + '/.keys'`, a
392
+ stable app-level directory (NOT inside `node_modules`, so `npm ci`/reinstall never
393
+ wipes it). Set `JWT_KEYS_DIR` to relocate it (e.g. a persistent volume in production).
394
+ A pre-existing pair at the legacy `node_modules`-relative location is migrated forward
395
+ automatically (with a logged warning) instead of being silently regenerated.
396
+ - `JWT_ALGORITHM` (default `RS256`) is validated at module load against the
397
+ {@link JwtAlgorithm} union; an unrecognized value (e.g. a typo like `rs256`) logs an
398
+ actionable warning and falls back to `RS256` instead of failing every `sign()`/`verify()`
399
+ call later with an opaque "invalid algorithm" error.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-jwt",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "JWT interface and key management for molecule.dev",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,8 @@
17
17
  }
18
18
  },
19
19
  "files": [
20
- "dist"
20
+ "dist",
21
+ "README.md"
21
22
  ],
22
23
  "keywords": [
23
24
  "molecule",
@@ -27,10 +28,10 @@
27
28
  ],
28
29
  "license": "Apache-2.0",
29
30
  "peerDependencies": {
30
- "@molecule/api-bond": "^1.0.0"
31
+ "@molecule/api-bond": "^1.0.1"
31
32
  },
32
33
  "devDependencies": {
33
- "@molecule/api-bond": "1.0.0",
34
+ "@molecule/api-bond": "1.0.1",
34
35
  "@types/node": "26.1.2",
35
36
  "typescript": "6.0.3",
36
37
  "vitest": "4.1.10"