@naturalcycles/nodejs-lib 15.116.2 → 15.117.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.
- package/dist/jwt/jwt.service2.d.ts +65 -11
- package/dist/jwt/jwt.service2.js +71 -25
- package/package.json +1 -1
- package/src/jwt/jwt.service2.ts +96 -31
|
@@ -27,8 +27,23 @@ export interface JWTService2Cfg<T extends AnyObject = AnyObject> {
|
|
|
27
27
|
* Recommended: ES256
|
|
28
28
|
* Keys (private/public) should be generated using proper settings
|
|
29
29
|
* that fit the used Algorithm.
|
|
30
|
+
*
|
|
31
|
+
* Used for Sign, and (unless `verifyAlgorithms` is set) it's also
|
|
32
|
+
* the only algorithm accepted on Verify.
|
|
30
33
|
*/
|
|
31
34
|
algorithm: JWTAlgorithm;
|
|
35
|
+
/**
|
|
36
|
+
* JWS algorithms accepted on Verify. Defaults to `[algorithm]`.
|
|
37
|
+
*
|
|
38
|
+
* Only needed when one verifier must accept keys of different types
|
|
39
|
+
* (e.g a per-kid key set mixing EC and RSA keys). Keep the list as narrow as possible.
|
|
40
|
+
*
|
|
41
|
+
* The token's `alg` header chooses among these, but only within what the
|
|
42
|
+
* verification key can serve: a token/key algorithm mismatch fails with JWTInvalidError.
|
|
43
|
+
* (All JWTAlgorithms are asymmetric, so the classic algorithm-confusion attacks
|
|
44
|
+
* don't apply regardless.)
|
|
45
|
+
*/
|
|
46
|
+
verifyAlgorithms?: JWTAlgorithm[];
|
|
32
47
|
/**
|
|
33
48
|
* If provided - payloads are validated against it on every Sign/Verify/Decode.
|
|
34
49
|
* Can be overridden per-call via `opt.schema`.
|
|
@@ -134,7 +149,8 @@ export interface JWTDecoded<T extends AnyObject> {
|
|
|
134
149
|
/**
|
|
135
150
|
* Wraps the `jose` library, exposing an implementation-agnostic API:
|
|
136
151
|
* no jose types, options or errors leak out of this service.
|
|
137
|
-
* All errors are normalized into JWTError
|
|
152
|
+
* All errors are normalized into JWTError subclasses
|
|
153
|
+
* (JWTExpiredError / JWTNotYetValidError / JWTInvalidError), matchable with `instanceof`.
|
|
138
154
|
*
|
|
139
155
|
* Successor of JWTService (jsonwebtoken-based). Tokens are wire-compatible
|
|
140
156
|
* in both directions, so the two services can be swapped freely for the same key pair.
|
|
@@ -168,7 +184,7 @@ export declare class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
168
184
|
*/
|
|
169
185
|
private validate;
|
|
170
186
|
/**
|
|
171
|
-
* jose errors are normalized into JWTError (extended with cfg.errorData).
|
|
187
|
+
* jose errors are normalized into JWTError subclasses (extended with cfg.errorData).
|
|
172
188
|
* Non-jose errors (e.g TypeError from passing a key that doesn't fit the algorithm)
|
|
173
189
|
* indicate a programming error and are passed through as-is.
|
|
174
190
|
*/
|
|
@@ -179,7 +195,7 @@ export declare class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
179
195
|
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
180
196
|
* (no schema validation, no errorData extension).
|
|
181
197
|
*
|
|
182
|
-
* Throws
|
|
198
|
+
* Throws JWTInvalidError if the token cannot be decoded.
|
|
183
199
|
*/
|
|
184
200
|
export declare function jwtDecode<T extends AnyObject>(token: JWTString): JWTDecoded<T>;
|
|
185
201
|
export type JWTErrorCode = 'JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID';
|
|
@@ -187,17 +203,55 @@ export interface JWTErrorData extends ErrorData {
|
|
|
187
203
|
code: JWTErrorCode;
|
|
188
204
|
}
|
|
189
205
|
/**
|
|
190
|
-
* Thrown by JWTService2 on any Verify/Decode failure
|
|
206
|
+
* Thrown by JWTService2 on any Verify/Decode failure, always as one of its subclasses:
|
|
207
|
+
* - JWTExpiredError - `exp` claim check failed
|
|
208
|
+
* - JWTNotYetValidError - `nbf` claim check failed
|
|
209
|
+
* - JWTInvalidError - anything else (malformed token, wrong signature, other claim mismatches)
|
|
191
210
|
*
|
|
192
|
-
* `
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
211
|
+
* Match errors with `instanceof`, e.g `err instanceof JWTExpiredError`.
|
|
212
|
+
* `data.code` carries the same distinction ('JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID'),
|
|
213
|
+
* for when the error crosses a serialization boundary (e.g ErrorObject over HTTP)
|
|
214
|
+
* where `instanceof` no longer works.
|
|
196
215
|
*
|
|
197
|
-
* The
|
|
216
|
+
* The underlying jose error is deliberately NOT preserved in `cause`: everything useful
|
|
217
|
+
* from it is already carried (message is copied, the failed claim is the subclass,
|
|
218
|
+
* `payload` is exposed where trustworthy), and jose nests the raw JWT payload into its own
|
|
219
|
+
* `cause`, which would otherwise survive ErrorObject serialization and leak into logs.
|
|
198
220
|
*/
|
|
199
221
|
export declare class JWTError extends AppError<JWTErrorData> {
|
|
200
|
-
constructor(message: string, data: JWTErrorData
|
|
201
|
-
|
|
222
|
+
constructor(message: string, data: JWTErrorData);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The token is well-formed and its signature is valid, but the `exp` claim check failed.
|
|
226
|
+
*
|
|
227
|
+
* `payload` carries the decoded token payload: signature is verified before claims are
|
|
228
|
+
* checked, so the payload is trustworthy - it's just expired.
|
|
229
|
+
* (Note: it's the raw JWT payload, incl. standard claims; cfg.schema is not applied to it.)
|
|
230
|
+
*/
|
|
231
|
+
export declare class JWTExpiredError extends JWTError {
|
|
232
|
+
readonly payload: AnyObject;
|
|
233
|
+
constructor(message: string, data?: ErrorData, opt?: {
|
|
234
|
+
payload?: AnyObject;
|
|
202
235
|
});
|
|
203
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* The token is well-formed and its signature is valid, but the `nbf` claim check failed.
|
|
239
|
+
*
|
|
240
|
+
* `payload` carries the decoded token payload: signature is verified before claims are
|
|
241
|
+
* checked, so the payload is trustworthy - it's just not valid yet.
|
|
242
|
+
* (Note: it's the raw JWT payload, incl. standard claims; cfg.schema is not applied to it.)
|
|
243
|
+
*/
|
|
244
|
+
export declare class JWTNotYetValidError extends JWTError {
|
|
245
|
+
readonly payload: AnyObject;
|
|
246
|
+
constructor(message: string, data?: ErrorData, opt?: {
|
|
247
|
+
payload?: AnyObject;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The token could not be trusted: malformed token, wrong signature,
|
|
252
|
+
* or a claim mismatch other than exp/nbf (e.g issuer/audience).
|
|
253
|
+
* No payload is exposed - nothing from such a token should be used.
|
|
254
|
+
*/
|
|
255
|
+
export declare class JWTInvalidError extends JWTError {
|
|
256
|
+
constructor(message: string, data?: ErrorData);
|
|
257
|
+
}
|
package/dist/jwt/jwt.service2.js
CHANGED
|
@@ -5,7 +5,8 @@ import { decodeJwt, decodeProtectedHeader, errors, jwtVerify, SignJWT } from 'jo
|
|
|
5
5
|
/**
|
|
6
6
|
* Wraps the `jose` library, exposing an implementation-agnostic API:
|
|
7
7
|
* no jose types, options or errors leak out of this service.
|
|
8
|
-
* All errors are normalized into JWTError
|
|
8
|
+
* All errors are normalized into JWTError subclasses
|
|
9
|
+
* (JWTExpiredError / JWTNotYetValidError / JWTInvalidError), matchable with `instanceof`.
|
|
9
10
|
*
|
|
10
11
|
* Successor of JWTService (jsonwebtoken-based). Tokens are wire-compatible
|
|
11
12
|
* in both directions, so the two services can be swapped freely for the same key pair.
|
|
@@ -75,7 +76,7 @@ export class JWTService2 {
|
|
|
75
76
|
let data;
|
|
76
77
|
try {
|
|
77
78
|
const { payload } = await jwtVerify(token, key, {
|
|
78
|
-
algorithms: [this.cfg.algorithm],
|
|
79
|
+
algorithms: this.cfg.verifyAlgorithms || [this.cfg.algorithm],
|
|
79
80
|
...joseOpt,
|
|
80
81
|
currentDate: now === undefined ? undefined : new Date(now * 1000),
|
|
81
82
|
});
|
|
@@ -120,28 +121,27 @@ export class JWTService2 {
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
/**
|
|
123
|
-
* jose errors are normalized into JWTError (extended with cfg.errorData).
|
|
124
|
+
* jose errors are normalized into JWTError subclasses (extended with cfg.errorData).
|
|
124
125
|
* Non-jose errors (e.g TypeError from passing a key that doesn't fit the algorithm)
|
|
125
126
|
* indicate a programming error and are passed through as-is.
|
|
126
127
|
*/
|
|
127
128
|
normalizeError(err) {
|
|
128
|
-
|
|
129
|
+
const { errorData } = this.cfg;
|
|
129
130
|
if (err instanceof errors.JWTExpired) {
|
|
130
|
-
|
|
131
|
+
return new JWTExpiredError(err.message, errorData, { payload: err.payload });
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
if (err instanceof errors.JWTClaimValidationFailed && err.claim === 'nbf') {
|
|
134
|
+
return new JWTNotYetValidError(err.message, errorData, { payload: err.payload });
|
|
134
135
|
}
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
if (err instanceof errors.JOSEError) {
|
|
137
|
+
return new JWTInvalidError(err.message, errorData);
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
if (this.cfg.verifyAlgorithms && err instanceof TypeError) {
|
|
140
|
+
// With multiple verifyAlgorithms, a token/key algorithm mismatch is reachable
|
|
141
|
+
// by untrusted input, and jose reports it as TypeError - treat it as an invalid token
|
|
142
|
+
return new JWTInvalidError(err.message, errorData);
|
|
140
143
|
}
|
|
141
|
-
return
|
|
142
|
-
...this.cfg.errorData,
|
|
143
|
-
code,
|
|
144
|
-
}, { cause: err });
|
|
144
|
+
return err;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
/**
|
|
@@ -149,7 +149,7 @@ export class JWTService2 {
|
|
|
149
149
|
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
150
150
|
* (no schema validation, no errorData extension).
|
|
151
151
|
*
|
|
152
|
-
* Throws
|
|
152
|
+
* Throws JWTInvalidError if the token cannot be decoded.
|
|
153
153
|
*/
|
|
154
154
|
export function jwtDecode(token) {
|
|
155
155
|
let header;
|
|
@@ -159,7 +159,8 @@ export function jwtDecode(token) {
|
|
|
159
159
|
payload = decodeJwt(token);
|
|
160
160
|
}
|
|
161
161
|
catch (err) {
|
|
162
|
-
|
|
162
|
+
// The underlying message is folded in, as it carries the only specific detail
|
|
163
|
+
throw new JWTInvalidError(`invalid token, unable to decode: ${err.message}`);
|
|
163
164
|
}
|
|
164
165
|
return {
|
|
165
166
|
header,
|
|
@@ -168,17 +169,62 @@ export function jwtDecode(token) {
|
|
|
168
169
|
};
|
|
169
170
|
}
|
|
170
171
|
/**
|
|
171
|
-
* Thrown by JWTService2 on any Verify/Decode failure
|
|
172
|
+
* Thrown by JWTService2 on any Verify/Decode failure, always as one of its subclasses:
|
|
173
|
+
* - JWTExpiredError - `exp` claim check failed
|
|
174
|
+
* - JWTNotYetValidError - `nbf` claim check failed
|
|
175
|
+
* - JWTInvalidError - anything else (malformed token, wrong signature, other claim mismatches)
|
|
172
176
|
*
|
|
173
|
-
* `
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
+
* Match errors with `instanceof`, e.g `err instanceof JWTExpiredError`.
|
|
178
|
+
* `data.code` carries the same distinction ('JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID'),
|
|
179
|
+
* for when the error crosses a serialization boundary (e.g ErrorObject over HTTP)
|
|
180
|
+
* where `instanceof` no longer works.
|
|
177
181
|
*
|
|
178
|
-
* The
|
|
182
|
+
* The underlying jose error is deliberately NOT preserved in `cause`: everything useful
|
|
183
|
+
* from it is already carried (message is copied, the failed claim is the subclass,
|
|
184
|
+
* `payload` is exposed where trustworthy), and jose nests the raw JWT payload into its own
|
|
185
|
+
* `cause`, which would otherwise survive ErrorObject serialization and leak into logs.
|
|
179
186
|
*/
|
|
180
187
|
export class JWTError extends AppError {
|
|
181
|
-
constructor(message, data
|
|
182
|
-
|
|
188
|
+
constructor(message, data) {
|
|
189
|
+
// `new.target.name` makes subclasses report their own name
|
|
190
|
+
super(message, data, { name: new.target.name });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The token is well-formed and its signature is valid, but the `exp` claim check failed.
|
|
195
|
+
*
|
|
196
|
+
* `payload` carries the decoded token payload: signature is verified before claims are
|
|
197
|
+
* checked, so the payload is trustworthy - it's just expired.
|
|
198
|
+
* (Note: it's the raw JWT payload, incl. standard claims; cfg.schema is not applied to it.)
|
|
199
|
+
*/
|
|
200
|
+
export class JWTExpiredError extends JWTError {
|
|
201
|
+
payload;
|
|
202
|
+
constructor(message, data = {}, opt = {}) {
|
|
203
|
+
super(message, { ...data, code: 'JWT_EXPIRED' });
|
|
204
|
+
this.payload = opt.payload || {};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The token is well-formed and its signature is valid, but the `nbf` claim check failed.
|
|
209
|
+
*
|
|
210
|
+
* `payload` carries the decoded token payload: signature is verified before claims are
|
|
211
|
+
* checked, so the payload is trustworthy - it's just not valid yet.
|
|
212
|
+
* (Note: it's the raw JWT payload, incl. standard claims; cfg.schema is not applied to it.)
|
|
213
|
+
*/
|
|
214
|
+
export class JWTNotYetValidError extends JWTError {
|
|
215
|
+
payload;
|
|
216
|
+
constructor(message, data = {}, opt = {}) {
|
|
217
|
+
super(message, { ...data, code: 'JWT_NOT_YET_VALID' });
|
|
218
|
+
this.payload = opt.payload || {};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The token could not be trusted: malformed token, wrong signature,
|
|
223
|
+
* or a claim mismatch other than exp/nbf (e.g issuer/audience).
|
|
224
|
+
* No payload is exposed - nothing from such a token should be used.
|
|
225
|
+
*/
|
|
226
|
+
export class JWTInvalidError extends JWTError {
|
|
227
|
+
constructor(message, data = {}) {
|
|
228
|
+
super(message, { ...data, code: 'JWT_INVALID' });
|
|
183
229
|
}
|
|
184
230
|
}
|
package/package.json
CHANGED
package/src/jwt/jwt.service2.ts
CHANGED
|
@@ -49,9 +49,25 @@ export interface JWTService2Cfg<T extends AnyObject = AnyObject> {
|
|
|
49
49
|
* Recommended: ES256
|
|
50
50
|
* Keys (private/public) should be generated using proper settings
|
|
51
51
|
* that fit the used Algorithm.
|
|
52
|
+
*
|
|
53
|
+
* Used for Sign, and (unless `verifyAlgorithms` is set) it's also
|
|
54
|
+
* the only algorithm accepted on Verify.
|
|
52
55
|
*/
|
|
53
56
|
algorithm: JWTAlgorithm
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
* JWS algorithms accepted on Verify. Defaults to `[algorithm]`.
|
|
60
|
+
*
|
|
61
|
+
* Only needed when one verifier must accept keys of different types
|
|
62
|
+
* (e.g a per-kid key set mixing EC and RSA keys). Keep the list as narrow as possible.
|
|
63
|
+
*
|
|
64
|
+
* The token's `alg` header chooses among these, but only within what the
|
|
65
|
+
* verification key can serve: a token/key algorithm mismatch fails with JWTInvalidError.
|
|
66
|
+
* (All JWTAlgorithms are asymmetric, so the classic algorithm-confusion attacks
|
|
67
|
+
* don't apply regardless.)
|
|
68
|
+
*/
|
|
69
|
+
verifyAlgorithms?: JWTAlgorithm[]
|
|
70
|
+
|
|
55
71
|
/**
|
|
56
72
|
* If provided - payloads are validated against it on every Sign/Verify/Decode.
|
|
57
73
|
* Can be overridden per-call via `opt.schema`.
|
|
@@ -166,7 +182,8 @@ export interface JWTDecoded<T extends AnyObject> {
|
|
|
166
182
|
/**
|
|
167
183
|
* Wraps the `jose` library, exposing an implementation-agnostic API:
|
|
168
184
|
* no jose types, options or errors leak out of this service.
|
|
169
|
-
* All errors are normalized into JWTError
|
|
185
|
+
* All errors are normalized into JWTError subclasses
|
|
186
|
+
* (JWTExpiredError / JWTNotYetValidError / JWTInvalidError), matchable with `instanceof`.
|
|
170
187
|
*
|
|
171
188
|
* Successor of JWTService (jsonwebtoken-based). Tokens are wire-compatible
|
|
172
189
|
* in both directions, so the two services can be swapped freely for the same key pair.
|
|
@@ -243,7 +260,7 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
243
260
|
|
|
244
261
|
try {
|
|
245
262
|
const { payload } = await jwtVerify(token, key, {
|
|
246
|
-
algorithms: [this.cfg.algorithm],
|
|
263
|
+
algorithms: this.cfg.verifyAlgorithms || [this.cfg.algorithm],
|
|
247
264
|
...joseOpt,
|
|
248
265
|
currentDate: now === undefined ? undefined : new Date(now * 1000),
|
|
249
266
|
})
|
|
@@ -292,31 +309,28 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
292
309
|
}
|
|
293
310
|
|
|
294
311
|
/**
|
|
295
|
-
* jose errors are normalized into JWTError (extended with cfg.errorData).
|
|
312
|
+
* jose errors are normalized into JWTError subclasses (extended with cfg.errorData).
|
|
296
313
|
* Non-jose errors (e.g TypeError from passing a key that doesn't fit the algorithm)
|
|
297
314
|
* indicate a programming error and are passed through as-is.
|
|
298
315
|
*/
|
|
299
316
|
private normalizeError(err: unknown): Error {
|
|
300
|
-
|
|
317
|
+
const { errorData } = this.cfg
|
|
301
318
|
|
|
302
319
|
if (err instanceof errors.JWTExpired) {
|
|
303
|
-
|
|
304
|
-
} else if (err instanceof errors.JWTClaimValidationFailed && err.claim === 'nbf') {
|
|
305
|
-
code = 'JWT_NOT_YET_VALID'
|
|
306
|
-
} else if (err instanceof errors.JOSEError) {
|
|
307
|
-
code = 'JWT_INVALID'
|
|
308
|
-
} else {
|
|
309
|
-
return err as Error
|
|
320
|
+
return new JWTExpiredError(err.message, errorData, { payload: err.payload })
|
|
310
321
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
if (err instanceof errors.JWTClaimValidationFailed && err.claim === 'nbf') {
|
|
323
|
+
return new JWTNotYetValidError(err.message, errorData, { payload: err.payload })
|
|
324
|
+
}
|
|
325
|
+
if (err instanceof errors.JOSEError) {
|
|
326
|
+
return new JWTInvalidError(err.message, errorData)
|
|
327
|
+
}
|
|
328
|
+
if (this.cfg.verifyAlgorithms && err instanceof TypeError) {
|
|
329
|
+
// With multiple verifyAlgorithms, a token/key algorithm mismatch is reachable
|
|
330
|
+
// by untrusted input, and jose reports it as TypeError - treat it as an invalid token
|
|
331
|
+
return new JWTInvalidError(err.message, errorData)
|
|
332
|
+
}
|
|
333
|
+
return err as Error
|
|
320
334
|
}
|
|
321
335
|
}
|
|
322
336
|
|
|
@@ -325,7 +339,7 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
325
339
|
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
326
340
|
* (no schema validation, no errorData extension).
|
|
327
341
|
*
|
|
328
|
-
* Throws
|
|
342
|
+
* Throws JWTInvalidError if the token cannot be decoded.
|
|
329
343
|
*/
|
|
330
344
|
export function jwtDecode<T extends AnyObject>(token: JWTString): JWTDecoded<T> {
|
|
331
345
|
let header: JWTHeader
|
|
@@ -333,9 +347,10 @@ export function jwtDecode<T extends AnyObject>(token: JWTString): JWTDecoded<T>
|
|
|
333
347
|
|
|
334
348
|
try {
|
|
335
349
|
header = decodeProtectedHeader(token) as JWTHeader
|
|
336
|
-
payload = decodeJwt(token)
|
|
350
|
+
payload = decodeJwt(token)
|
|
337
351
|
} catch (err) {
|
|
338
|
-
|
|
352
|
+
// The underlying message is folded in, as it carries the only specific detail
|
|
353
|
+
throw new JWTInvalidError(`invalid token, unable to decode: ${(err as Error).message}`)
|
|
339
354
|
}
|
|
340
355
|
|
|
341
356
|
return {
|
|
@@ -352,17 +367,67 @@ export interface JWTErrorData extends ErrorData {
|
|
|
352
367
|
}
|
|
353
368
|
|
|
354
369
|
/**
|
|
355
|
-
* Thrown by JWTService2 on any Verify/Decode failure
|
|
370
|
+
* Thrown by JWTService2 on any Verify/Decode failure, always as one of its subclasses:
|
|
371
|
+
* - JWTExpiredError - `exp` claim check failed
|
|
372
|
+
* - JWTNotYetValidError - `nbf` claim check failed
|
|
373
|
+
* - JWTInvalidError - anything else (malformed token, wrong signature, other claim mismatches)
|
|
356
374
|
*
|
|
357
|
-
* `
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
375
|
+
* Match errors with `instanceof`, e.g `err instanceof JWTExpiredError`.
|
|
376
|
+
* `data.code` carries the same distinction ('JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID'),
|
|
377
|
+
* for when the error crosses a serialization boundary (e.g ErrorObject over HTTP)
|
|
378
|
+
* where `instanceof` no longer works.
|
|
361
379
|
*
|
|
362
|
-
* The
|
|
380
|
+
* The underlying jose error is deliberately NOT preserved in `cause`: everything useful
|
|
381
|
+
* from it is already carried (message is copied, the failed claim is the subclass,
|
|
382
|
+
* `payload` is exposed where trustworthy), and jose nests the raw JWT payload into its own
|
|
383
|
+
* `cause`, which would otherwise survive ErrorObject serialization and leak into logs.
|
|
363
384
|
*/
|
|
364
385
|
export class JWTError extends AppError<JWTErrorData> {
|
|
365
|
-
constructor(message: string, data: JWTErrorData
|
|
366
|
-
|
|
386
|
+
constructor(message: string, data: JWTErrorData) {
|
|
387
|
+
// `new.target.name` makes subclasses report their own name
|
|
388
|
+
super(message, data, { name: new.target.name })
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* The token is well-formed and its signature is valid, but the `exp` claim check failed.
|
|
394
|
+
*
|
|
395
|
+
* `payload` carries the decoded token payload: signature is verified before claims are
|
|
396
|
+
* checked, so the payload is trustworthy - it's just expired.
|
|
397
|
+
* (Note: it's the raw JWT payload, incl. standard claims; cfg.schema is not applied to it.)
|
|
398
|
+
*/
|
|
399
|
+
export class JWTExpiredError extends JWTError {
|
|
400
|
+
readonly payload: AnyObject
|
|
401
|
+
|
|
402
|
+
constructor(message: string, data: ErrorData = {}, opt: { payload?: AnyObject } = {}) {
|
|
403
|
+
super(message, { ...data, code: 'JWT_EXPIRED' })
|
|
404
|
+
this.payload = opt.payload || {}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* The token is well-formed and its signature is valid, but the `nbf` claim check failed.
|
|
410
|
+
*
|
|
411
|
+
* `payload` carries the decoded token payload: signature is verified before claims are
|
|
412
|
+
* checked, so the payload is trustworthy - it's just not valid yet.
|
|
413
|
+
* (Note: it's the raw JWT payload, incl. standard claims; cfg.schema is not applied to it.)
|
|
414
|
+
*/
|
|
415
|
+
export class JWTNotYetValidError extends JWTError {
|
|
416
|
+
readonly payload: AnyObject
|
|
417
|
+
|
|
418
|
+
constructor(message: string, data: ErrorData = {}, opt: { payload?: AnyObject } = {}) {
|
|
419
|
+
super(message, { ...data, code: 'JWT_NOT_YET_VALID' })
|
|
420
|
+
this.payload = opt.payload || {}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* The token could not be trusted: malformed token, wrong signature,
|
|
426
|
+
* or a claim mismatch other than exp/nbf (e.g issuer/audience).
|
|
427
|
+
* No payload is exposed - nothing from such a token should be used.
|
|
428
|
+
*/
|
|
429
|
+
export class JWTInvalidError extends JWTError {
|
|
430
|
+
constructor(message: string, data: ErrorData = {}) {
|
|
431
|
+
super(message, { ...data, code: 'JWT_INVALID' })
|
|
367
432
|
}
|
|
368
433
|
}
|