@naturalcycles/nodejs-lib 15.116.0 → 15.116.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jwt/jwt.service2.d.ts +19 -0
- package/dist/jwt/jwt.service2.js +58 -20
- package/package.json +1 -1
- package/src/jwt/jwt.service2.ts +62 -21
|
@@ -78,6 +78,10 @@ export interface JWTSignOptions<T extends AnyObject = AnyObject> {
|
|
|
78
78
|
* By default `iat` is NOT set (same as legacy JWTService with its `noTimestamp: true` default).
|
|
79
79
|
*/
|
|
80
80
|
issuedAt?: UnixTimestamp;
|
|
81
|
+
/**
|
|
82
|
+
* Sets the `kid` (key id) header, required by some APIs (e.g Apple App Store Connect).
|
|
83
|
+
*/
|
|
84
|
+
kid?: string;
|
|
81
85
|
/**
|
|
82
86
|
* Overrides cfg.schema for this call.
|
|
83
87
|
*/
|
|
@@ -156,6 +160,13 @@ export declare class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
156
160
|
sign<TT extends T = T>(payload: TT, opt: JWTSignOptions<TT>): Promise<JWTString>;
|
|
157
161
|
verify<TT extends T = T>(token: JWTString, opt?: JWTVerifyOptions<TT>): Promise<TT>;
|
|
158
162
|
decode<TT extends T = T>(token: JWTString, opt?: JWTDecodeOptions<TT>): JWTDecoded<TT>;
|
|
163
|
+
/**
|
|
164
|
+
* Schema-validation errors on Verify/Decode are extended with cfg.errorData:
|
|
165
|
+
* a token with a non-conforming payload is as unauthorized as an invalid one.
|
|
166
|
+
* (On Sign the payload comes from own code, not from user input, so a validation
|
|
167
|
+
* error there indicates a programming error and is thrown as-is.)
|
|
168
|
+
*/
|
|
169
|
+
private validate;
|
|
159
170
|
/**
|
|
160
171
|
* jose errors are normalized into JWTError (extended with cfg.errorData).
|
|
161
172
|
* Non-jose errors (e.g TypeError from passing a key that doesn't fit the algorithm)
|
|
@@ -163,6 +174,14 @@ export declare class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
163
174
|
*/
|
|
164
175
|
private normalizeError;
|
|
165
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Decodes a JWT without verifying its signature (no key needed) - never trust the result.
|
|
179
|
+
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
180
|
+
* (no schema validation, no errorData extension).
|
|
181
|
+
*
|
|
182
|
+
* Throws JWTError with code JWT_INVALID if the token cannot be decoded.
|
|
183
|
+
*/
|
|
184
|
+
export declare function jwtDecode<T extends AnyObject>(token: JWTString): JWTDecoded<T>;
|
|
166
185
|
export type JWTErrorCode = 'JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID';
|
|
167
186
|
export interface JWTErrorData extends ErrorData {
|
|
168
187
|
code: JWTErrorCode;
|
package/dist/jwt/jwt.service2.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createPrivateKey, createPublicKey } from 'node:crypto';
|
|
2
2
|
import { _assert } from '@naturalcycles/js-lib/error/assert.js';
|
|
3
|
-
import { AppError } from '@naturalcycles/js-lib/error/error.util.js';
|
|
3
|
+
import { _errorDataAppend, AppError } from '@naturalcycles/js-lib/error/error.util.js';
|
|
4
4
|
import { decodeJwt, decodeProtectedHeader, errors, jwtVerify, SignJWT } from 'jose';
|
|
5
5
|
/**
|
|
6
6
|
* Wraps the `jose` library, exposing an implementation-agnostic API:
|
|
@@ -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)
|
|
@@ -79,30 +84,40 @@ export class JWTService2 {
|
|
|
79
84
|
catch (err) {
|
|
80
85
|
throw this.normalizeError(err);
|
|
81
86
|
}
|
|
82
|
-
;
|
|
83
|
-
(schema || this.cfg.schema)?.validate(data);
|
|
87
|
+
this.validate(data, schema);
|
|
84
88
|
return data;
|
|
85
89
|
}
|
|
86
90
|
decode(token, opt = {}) {
|
|
87
|
-
let
|
|
88
|
-
let payload;
|
|
91
|
+
let decoded;
|
|
89
92
|
try {
|
|
90
|
-
|
|
91
|
-
payload = decodeJwt(token);
|
|
93
|
+
decoded = jwtDecode(token);
|
|
92
94
|
}
|
|
93
95
|
catch (err) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
if (this.cfg.errorData) {
|
|
97
|
+
_errorDataAppend(err, this.cfg.errorData);
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
this.validate(decoded.payload, opt.schema);
|
|
102
|
+
return decoded;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Schema-validation errors on Verify/Decode are extended with cfg.errorData:
|
|
106
|
+
* a token with a non-conforming payload is as unauthorized as an invalid one.
|
|
107
|
+
* (On Sign the payload comes from own code, not from user input, so a validation
|
|
108
|
+
* error there indicates a programming error and is thrown as-is.)
|
|
109
|
+
*/
|
|
110
|
+
validate(payload, schema) {
|
|
111
|
+
try {
|
|
112
|
+
;
|
|
113
|
+
(schema || this.cfg.schema)?.validate(payload);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (this.cfg.errorData) {
|
|
117
|
+
_errorDataAppend(err, this.cfg.errorData);
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
98
120
|
}
|
|
99
|
-
;
|
|
100
|
-
(opt.schema || this.cfg.schema)?.validate(payload);
|
|
101
|
-
return {
|
|
102
|
-
header,
|
|
103
|
-
payload,
|
|
104
|
-
signature: token.split('.')[2],
|
|
105
|
-
};
|
|
106
121
|
}
|
|
107
122
|
/**
|
|
108
123
|
* jose errors are normalized into JWTError (extended with cfg.errorData).
|
|
@@ -129,6 +144,29 @@ export class JWTService2 {
|
|
|
129
144
|
}, { cause: err });
|
|
130
145
|
}
|
|
131
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Decodes a JWT without verifying its signature (no key needed) - never trust the result.
|
|
149
|
+
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
150
|
+
* (no schema validation, no errorData extension).
|
|
151
|
+
*
|
|
152
|
+
* Throws JWTError with code JWT_INVALID if the token cannot be decoded.
|
|
153
|
+
*/
|
|
154
|
+
export function jwtDecode(token) {
|
|
155
|
+
let header;
|
|
156
|
+
let payload;
|
|
157
|
+
try {
|
|
158
|
+
header = decodeProtectedHeader(token);
|
|
159
|
+
payload = decodeJwt(token);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
throw new JWTError('invalid token, unable to decode', { code: 'JWT_INVALID' }, { cause: err });
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
header,
|
|
166
|
+
payload,
|
|
167
|
+
signature: token.split('.')[2],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
132
170
|
/**
|
|
133
171
|
* Thrown by JWTService2 on any Verify/Decode failure.
|
|
134
172
|
*
|
package/package.json
CHANGED
package/src/jwt/jwt.service2.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createPrivateKey, createPublicKey } from 'node:crypto'
|
|
|
2
2
|
import type { KeyObject } from 'node:crypto'
|
|
3
3
|
import type { ErrorData } from '@naturalcycles/js-lib/error'
|
|
4
4
|
import { _assert } from '@naturalcycles/js-lib/error/assert.js'
|
|
5
|
-
import { AppError } from '@naturalcycles/js-lib/error/error.util.js'
|
|
5
|
+
import { _errorDataAppend, AppError } from '@naturalcycles/js-lib/error/error.util.js'
|
|
6
6
|
import type {
|
|
7
7
|
AnyObject,
|
|
8
8
|
JWTString,
|
|
@@ -105,6 +105,10 @@ export interface JWTSignOptions<T extends AnyObject = AnyObject> {
|
|
|
105
105
|
* By default `iat` is NOT set (same as legacy JWTService with its `noTimestamp: true` default).
|
|
106
106
|
*/
|
|
107
107
|
issuedAt?: UnixTimestamp
|
|
108
|
+
/**
|
|
109
|
+
* Sets the `kid` (key id) header, required by some APIs (e.g Apple App Store Connect).
|
|
110
|
+
*/
|
|
111
|
+
kid?: string
|
|
108
112
|
/**
|
|
109
113
|
* Overrides cfg.schema for this call.
|
|
110
114
|
*/
|
|
@@ -198,14 +202,19 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
198
202
|
this.cfg.errorData,
|
|
199
203
|
)
|
|
200
204
|
|
|
201
|
-
const { expiresAt, notBefore, issuer, audience, subject, jwtid, issuedAt, schema } = {
|
|
205
|
+
const { expiresAt, notBefore, issuer, audience, subject, jwtid, issuedAt, kid, schema } = {
|
|
202
206
|
...this.cfg.signOptions,
|
|
203
207
|
...opt,
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
;(schema || this.cfg.schema)?.validate(payload)
|
|
207
211
|
|
|
208
|
-
|
|
212
|
+
// `kid: undefined` is dropped by JSON serialization, keeping the header unchanged when not set
|
|
213
|
+
const jwt = new SignJWT(payload).setProtectedHeader({
|
|
214
|
+
alg: this.cfg.algorithm,
|
|
215
|
+
typ: 'JWT',
|
|
216
|
+
kid,
|
|
217
|
+
})
|
|
209
218
|
if (expiresAt !== null) jwt.setExpirationTime(expiresAt)
|
|
210
219
|
if (notBefore !== undefined) jwt.setNotBefore(notBefore)
|
|
211
220
|
if (issuer) jwt.setIssuer(issuer)
|
|
@@ -243,35 +252,42 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
243
252
|
throw this.normalizeError(err)
|
|
244
253
|
}
|
|
245
254
|
|
|
246
|
-
|
|
255
|
+
this.validate(data, schema)
|
|
247
256
|
|
|
248
257
|
return data
|
|
249
258
|
}
|
|
250
259
|
|
|
251
260
|
decode<TT extends T = T>(token: JWTString, opt: JWTDecodeOptions<TT> = {}): JWTDecoded<TT> {
|
|
252
|
-
let
|
|
253
|
-
let payload: TT
|
|
261
|
+
let decoded: JWTDecoded<TT>
|
|
254
262
|
|
|
255
263
|
try {
|
|
256
|
-
|
|
257
|
-
payload = decodeJwt(token) as TT
|
|
264
|
+
decoded = jwtDecode<TT>(token)
|
|
258
265
|
} catch (err) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
code: 'JWT_INVALID',
|
|
264
|
-
},
|
|
265
|
-
{ cause: err },
|
|
266
|
-
)
|
|
266
|
+
if (this.cfg.errorData) {
|
|
267
|
+
_errorDataAppend(err, this.cfg.errorData)
|
|
268
|
+
}
|
|
269
|
+
throw err
|
|
267
270
|
}
|
|
268
271
|
|
|
269
|
-
|
|
272
|
+
this.validate(decoded.payload, opt.schema)
|
|
273
|
+
|
|
274
|
+
return decoded
|
|
275
|
+
}
|
|
270
276
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Schema-validation errors on Verify/Decode are extended with cfg.errorData:
|
|
279
|
+
* a token with a non-conforming payload is as unauthorized as an invalid one.
|
|
280
|
+
* (On Sign the payload comes from own code, not from user input, so a validation
|
|
281
|
+
* error there indicates a programming error and is thrown as-is.)
|
|
282
|
+
*/
|
|
283
|
+
private validate<TT extends T>(payload: TT, schema?: JSchema<TT, any> | AjvSchema<TT>): void {
|
|
284
|
+
try {
|
|
285
|
+
;(schema || this.cfg.schema)?.validate(payload)
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (this.cfg.errorData) {
|
|
288
|
+
_errorDataAppend(err, this.cfg.errorData)
|
|
289
|
+
}
|
|
290
|
+
throw err
|
|
275
291
|
}
|
|
276
292
|
}
|
|
277
293
|
|
|
@@ -304,6 +320,31 @@ export class JWTService2<T extends AnyObject = AnyObject> {
|
|
|
304
320
|
}
|
|
305
321
|
}
|
|
306
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Decodes a JWT without verifying its signature (no key needed) - never trust the result.
|
|
325
|
+
* Standalone version of JWTService2.decode, for when there's no service instance at hand
|
|
326
|
+
* (no schema validation, no errorData extension).
|
|
327
|
+
*
|
|
328
|
+
* Throws JWTError with code JWT_INVALID if the token cannot be decoded.
|
|
329
|
+
*/
|
|
330
|
+
export function jwtDecode<T extends AnyObject>(token: JWTString): JWTDecoded<T> {
|
|
331
|
+
let header: JWTHeader
|
|
332
|
+
let payload: T
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
header = decodeProtectedHeader(token) as JWTHeader
|
|
336
|
+
payload = decodeJwt(token) as T
|
|
337
|
+
} catch (err) {
|
|
338
|
+
throw new JWTError('invalid token, unable to decode', { code: 'JWT_INVALID' }, { cause: err })
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
header,
|
|
343
|
+
payload,
|
|
344
|
+
signature: token.split('.')[2]!,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
307
348
|
export type JWTErrorCode = 'JWT_EXPIRED' | 'JWT_NOT_YET_VALID' | 'JWT_INVALID'
|
|
308
349
|
|
|
309
350
|
export interface JWTErrorData extends ErrorData {
|