@naturalcycles/nodejs-lib 15.116.1 → 15.116.3
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 +27 -0
- package/dist/jwt/jwt.service2.js +44 -17
- package/package.json +1 -1
- package/src/jwt/jwt.service2.ts +65 -21
|
@@ -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 JWT_INVALID.
|
|
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`.
|
|
@@ -78,6 +93,10 @@ export interface JWTSignOptions<T extends AnyObject = AnyObject> {
|
|
|
78
93
|
* By default `iat` is NOT set (same as legacy JWTService with its `noTimestamp: true` default).
|
|
79
94
|
*/
|
|
80
95
|
issuedAt?: UnixTimestamp;
|
|
96
|
+
/**
|
|
97
|
+
* Sets the `kid` (key id) header, required by some APIs (e.g Apple App Store Connect).
|
|
98
|
+
*/
|
|
99
|
+
kid?: string;
|
|
81
100
|
/**
|
|
82
101
|
* Overrides cfg.schema for this call.
|
|
83
102
|
*/
|
|
@@ -170,6 +189,14 @@ export declare class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
170
189
|
*/
|
|
171
190
|
private normalizeError;
|
|
172
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Decodes a JWT without verifying its signature (no key needed) - never trust the result.
|
|
194
|
+
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
195
|
+
* (no schema validation, no errorData extension).
|
|
196
|
+
*
|
|
197
|
+
* Throws JWTError with code JWT_INVALID if the token cannot be decoded.
|
|
198
|
+
*/
|
|
199
|
+
export declare function jwtDecode<T extends AnyObject>(token: JWTString): JWTDecoded<T>;
|
|
173
200
|
export type JWTErrorCode = 'JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID';
|
|
174
201
|
export interface JWTErrorData extends ErrorData {
|
|
175
202
|
code: JWTErrorCode;
|
package/dist/jwt/jwt.service2.js
CHANGED
|
@@ -38,12 +38,17 @@ export class JWTService2 {
|
|
|
38
38
|
}
|
|
39
39
|
async sign(payload, opt) {
|
|
40
40
|
_assert(this.privateKey, 'JWTService2: privateKey is required to be able to sign, but not provided', this.cfg.errorData);
|
|
41
|
-
const { expiresAt, notBefore, issuer, audience, subject, jwtid, issuedAt, schema } = {
|
|
41
|
+
const { expiresAt, notBefore, issuer, audience, subject, jwtid, issuedAt, kid, schema } = {
|
|
42
42
|
...this.cfg.signOptions,
|
|
43
43
|
...opt,
|
|
44
44
|
};
|
|
45
45
|
(schema || this.cfg.schema)?.validate(payload);
|
|
46
|
-
|
|
46
|
+
// `kid: undefined` is dropped by JSON serialization, keeping the header unchanged when not set
|
|
47
|
+
const jwt = new SignJWT(payload).setProtectedHeader({
|
|
48
|
+
alg: this.cfg.algorithm,
|
|
49
|
+
typ: 'JWT',
|
|
50
|
+
kid,
|
|
51
|
+
});
|
|
47
52
|
if (expiresAt !== null)
|
|
48
53
|
jwt.setExpirationTime(expiresAt);
|
|
49
54
|
if (notBefore !== undefined)
|
|
@@ -70,7 +75,7 @@ export class JWTService2 {
|
|
|
70
75
|
let data;
|
|
71
76
|
try {
|
|
72
77
|
const { payload } = await jwtVerify(token, key, {
|
|
73
|
-
algorithms: [this.cfg.algorithm],
|
|
78
|
+
algorithms: this.cfg.verifyAlgorithms || [this.cfg.algorithm],
|
|
74
79
|
...joseOpt,
|
|
75
80
|
currentDate: now === undefined ? undefined : new Date(now * 1000),
|
|
76
81
|
});
|
|
@@ -83,24 +88,18 @@ export class JWTService2 {
|
|
|
83
88
|
return data;
|
|
84
89
|
}
|
|
85
90
|
decode(token, opt = {}) {
|
|
86
|
-
let
|
|
87
|
-
let payload;
|
|
91
|
+
let decoded;
|
|
88
92
|
try {
|
|
89
|
-
|
|
90
|
-
payload = decodeJwt(token);
|
|
93
|
+
decoded = jwtDecode(token);
|
|
91
94
|
}
|
|
92
95
|
catch (err) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
if (this.cfg.errorData) {
|
|
97
|
+
_errorDataAppend(err, this.cfg.errorData);
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
97
100
|
}
|
|
98
|
-
this.validate(payload, opt.schema);
|
|
99
|
-
return
|
|
100
|
-
header,
|
|
101
|
-
payload,
|
|
102
|
-
signature: token.split('.')[2],
|
|
103
|
-
};
|
|
101
|
+
this.validate(decoded.payload, opt.schema);
|
|
102
|
+
return decoded;
|
|
104
103
|
}
|
|
105
104
|
/**
|
|
106
105
|
* Schema-validation errors on Verify/Decode are extended with cfg.errorData:
|
|
@@ -136,6 +135,11 @@ export class JWTService2 {
|
|
|
136
135
|
else if (err instanceof errors.JOSEError) {
|
|
137
136
|
code = 'JWT_INVALID';
|
|
138
137
|
}
|
|
138
|
+
else if (this.cfg.verifyAlgorithms && err instanceof TypeError) {
|
|
139
|
+
// With multiple verifyAlgorithms, a token/key algorithm mismatch is reachable
|
|
140
|
+
// by untrusted input, and jose reports it as TypeError - treat it as an invalid token
|
|
141
|
+
code = 'JWT_INVALID';
|
|
142
|
+
}
|
|
139
143
|
else {
|
|
140
144
|
return err;
|
|
141
145
|
}
|
|
@@ -145,6 +149,29 @@ export class JWTService2 {
|
|
|
145
149
|
}, { cause: err });
|
|
146
150
|
}
|
|
147
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Decodes a JWT without verifying its signature (no key needed) - never trust the result.
|
|
154
|
+
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
155
|
+
* (no schema validation, no errorData extension).
|
|
156
|
+
*
|
|
157
|
+
* Throws JWTError with code JWT_INVALID if the token cannot be decoded.
|
|
158
|
+
*/
|
|
159
|
+
export function jwtDecode(token) {
|
|
160
|
+
let header;
|
|
161
|
+
let payload;
|
|
162
|
+
try {
|
|
163
|
+
header = decodeProtectedHeader(token);
|
|
164
|
+
payload = decodeJwt(token);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
throw new JWTError('invalid token, unable to decode', { code: 'JWT_INVALID' }, { cause: err });
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
header,
|
|
171
|
+
payload,
|
|
172
|
+
signature: token.split('.')[2],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
148
175
|
/**
|
|
149
176
|
* Thrown by JWTService2 on any Verify/Decode failure.
|
|
150
177
|
*
|
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 JWT_INVALID.
|
|
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`.
|
|
@@ -105,6 +121,10 @@ export interface JWTSignOptions<T extends AnyObject = AnyObject> {
|
|
|
105
121
|
* By default `iat` is NOT set (same as legacy JWTService with its `noTimestamp: true` default).
|
|
106
122
|
*/
|
|
107
123
|
issuedAt?: UnixTimestamp
|
|
124
|
+
/**
|
|
125
|
+
* Sets the `kid` (key id) header, required by some APIs (e.g Apple App Store Connect).
|
|
126
|
+
*/
|
|
127
|
+
kid?: string
|
|
108
128
|
/**
|
|
109
129
|
* Overrides cfg.schema for this call.
|
|
110
130
|
*/
|
|
@@ -198,14 +218,19 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
198
218
|
this.cfg.errorData,
|
|
199
219
|
)
|
|
200
220
|
|
|
201
|
-
const { expiresAt, notBefore, issuer, audience, subject, jwtid, issuedAt, schema } = {
|
|
221
|
+
const { expiresAt, notBefore, issuer, audience, subject, jwtid, issuedAt, kid, schema } = {
|
|
202
222
|
...this.cfg.signOptions,
|
|
203
223
|
...opt,
|
|
204
224
|
}
|
|
205
225
|
|
|
206
226
|
;(schema || this.cfg.schema)?.validate(payload)
|
|
207
227
|
|
|
208
|
-
|
|
228
|
+
// `kid: undefined` is dropped by JSON serialization, keeping the header unchanged when not set
|
|
229
|
+
const jwt = new SignJWT(payload).setProtectedHeader({
|
|
230
|
+
alg: this.cfg.algorithm,
|
|
231
|
+
typ: 'JWT',
|
|
232
|
+
kid,
|
|
233
|
+
})
|
|
209
234
|
if (expiresAt !== null) jwt.setExpirationTime(expiresAt)
|
|
210
235
|
if (notBefore !== undefined) jwt.setNotBefore(notBefore)
|
|
211
236
|
if (issuer) jwt.setIssuer(issuer)
|
|
@@ -234,7 +259,7 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
234
259
|
|
|
235
260
|
try {
|
|
236
261
|
const { payload } = await jwtVerify(token, key, {
|
|
237
|
-
algorithms: [this.cfg.algorithm],
|
|
262
|
+
algorithms: this.cfg.verifyAlgorithms || [this.cfg.algorithm],
|
|
238
263
|
...joseOpt,
|
|
239
264
|
currentDate: now === undefined ? undefined : new Date(now * 1000),
|
|
240
265
|
})
|
|
@@ -249,30 +274,20 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
249
274
|
}
|
|
250
275
|
|
|
251
276
|
decode<TT extends T = T>(token: JWTString, opt: JWTDecodeOptions<TT> = {}): JWTDecoded<TT> {
|
|
252
|
-
let
|
|
253
|
-
let payload: TT
|
|
277
|
+
let decoded: JWTDecoded<TT>
|
|
254
278
|
|
|
255
279
|
try {
|
|
256
|
-
|
|
257
|
-
payload = decodeJwt(token) as TT
|
|
280
|
+
decoded = jwtDecode<TT>(token)
|
|
258
281
|
} catch (err) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
code: 'JWT_INVALID',
|
|
264
|
-
},
|
|
265
|
-
{ cause: err },
|
|
266
|
-
)
|
|
282
|
+
if (this.cfg.errorData) {
|
|
283
|
+
_errorDataAppend(err, this.cfg.errorData)
|
|
284
|
+
}
|
|
285
|
+
throw err
|
|
267
286
|
}
|
|
268
287
|
|
|
269
|
-
this.validate(payload, opt.schema)
|
|
288
|
+
this.validate(decoded.payload, opt.schema)
|
|
270
289
|
|
|
271
|
-
return
|
|
272
|
-
header,
|
|
273
|
-
payload,
|
|
274
|
-
signature: token.split('.')[2]!,
|
|
275
|
-
}
|
|
290
|
+
return decoded
|
|
276
291
|
}
|
|
277
292
|
|
|
278
293
|
/**
|
|
@@ -306,6 +321,10 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
306
321
|
code = 'JWT_NOT_YET_VALID'
|
|
307
322
|
} else if (err instanceof errors.JOSEError) {
|
|
308
323
|
code = 'JWT_INVALID'
|
|
324
|
+
} else if (this.cfg.verifyAlgorithms && err instanceof TypeError) {
|
|
325
|
+
// With multiple verifyAlgorithms, a token/key algorithm mismatch is reachable
|
|
326
|
+
// by untrusted input, and jose reports it as TypeError - treat it as an invalid token
|
|
327
|
+
code = 'JWT_INVALID'
|
|
309
328
|
} else {
|
|
310
329
|
return err as Error
|
|
311
330
|
}
|
|
@@ -321,6 +340,31 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
321
340
|
}
|
|
322
341
|
}
|
|
323
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Decodes a JWT without verifying its signature (no key needed) - never trust the result.
|
|
345
|
+
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
346
|
+
* (no schema validation, no errorData extension).
|
|
347
|
+
*
|
|
348
|
+
* Throws JWTError with code JWT_INVALID if the token cannot be decoded.
|
|
349
|
+
*/
|
|
350
|
+
export function jwtDecode<T extends AnyObject>(token: JWTString): JWTDecoded<T> {
|
|
351
|
+
let header: JWTHeader
|
|
352
|
+
let payload: T
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
header = decodeProtectedHeader(token) as JWTHeader
|
|
356
|
+
payload = decodeJwt(token) as T
|
|
357
|
+
} catch (err) {
|
|
358
|
+
throw new JWTError('invalid token, unable to decode', { code: 'JWT_INVALID' }, { cause: err })
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
header,
|
|
363
|
+
payload,
|
|
364
|
+
signature: token.split('.')[2]!,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
324
368
|
export type JWTErrorCode = 'JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID'
|
|
325
369
|
|
|
326
370
|
export interface JWTErrorData extends ErrorData {
|