jose 4.6.0 → 4.7.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/browser/jwks/local.js +2 -1
- package/dist/browser/jwks/remote.js +11 -6
- package/dist/browser/lib/buffer_utils.js +5 -11
- package/dist/browser/lib/jwt_claims_set.js +2 -7
- package/dist/browser/runtime/base64url.js +6 -3
- package/dist/node/cjs/jwks/local.js +2 -1
- package/dist/node/cjs/jwks/remote.js +11 -6
- package/dist/node/cjs/lib/buffer_utils.js +5 -11
- package/dist/node/cjs/lib/jwt_claims_set.js +2 -7
- package/dist/node/cjs/runtime/generate.js +3 -2
- package/dist/node/esm/jwks/local.js +2 -1
- package/dist/node/esm/jwks/remote.js +11 -6
- package/dist/node/esm/lib/buffer_utils.js +5 -11
- package/dist/node/esm/lib/jwt_claims_set.js +2 -7
- package/dist/node/esm/runtime/generate.js +3 -2
- package/dist/types/jwks/remote.d.ts +20 -11
- package/package.json +1 -1
|
@@ -39,8 +39,9 @@ export class LocalJWKSet {
|
|
|
39
39
|
}
|
|
40
40
|
async getKey(protectedHeader, token) {
|
|
41
41
|
const { alg, kid } = { ...protectedHeader, ...token.header };
|
|
42
|
+
const kty = getKtyFromAlg(alg);
|
|
42
43
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
43
|
-
let candidate =
|
|
44
|
+
let candidate = kty === jwk.kty;
|
|
44
45
|
if (candidate && typeof kid === 'string') {
|
|
45
46
|
candidate = kid === jwk.kid;
|
|
46
47
|
}
|
|
@@ -15,15 +15,20 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
15
15
|
typeof (options === null || options === void 0 ? void 0 : options.timeoutDuration) === 'number' ? options === null || options === void 0 ? void 0 : options.timeoutDuration : 5000;
|
|
16
16
|
this._cooldownDuration =
|
|
17
17
|
typeof (options === null || options === void 0 ? void 0 : options.cooldownDuration) === 'number' ? options === null || options === void 0 ? void 0 : options.cooldownDuration : 30000;
|
|
18
|
+
this._cacheMaxAge = typeof (options === null || options === void 0 ? void 0 : options.cacheMaxAge) === 'number' ? options === null || options === void 0 ? void 0 : options.cacheMaxAge : 600000;
|
|
18
19
|
}
|
|
19
20
|
coolingDown() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
return typeof this._jwksTimestamp === 'number'
|
|
22
|
+
? Date.now() < this._jwksTimestamp + this._cooldownDuration
|
|
23
|
+
: false;
|
|
24
|
+
}
|
|
25
|
+
fresh() {
|
|
26
|
+
return typeof this._jwksTimestamp === 'number'
|
|
27
|
+
? Date.now() < this._jwksTimestamp + this._cacheMaxAge
|
|
28
|
+
: false;
|
|
24
29
|
}
|
|
25
30
|
async getKey(protectedHeader, token) {
|
|
26
|
-
if (!this._jwks) {
|
|
31
|
+
if (!this._jwks || !this.fresh()) {
|
|
27
32
|
await this.reload();
|
|
28
33
|
}
|
|
29
34
|
try {
|
|
@@ -60,7 +65,7 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
60
65
|
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
61
66
|
}
|
|
62
67
|
this._jwks = { keys: json.keys };
|
|
63
|
-
this.
|
|
68
|
+
this._jwksTimestamp = Date.now();
|
|
64
69
|
this._pendingFetch = undefined;
|
|
65
70
|
})
|
|
66
71
|
.catch((err) => {
|
|
@@ -39,19 +39,13 @@ export function lengthAndInput(input) {
|
|
|
39
39
|
}
|
|
40
40
|
export async function concatKdf(secret, bits, value) {
|
|
41
41
|
const iterations = Math.ceil((bits >> 3) / 32);
|
|
42
|
-
|
|
43
|
-
for (let iter =
|
|
42
|
+
const res = new Uint8Array(iterations * 32);
|
|
43
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
44
44
|
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
45
|
-
buf.set(uint32be(iter));
|
|
45
|
+
buf.set(uint32be(iter + 1));
|
|
46
46
|
buf.set(secret, 4);
|
|
47
47
|
buf.set(value, 4 + secret.length);
|
|
48
|
-
|
|
49
|
-
res = await digest('sha256', buf);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
res = concat(res, await digest('sha256', buf));
|
|
53
|
-
}
|
|
48
|
+
res.set(await digest('sha256', buf), iter * 32);
|
|
54
49
|
}
|
|
55
|
-
|
|
56
|
-
return res;
|
|
50
|
+
return res.slice(0, bits >> 3);
|
|
57
51
|
}
|
|
@@ -58,13 +58,8 @@ export default (protectedHeader, encodedPayload, options = {}) => {
|
|
|
58
58
|
}
|
|
59
59
|
const { currentDate } = options;
|
|
60
60
|
const now = epoch(currentDate || new Date());
|
|
61
|
-
if (payload.iat !== undefined || options.maxTokenAge) {
|
|
62
|
-
|
|
63
|
-
throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
64
|
-
}
|
|
65
|
-
if (payload.exp === undefined && payload.iat > now + tolerance) {
|
|
66
|
-
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', 'iat', 'check_failed');
|
|
67
|
-
}
|
|
61
|
+
if ((payload.iat !== undefined || options.maxTokenAge) && typeof payload.iat !== 'number') {
|
|
62
|
+
throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
68
63
|
}
|
|
69
64
|
if (payload.nbf !== undefined) {
|
|
70
65
|
if (typeof payload.nbf !== 'number') {
|
|
@@ -15,9 +15,12 @@ export const encode = (input) => {
|
|
|
15
15
|
return encodeBase64(input).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
16
16
|
};
|
|
17
17
|
export const decodeBase64 = (encoded) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const binary = atob(encoded);
|
|
19
|
+
const bytes = new Uint8Array(binary.length);
|
|
20
|
+
for (let i = 0; i < binary.length; i++) {
|
|
21
|
+
bytes[i] = binary.charCodeAt(i);
|
|
22
|
+
}
|
|
23
|
+
return bytes;
|
|
21
24
|
};
|
|
22
25
|
export const decode = (input) => {
|
|
23
26
|
let encoded = input;
|
|
@@ -43,8 +43,9 @@ class LocalJWKSet {
|
|
|
43
43
|
}
|
|
44
44
|
async getKey(protectedHeader, token) {
|
|
45
45
|
const { alg, kid } = { ...protectedHeader, ...token.header };
|
|
46
|
+
const kty = getKtyFromAlg(alg);
|
|
46
47
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
47
|
-
let candidate =
|
|
48
|
+
let candidate = kty === jwk.kty;
|
|
48
49
|
if (candidate && typeof kid === 'string') {
|
|
49
50
|
candidate = kid === jwk.kid;
|
|
50
51
|
}
|
|
@@ -18,15 +18,20 @@ class RemoteJWKSet extends local_js_1.LocalJWKSet {
|
|
|
18
18
|
typeof (options === null || options === void 0 ? void 0 : options.timeoutDuration) === 'number' ? options === null || options === void 0 ? void 0 : options.timeoutDuration : 5000;
|
|
19
19
|
this._cooldownDuration =
|
|
20
20
|
typeof (options === null || options === void 0 ? void 0 : options.cooldownDuration) === 'number' ? options === null || options === void 0 ? void 0 : options.cooldownDuration : 30000;
|
|
21
|
+
this._cacheMaxAge = typeof (options === null || options === void 0 ? void 0 : options.cacheMaxAge) === 'number' ? options === null || options === void 0 ? void 0 : options.cacheMaxAge : 600000;
|
|
21
22
|
}
|
|
22
23
|
coolingDown() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
return typeof this._jwksTimestamp === 'number'
|
|
25
|
+
? Date.now() < this._jwksTimestamp + this._cooldownDuration
|
|
26
|
+
: false;
|
|
27
|
+
}
|
|
28
|
+
fresh() {
|
|
29
|
+
return typeof this._jwksTimestamp === 'number'
|
|
30
|
+
? Date.now() < this._jwksTimestamp + this._cacheMaxAge
|
|
31
|
+
: false;
|
|
27
32
|
}
|
|
28
33
|
async getKey(protectedHeader, token) {
|
|
29
|
-
if (!this._jwks) {
|
|
34
|
+
if (!this._jwks || !this.fresh()) {
|
|
30
35
|
await this.reload();
|
|
31
36
|
}
|
|
32
37
|
try {
|
|
@@ -63,7 +68,7 @@ class RemoteJWKSet extends local_js_1.LocalJWKSet {
|
|
|
63
68
|
throw new errors_js_1.JWKSInvalid('JSON Web Key Set malformed');
|
|
64
69
|
}
|
|
65
70
|
this._jwks = { keys: json.keys };
|
|
66
|
-
this.
|
|
71
|
+
this._jwksTimestamp = Date.now();
|
|
67
72
|
this._pendingFetch = undefined;
|
|
68
73
|
})
|
|
69
74
|
.catch((err) => {
|
|
@@ -47,20 +47,14 @@ function lengthAndInput(input) {
|
|
|
47
47
|
exports.lengthAndInput = lengthAndInput;
|
|
48
48
|
async function concatKdf(secret, bits, value) {
|
|
49
49
|
const iterations = Math.ceil((bits >> 3) / 32);
|
|
50
|
-
|
|
51
|
-
for (let iter =
|
|
50
|
+
const res = new Uint8Array(iterations * 32);
|
|
51
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
52
52
|
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
53
|
-
buf.set(uint32be(iter));
|
|
53
|
+
buf.set(uint32be(iter + 1));
|
|
54
54
|
buf.set(secret, 4);
|
|
55
55
|
buf.set(value, 4 + secret.length);
|
|
56
|
-
|
|
57
|
-
res = await (0, digest_js_1.default)('sha256', buf);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
res = concat(res, await (0, digest_js_1.default)('sha256', buf));
|
|
61
|
-
}
|
|
56
|
+
res.set(await (0, digest_js_1.default)('sha256', buf), iter * 32);
|
|
62
57
|
}
|
|
63
|
-
|
|
64
|
-
return res;
|
|
58
|
+
return res.slice(0, bits >> 3);
|
|
65
59
|
}
|
|
66
60
|
exports.concatKdf = concatKdf;
|
|
@@ -60,13 +60,8 @@ exports.default = (protectedHeader, encodedPayload, options = {}) => {
|
|
|
60
60
|
}
|
|
61
61
|
const { currentDate } = options;
|
|
62
62
|
const now = (0, epoch_js_1.default)(currentDate || new Date());
|
|
63
|
-
if (payload.iat !== undefined || options.maxTokenAge) {
|
|
64
|
-
|
|
65
|
-
throw new errors_js_1.JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
66
|
-
}
|
|
67
|
-
if (payload.exp === undefined && payload.iat > now + tolerance) {
|
|
68
|
-
throw new errors_js_1.JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', 'iat', 'check_failed');
|
|
69
|
-
}
|
|
63
|
+
if ((payload.iat !== undefined || options.maxTokenAge) && typeof payload.iat !== 'number') {
|
|
64
|
+
throw new errors_js_1.JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
70
65
|
}
|
|
71
66
|
if (payload.nbf !== undefined) {
|
|
72
67
|
if (typeof payload.nbf !== 'number') {
|
|
@@ -84,12 +84,13 @@ async function generateKeyPair(alg, options) {
|
|
|
84
84
|
case 'ECDH-ES+A128KW':
|
|
85
85
|
case 'ECDH-ES+A192KW':
|
|
86
86
|
case 'ECDH-ES+A256KW':
|
|
87
|
-
|
|
87
|
+
const crv = (_b = options === null || options === void 0 ? void 0 : options.crv) !== null && _b !== void 0 ? _b : 'P-256';
|
|
88
|
+
switch (crv) {
|
|
88
89
|
case undefined:
|
|
89
90
|
case 'P-256':
|
|
90
91
|
case 'P-384':
|
|
91
92
|
case 'P-521':
|
|
92
|
-
return generate('ec', { namedCurve:
|
|
93
|
+
return generate('ec', { namedCurve: crv });
|
|
93
94
|
case 'X25519':
|
|
94
95
|
return generate('x25519');
|
|
95
96
|
case 'X448':
|
|
@@ -39,8 +39,9 @@ export class LocalJWKSet {
|
|
|
39
39
|
}
|
|
40
40
|
async getKey(protectedHeader, token) {
|
|
41
41
|
const { alg, kid } = { ...protectedHeader, ...token.header };
|
|
42
|
+
const kty = getKtyFromAlg(alg);
|
|
42
43
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
43
|
-
let candidate =
|
|
44
|
+
let candidate = kty === jwk.kty;
|
|
44
45
|
if (candidate && typeof kid === 'string') {
|
|
45
46
|
candidate = kid === jwk.kid;
|
|
46
47
|
}
|
|
@@ -15,15 +15,20 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
15
15
|
typeof (options === null || options === void 0 ? void 0 : options.timeoutDuration) === 'number' ? options === null || options === void 0 ? void 0 : options.timeoutDuration : 5000;
|
|
16
16
|
this._cooldownDuration =
|
|
17
17
|
typeof (options === null || options === void 0 ? void 0 : options.cooldownDuration) === 'number' ? options === null || options === void 0 ? void 0 : options.cooldownDuration : 30000;
|
|
18
|
+
this._cacheMaxAge = typeof (options === null || options === void 0 ? void 0 : options.cacheMaxAge) === 'number' ? options === null || options === void 0 ? void 0 : options.cacheMaxAge : 600000;
|
|
18
19
|
}
|
|
19
20
|
coolingDown() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
return typeof this._jwksTimestamp === 'number'
|
|
22
|
+
? Date.now() < this._jwksTimestamp + this._cooldownDuration
|
|
23
|
+
: false;
|
|
24
|
+
}
|
|
25
|
+
fresh() {
|
|
26
|
+
return typeof this._jwksTimestamp === 'number'
|
|
27
|
+
? Date.now() < this._jwksTimestamp + this._cacheMaxAge
|
|
28
|
+
: false;
|
|
24
29
|
}
|
|
25
30
|
async getKey(protectedHeader, token) {
|
|
26
|
-
if (!this._jwks) {
|
|
31
|
+
if (!this._jwks || !this.fresh()) {
|
|
27
32
|
await this.reload();
|
|
28
33
|
}
|
|
29
34
|
try {
|
|
@@ -60,7 +65,7 @@ class RemoteJWKSet extends LocalJWKSet {
|
|
|
60
65
|
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
61
66
|
}
|
|
62
67
|
this._jwks = { keys: json.keys };
|
|
63
|
-
this.
|
|
68
|
+
this._jwksTimestamp = Date.now();
|
|
64
69
|
this._pendingFetch = undefined;
|
|
65
70
|
})
|
|
66
71
|
.catch((err) => {
|
|
@@ -39,19 +39,13 @@ export function lengthAndInput(input) {
|
|
|
39
39
|
}
|
|
40
40
|
export async function concatKdf(secret, bits, value) {
|
|
41
41
|
const iterations = Math.ceil((bits >> 3) / 32);
|
|
42
|
-
|
|
43
|
-
for (let iter =
|
|
42
|
+
const res = new Uint8Array(iterations * 32);
|
|
43
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
44
44
|
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
45
|
-
buf.set(uint32be(iter));
|
|
45
|
+
buf.set(uint32be(iter + 1));
|
|
46
46
|
buf.set(secret, 4);
|
|
47
47
|
buf.set(value, 4 + secret.length);
|
|
48
|
-
|
|
49
|
-
res = await digest('sha256', buf);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
res = concat(res, await digest('sha256', buf));
|
|
53
|
-
}
|
|
48
|
+
res.set(await digest('sha256', buf), iter * 32);
|
|
54
49
|
}
|
|
55
|
-
|
|
56
|
-
return res;
|
|
50
|
+
return res.slice(0, bits >> 3);
|
|
57
51
|
}
|
|
@@ -58,13 +58,8 @@ export default (protectedHeader, encodedPayload, options = {}) => {
|
|
|
58
58
|
}
|
|
59
59
|
const { currentDate } = options;
|
|
60
60
|
const now = epoch(currentDate || new Date());
|
|
61
|
-
if (payload.iat !== undefined || options.maxTokenAge) {
|
|
62
|
-
|
|
63
|
-
throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
64
|
-
}
|
|
65
|
-
if (payload.exp === undefined && payload.iat > now + tolerance) {
|
|
66
|
-
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', 'iat', 'check_failed');
|
|
67
|
-
}
|
|
61
|
+
if ((payload.iat !== undefined || options.maxTokenAge) && typeof payload.iat !== 'number') {
|
|
62
|
+
throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid');
|
|
68
63
|
}
|
|
69
64
|
if (payload.nbf !== undefined) {
|
|
70
65
|
if (typeof payload.nbf !== 'number') {
|
|
@@ -80,12 +80,13 @@ export async function generateKeyPair(alg, options) {
|
|
|
80
80
|
case 'ECDH-ES+A128KW':
|
|
81
81
|
case 'ECDH-ES+A192KW':
|
|
82
82
|
case 'ECDH-ES+A256KW':
|
|
83
|
-
|
|
83
|
+
const crv = (_b = options === null || options === void 0 ? void 0 : options.crv) !== null && _b !== void 0 ? _b : 'P-256';
|
|
84
|
+
switch (crv) {
|
|
84
85
|
case undefined:
|
|
85
86
|
case 'P-256':
|
|
86
87
|
case 'P-384':
|
|
87
88
|
case 'P-521':
|
|
88
|
-
return generate('ec', { namedCurve:
|
|
89
|
+
return generate('ec', { namedCurve: crv });
|
|
89
90
|
case 'X25519':
|
|
90
91
|
return generate('x25519');
|
|
91
92
|
case 'X448':
|
|
@@ -4,23 +4,32 @@ import type { JWSHeaderParameters, FlattenedJWSInput, GetKeyFunction } from '../
|
|
|
4
4
|
*/
|
|
5
5
|
export interface RemoteJWKSetOptions {
|
|
6
6
|
/**
|
|
7
|
-
* Timeout (in milliseconds) for the HTTP request. When reached the request
|
|
8
|
-
* aborted and the verification will fail. Default is 5000
|
|
7
|
+
* Timeout (in milliseconds) for the HTTP request. When reached the request
|
|
8
|
+
* will be aborted and the verification will fail. Default is 5000 (5
|
|
9
|
+
* seconds).
|
|
9
10
|
*/
|
|
10
11
|
timeoutDuration?: number;
|
|
11
12
|
/**
|
|
12
|
-
* Duration (in milliseconds) for which no more HTTP requests will be
|
|
13
|
-
* after a previous successful fetch. Default is 30000.
|
|
13
|
+
* Duration (in milliseconds) for which no more HTTP requests will be
|
|
14
|
+
* triggered after a previous successful fetch. Default is 30000 (30 seconds).
|
|
14
15
|
*/
|
|
15
16
|
cooldownDuration?: number;
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Maximum time (in milliseconds) between successful HTTP requests. Default is
|
|
19
|
+
* 600000 (10 minutes).
|
|
20
|
+
*/
|
|
21
|
+
cacheMaxAge?: number | typeof Infinity;
|
|
22
|
+
/**
|
|
23
|
+
* An instance of
|
|
24
|
+
* [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent) or
|
|
25
|
+
* [https.Agent](https://nodejs.org/api/https.html#https_class_https_agent) to
|
|
26
|
+
* pass to the
|
|
27
|
+
* [http.get](https://nodejs.org/api/http.html#http_http_get_options_callback)
|
|
28
|
+
* or
|
|
29
|
+
* [https.get](https://nodejs.org/api/https.html#https_https_get_options_callback)
|
|
30
|
+
* method's options. Use when behind an http(s) proxy. This is a Node.js
|
|
31
|
+
* runtime specific option, it is ignored when used outside of Node.js
|
|
32
|
+
* runtime.
|
|
24
33
|
*/
|
|
25
34
|
agent?: any;
|
|
26
35
|
}
|