repocairn 0.1.6 → 0.1.7

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 (62) hide show
  1. package/node_modules/fast-uri/index.js +201 -48
  2. package/node_modules/fast-uri/lib/schemes.js +9 -4
  3. package/node_modules/fast-uri/lib/utils.js +388 -91
  4. package/node_modules/fast-uri/package.json +1 -1
  5. package/node_modules/fast-uri/test/component-safe-serialization.test.js +105 -0
  6. package/node_modules/fast-uri/test/equal.test.js +31 -3
  7. package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
  8. package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
  9. package/node_modules/fast-uri/test/ipv6-validation.test.js +90 -0
  10. package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
  11. package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
  12. package/node_modules/fast-uri/test/parse.test.js +7 -3
  13. package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
  14. package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
  15. package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
  16. package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
  17. package/node_modules/fast-uri/test/security.test.js +75 -3
  18. package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
  19. package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
  20. package/node_modules/jose/dist/types/jwks/remote.d.ts +4 -4
  21. package/node_modules/jose/dist/types/jws/general/verify.d.ts +3 -3
  22. package/node_modules/jose/dist/types/jwt/encrypt.d.ts +8 -14
  23. package/node_modules/jose/dist/types/jwt/sign.d.ts +8 -14
  24. package/node_modules/jose/dist/types/jwt/unsecured.d.ts +9 -15
  25. package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +30 -8
  26. package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +8 -4
  27. package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +6 -13
  28. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +19 -19
  29. package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +27 -20
  30. package/node_modules/jose/dist/webapi/jwk/embedded.js +15 -1
  31. package/node_modules/jose/dist/webapi/jwk/thumbprint.js +11 -5
  32. package/node_modules/jose/dist/webapi/jwks/local.js +45 -53
  33. package/node_modules/jose/dist/webapi/jwks/remote.js +82 -103
  34. package/node_modules/jose/dist/webapi/jws/compact/sign.js +10 -9
  35. package/node_modules/jose/dist/webapi/jws/flattened/sign.js +2 -1
  36. package/node_modules/jose/dist/webapi/jws/flattened/verify.js +8 -7
  37. package/node_modules/jose/dist/webapi/jws/general/sign.js +5 -3
  38. package/node_modules/jose/dist/webapi/jws/general/verify.js +51 -21
  39. package/node_modules/jose/dist/webapi/jwt/decrypt.js +7 -9
  40. package/node_modules/jose/dist/webapi/jwt/encrypt.js +21 -50
  41. package/node_modules/jose/dist/webapi/jwt/sign.js +8 -41
  42. package/node_modules/jose/dist/webapi/jwt/unsecured.js +21 -42
  43. package/node_modules/jose/dist/webapi/key/generate_key_pair.js +6 -2
  44. package/node_modules/jose/dist/webapi/key/generate_secret.js +7 -6
  45. package/node_modules/jose/dist/webapi/key/import.js +16 -12
  46. package/node_modules/jose/dist/webapi/lib/asn1.js +7 -2
  47. package/node_modules/jose/dist/webapi/lib/content_encryption.js +7 -15
  48. package/node_modules/jose/dist/webapi/lib/deflate.js +8 -0
  49. package/node_modules/jose/dist/webapi/lib/helpers.js +1 -1
  50. package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +85 -28
  51. package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +40 -12
  52. package/node_modules/jose/dist/webapi/lib/jwk_metadata.js +20 -0
  53. package/node_modules/jose/dist/webapi/lib/jws_sign.js +47 -36
  54. package/node_modules/jose/dist/webapi/lib/jws_verify.js +77 -45
  55. package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +82 -47
  56. package/node_modules/jose/dist/webapi/lib/key.js +24 -9
  57. package/node_modules/jose/dist/webapi/lib/key_management.js +10 -4
  58. package/node_modules/jose/dist/webapi/lib/key_options.js +6 -0
  59. package/node_modules/jose/dist/webapi/lib/options.js +26 -0
  60. package/node_modules/jose/dist/webapi/lib/type_checks.js +11 -13
  61. package/node_modules/jose/package.json +1 -1
  62. package/package.json +1 -1
@@ -1,13 +1,12 @@
1
- import { FlattenedEncrypt } from '../flattened/encrypt.js';
2
1
  import { assertNotSet } from '../../lib/helpers.js';
3
2
  import { JWEInvalid } from '../../util/errors.js';
4
3
  import { generateCek } from '../../lib/content_encryption.js';
5
4
  import { encryptKeyManagement } from '../../lib/key_management.js';
6
5
  import { encode as b64u } from '../../util/base64url.js';
7
- import { validateCritDuplicates } from '../../lib/options.js';
8
- import { checkDisjoint, checkEncryptHeaders, encryptJWE } from '../../lib/jwe_encrypt.js';
6
+ import { checkDisjoint, checkEncryptHeaders, createJWE, encryptJWE } from '../../lib/jwe_encrypt.js';
9
7
  import { prepareKey } from '../../lib/key.js';
10
8
  import { jweAlgorithm } from '../../lib/jwe_algorithms.js';
9
+ import { assertUint8Array } from '../../lib/type_checks.js';
11
10
  class IndividualRecipient {
12
11
  #parent;
13
12
  state;
@@ -78,19 +77,21 @@ export class GeneralEncrypt {
78
77
  if (!this.#recipients.length) {
79
78
  throw new JWEInvalid('at least one recipient must be added');
80
79
  }
81
- if (!(this.#plaintext instanceof Uint8Array)) {
82
- throw new TypeError('plaintext must be an instance of Uint8Array');
83
- }
80
+ assertUint8Array(this.#plaintext, 'plaintext');
84
81
  if (this.#recipients.length === 1) {
85
- const [recipient] = this.#recipients;
86
- const [unprotectedHeader, keyManagementParameters, key, crit] = recipient.state;
87
- const flattened = await new FlattenedEncrypt(this.#plaintext)
88
- .setAdditionalAuthenticatedData(this.#aad)
89
- .setProtectedHeader(this.#protectedHeader)
90
- .setSharedUnprotectedHeader(this.#unprotectedHeader)
91
- .setUnprotectedHeader(unprotectedHeader)
92
- .setKeyManagementParameters(keyManagementParameters)
93
- .encrypt(key, { crit });
82
+ const [unprotectedHeader, keyManagementParameters, key, crit] = this.#recipients[0].state;
83
+ const flattened = await createJWE([
84
+ this.#plaintext,
85
+ this.#protectedHeader,
86
+ unprotectedHeader,
87
+ this.#unprotectedHeader,
88
+ this.#aad,
89
+ undefined,
90
+ undefined,
91
+ keyManagementParameters,
92
+ crit,
93
+ false,
94
+ ], key);
94
95
  const jwe = {
95
96
  ciphertext: flattened.ciphertext,
96
97
  iv: flattened.iv,
@@ -102,8 +103,9 @@ export class GeneralEncrypt {
102
103
  copyOptionalMembers(flattened, jwe, jwe.recipients[0]);
103
104
  return jwe;
104
105
  }
105
- validateCritDuplicates(JWEInvalid, this.#protectedHeader);
106
106
  let enc;
107
+ let protectedHeader = this.#protectedHeader;
108
+ let sharedUnprotectedHeader = this.#unprotectedHeader;
107
109
  const inputs = [];
108
110
  const checked = [];
109
111
  for (let i = 0; i < this.#recipients.length; i++) {
@@ -111,9 +113,9 @@ export class GeneralEncrypt {
111
113
  const [unprotectedHeader, keyManagementParameters, , crit] = recipient.state;
112
114
  const input = [
113
115
  this.#plaintext,
114
- this.#protectedHeader,
116
+ protectedHeader,
115
117
  unprotectedHeader,
116
- this.#unprotectedHeader,
118
+ sharedUnprotectedHeader,
117
119
  this.#aad,
118
120
  undefined,
119
121
  undefined,
@@ -124,6 +126,10 @@ export class GeneralEncrypt {
124
126
  const headers = checkEncryptHeaders(input);
125
127
  inputs.push(input);
126
128
  checked.push(headers);
129
+ if (i === 0) {
130
+ protectedHeader = input[1];
131
+ sharedUnprotectedHeader = input[3];
132
+ }
127
133
  if (headers[1] === 'dir' || headers[1] === 'ECDH-ES') {
128
134
  throw new JWEInvalid(`"${headers[1]}" alg may only have a single recipient`);
129
135
  }
@@ -141,7 +147,7 @@ export class GeneralEncrypt {
141
147
  };
142
148
  for (let i = 0; i < this.#recipients.length; i++) {
143
149
  const recipient = this.#recipients[i];
144
- const [unprotectedHeader, keyManagementParameters, key] = recipient.state;
150
+ const [, keyManagementParameters, key] = recipient.state;
145
151
  const target = {};
146
152
  jwe.recipients.push(target);
147
153
  if (i === 0) {
@@ -155,13 +161,14 @@ export class GeneralEncrypt {
155
161
  continue;
156
162
  }
157
163
  const [, alg, , encEntry] = checked[i];
164
+ const unprotectedHeader = inputs[i][2];
158
165
  const k = await prepareKey(jweAlgorithm(alg), key, 'encrypt');
159
166
  const [, encryptedKey, parameters] = await encryptKeyManagement(alg, encEntry, k, cek, keyManagementParameters);
160
167
  target.encrypted_key = b64u(encryptedKey);
161
168
  if (unprotectedHeader || parameters) {
162
169
  const header = { ...unprotectedHeader, ...parameters };
163
170
  if (parameters)
164
- checkDisjoint(this.#protectedHeader, header, this.#unprotectedHeader);
171
+ checkDisjoint(inputs[i][1], header, inputs[i][3]);
165
172
  target.header = header;
166
173
  }
167
174
  }
@@ -2,6 +2,7 @@ import { jwkToKey } from '../lib/jwk_to_key.js';
2
2
  import { jwsAlgorithm } from '../lib/jws_algorithms.js';
3
3
  import { isObject } from '../lib/type_checks.js';
4
4
  import { JWSInvalid } from '../util/errors.js';
5
+ import { normalizeJwk } from '../lib/jwk_metadata.js';
5
6
  export async function EmbeddedJWK(protectedHeader, token) {
6
7
  const joseHeader = {
7
8
  ...protectedHeader,
@@ -10,8 +11,21 @@ export async function EmbeddedJWK(protectedHeader, token) {
10
11
  if (!isObject(joseHeader.jwk)) {
11
12
  throw new JWSInvalid('"jwk" (JSON Web Key) Header Parameter must be a JSON object');
12
13
  }
14
+ let jwk;
15
+ try {
16
+ jwk = normalizeJwk(joseHeader.jwk);
17
+ }
18
+ catch (cause) {
19
+ throw new JWSInvalid('Invalid Embedded JWK', { cause });
20
+ }
13
21
  const entry = jwsAlgorithm(joseHeader.alg);
14
- const key = await jwkToKey(entry, { ...joseHeader.jwk, ext: true });
22
+ if (jwk.use !== undefined && jwk.use !== 'sig') {
23
+ throw new JWSInvalid('Invalid Embedded JWK, its "use" must be "sig" when present');
24
+ }
25
+ if (jwk.alg !== undefined && jwk.alg !== entry.alg) {
26
+ throw new JWSInvalid(`Invalid Embedded JWK, its "alg" must be "${entry.alg}" when present`);
27
+ }
28
+ const key = await jwkToKey(entry, { ...jwk, ext: true });
15
29
  if (key.type !== 'public') {
16
30
  throw new JWSInvalid('"jwk" (JSON Web Key) Header Parameter must be a public key');
17
31
  }
@@ -3,9 +3,10 @@ import { encode as b64u } from '../util/base64url.js';
3
3
  import { JOSENotSupported, JWKInvalid } from '../util/errors.js';
4
4
  import { encode } from '../lib/buffer_utils.js';
5
5
  import { isKeyLike } from '../lib/is_key_like.js';
6
- import { isJWK } from '../lib/type_checks.js';
6
+ import { isObject } from '../lib/type_checks.js';
7
7
  import { exportJWK } from '../key/export.js';
8
8
  import { invalidKeyInput } from '../lib/invalid_key_input.js';
9
+ import { snapshotJwk } from '../lib/jwk_metadata.js';
9
10
  const check = (value, description) => {
10
11
  if (typeof value !== 'string' || !value) {
11
12
  throw new JWKInvalid(`${description} missing or invalid`);
@@ -13,11 +14,14 @@ const check = (value, description) => {
13
14
  };
14
15
  export async function calculateJwkThumbprint(key, digestAlgorithm) {
15
16
  let jwk;
16
- if (isJWK(key)) {
17
- jwk = key;
17
+ if (isObject(key)) {
18
+ jwk = snapshotJwk(key);
19
+ if (typeof jwk.kty !== 'string') {
20
+ throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
21
+ }
18
22
  }
19
23
  else if (isKeyLike(key)) {
20
- jwk = await exportJWK(key);
24
+ jwk = snapshotJwk(await exportJWK(key));
21
25
  }
22
26
  else {
23
27
  throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
@@ -52,7 +56,9 @@ export async function calculateJwkThumbprint(key, digestAlgorithm) {
52
56
  components = { e: jwk.e, kty: jwk.kty, n: jwk.n };
53
57
  break;
54
58
  case 'oct':
55
- check(jwk.k, '"k" (Key Value) Parameter');
59
+ if (typeof jwk.k !== 'string') {
60
+ throw new JWKInvalid('"k" (Key Value) Parameter missing or invalid');
61
+ }
56
62
  components = { k: jwk.k, kty: jwk.kty };
57
63
  break;
58
64
  default:
@@ -1,78 +1,70 @@
1
1
  import { jwkToKey } from '../lib/jwk_to_key.js';
2
2
  import { JWS } from '../lib/jws_algorithms.js';
3
3
  import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
4
- import { isObject } from '../lib/type_checks.js';
5
- function signatureAlgorithm(alg) {
6
- const entry = typeof alg === 'string' ? JWS[alg] : undefined;
7
- if (!entry || entry.secret) {
8
- throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
9
- }
10
- return entry;
4
+ import { isJwkSet } from '../lib/type_checks.js';
5
+ import { snapshotJwk } from '../lib/jwk_metadata.js';
6
+ function isUsableJWK(jwk, entry, alg, kid) {
7
+ const { kty, key_ops, ext, kid: jwkKid, alg: jwkAlg, use, crv } = snapshotJwk(jwk);
8
+ const keyOps = Array.isArray(key_ops) ? [...key_ops] : key_ops;
9
+ return ((ext === undefined || typeof ext === 'boolean') &&
10
+ (keyOps === undefined ||
11
+ (Array.isArray(keyOps) &&
12
+ keyOps.every((operation, index) => typeof operation === 'string' && keyOps.indexOf(operation) === index) &&
13
+ keyOps.includes('verify'))) &&
14
+ entry.kty.includes(kty) &&
15
+ (kid === undefined || (typeof kid === 'string' && kid === jwkKid)) &&
16
+ (jwkAlg === undefined ? kty !== 'AKP' : alg === jwkAlg) &&
17
+ (use === undefined || use === 'sig') &&
18
+ (!entry.crv || crv === entry.crv));
11
19
  }
12
- function isJWKSLike(jwks) {
13
- if (!jwks || typeof jwks !== 'object') {
14
- return false;
20
+ async function importWithAlgCache(cache, jwk, entry) {
21
+ const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
22
+ const { alg } = entry;
23
+ if (cached[alg] === undefined) {
24
+ const key = await jwkToKey(entry, { ...jwk, alg, ext: true });
25
+ if (key.type !== 'public') {
26
+ throw new JWKSInvalid('JSON Web Key Set members must be public keys');
27
+ }
28
+ cached[alg] = key;
15
29
  }
16
- const { keys } = jwks;
17
- return Array.isArray(keys) && keys.every((isObject));
30
+ return cached[alg];
18
31
  }
19
- class LocalJWKSetImpl {
20
- #jwks;
21
- #cached = new WeakMap();
22
- constructor(jwks) {
23
- if (!isJWKSLike(jwks)) {
24
- throw new JWKSInvalid('JSON Web Key Set malformed');
25
- }
26
- this.#jwks = structuredClone(jwks);
32
+ export function createLocalJWKSet(jwks) {
33
+ let snapshot;
34
+ try {
35
+ snapshot = structuredClone(jwks);
27
36
  }
28
- jwks() {
29
- return this.#jwks;
37
+ catch { }
38
+ if (!isJwkSet(snapshot)) {
39
+ throw new JWKSInvalid('JSON Web Key Set malformed');
30
40
  }
31
- async getKey(protectedHeader, token) {
41
+ const cached = new WeakMap();
42
+ const localJWKSet = async (protectedHeader, token) => {
32
43
  const { alg, kid } = { ...protectedHeader, ...token?.header };
33
- const entry = signatureAlgorithm(alg);
34
- const candidates = this.#jwks.keys.filter((jwk) => entry.kty.includes(jwk.kty) &&
35
- (typeof kid !== 'string' || kid === jwk.kid) &&
36
- (!(typeof jwk.alg === 'string' || jwk.kty === 'AKP') || alg === jwk.alg) &&
37
- (typeof jwk.use !== 'string' || jwk.use === 'sig') &&
38
- (!Array.isArray(jwk.key_ops) || jwk.key_ops.includes('verify')) &&
39
- (!entry.crv || jwk.crv === entry.crv));
44
+ const entry = typeof alg === 'string' ? JWS[alg] : undefined;
45
+ if (!entry || entry.secret) {
46
+ throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
47
+ }
48
+ const candidates = snapshot.keys.filter((jwk) => isUsableJWK(jwk, entry, alg, kid));
40
49
  const { 0: jwk, length } = candidates;
41
- if (length === 0) {
50
+ if (!length) {
42
51
  throw new JWKSNoMatchingKey();
43
52
  }
44
53
  if (length !== 1) {
45
54
  const error = new JWKSMultipleMatchingKeys();
46
- const _cached = this.#cached;
47
55
  error[Symbol.asyncIterator] = async function* () {
48
56
  for (const jwk of candidates) {
49
57
  try {
50
- yield await importWithAlgCache(_cached, jwk, entry);
58
+ yield await importWithAlgCache(cached, jwk, entry);
51
59
  }
52
60
  catch { }
53
61
  }
54
62
  };
55
63
  throw error;
56
64
  }
57
- return importWithAlgCache(this.#cached, jwk, entry);
58
- }
59
- }
60
- async function importWithAlgCache(cache, jwk, entry) {
61
- const cached = cache.get(jwk) || cache.set(jwk, { __proto__: null }).get(jwk);
62
- if (cached[entry.alg] === undefined) {
63
- const key = await jwkToKey(entry, { ...jwk, alg: entry.alg, ext: true });
64
- if (key.type !== 'public') {
65
- throw new JWKSInvalid('JSON Web Key Set members must be public keys');
66
- }
67
- cached[entry.alg] = key;
68
- }
69
- return cached[entry.alg];
70
- }
71
- export function createLocalJWKSet(jwks) {
72
- const set = new LocalJWKSetImpl(jwks);
73
- const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
74
- Object.defineProperty(localJWKSet, 'jwks', {
75
- value: () => structuredClone(set.jwks()),
65
+ return importWithAlgCache(cached, jwk, entry);
66
+ };
67
+ return Object.defineProperty(localJWKSet, 'jwks', {
68
+ value: () => structuredClone(snapshot),
76
69
  });
77
- return localJWKSet;
78
70
  }
@@ -1,6 +1,6 @@
1
1
  import { JOSEError, JWKSNoMatchingKey, JWKSTimeout } from '../util/errors.js';
2
2
  import { createLocalJWKSet } from './local.js';
3
- import { isObject } from '../lib/type_checks.js';
3
+ import { isJwkSet } from '../lib/type_checks.js';
4
4
  function isCloudflareWorkers() {
5
5
  return (typeof WebSocketPair !== 'undefined' ||
6
6
  (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') ||
@@ -9,7 +9,7 @@ function isCloudflareWorkers() {
9
9
  let USER_AGENT;
10
10
  if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
11
11
  const NAME = 'jose';
12
- const VERSION = 'v6.2.9';
12
+ const VERSION = 'v6.2.10';
13
13
  USER_AGENT = `${NAME}/${VERSION}`;
14
14
  }
15
15
  export const customFetch = Symbol();
@@ -36,135 +36,114 @@ async function fetchJwks(url, headers, signal, fetchImpl = fetch) {
36
36
  }
37
37
  }
38
38
  export const jwksCache = Symbol();
39
- function isFreshJwksCache(input, cacheMaxAge) {
40
- if (typeof input !== 'object' || input === null) {
41
- return false;
42
- }
43
- if (!('uat' in input) || typeof input.uat !== 'number' || Date.now() - input.uat >= cacheMaxAge) {
44
- return false;
45
- }
46
- if (!('jwks' in input) ||
47
- !isObject(input.jwks) ||
48
- !Array.isArray(input.jwks.keys) ||
49
- !Array.prototype.every.call(input.jwks.keys, isObject)) {
50
- return false;
51
- }
52
- return true;
39
+ function isFreshFor(timestamp, duration) {
40
+ return Number.isFinite(timestamp) && Date.now() < timestamp + duration;
53
41
  }
54
- class RemoteJWKSetImpl {
55
- #url;
56
- #timeoutDuration;
57
- #cooldownDuration;
58
- #cacheMaxAge;
59
- #jwksTimestamp;
60
- #pendingFetch;
61
- #headers;
62
- #customFetch;
63
- #local;
64
- #cache;
65
- constructor(url, options) {
66
- if (!(url instanceof URL)) {
67
- throw new TypeError('url must be an instance of URL');
68
- }
69
- this.#url = new URL(url.href);
70
- const opts = options ?? {};
71
- this.#timeoutDuration = typeof opts.timeoutDuration === 'number' ? opts.timeoutDuration : 5000;
72
- this.#cooldownDuration =
73
- typeof opts.cooldownDuration === 'number' ? opts.cooldownDuration : 30000;
74
- this.#cacheMaxAge = typeof opts.cacheMaxAge === 'number' ? opts.cacheMaxAge : 600000;
75
- this.#headers = new Headers(opts.headers);
76
- if (USER_AGENT && !this.#headers.has('User-Agent')) {
77
- this.#headers.set('User-Agent', USER_AGENT);
78
- }
79
- if (!this.#headers.has('accept')) {
80
- this.#headers.set('accept', 'application/json');
81
- this.#headers.append('accept', 'application/jwk-set+json');
82
- }
83
- this.#customFetch = opts[customFetch];
84
- const cache = opts[jwksCache];
85
- if (cache !== undefined) {
86
- this.#cache = cache;
87
- if (isFreshJwksCache(cache, this.#cacheMaxAge)) {
88
- this.#jwksTimestamp = this.#cache.uat;
89
- this.#local = createLocalJWKSet(this.#cache.jwks);
90
- }
91
- }
42
+ function validateDuration(value, fallback, option) {
43
+ if (Number.isNaN(value)) {
44
+ throw new TypeError(`"${option}" option must not be NaN`);
92
45
  }
93
- pendingFetch() {
94
- return !!this.#pendingFetch;
46
+ return typeof value === 'number' ? value : fallback;
47
+ }
48
+ export function createRemoteJWKSet(url, options) {
49
+ if (!(url instanceof URL)) {
50
+ throw new TypeError('url must be an instance of URL');
95
51
  }
96
- #validFor(duration) {
97
- return typeof this.#jwksTimestamp === 'number' && Date.now() < this.#jwksTimestamp + duration;
52
+ const href = new URL(url.href).href;
53
+ const opts = options ?? {};
54
+ const timeoutOption = opts.timeoutDuration;
55
+ if (typeof timeoutOption === 'number' &&
56
+ (!Number.isInteger(timeoutOption) || timeoutOption < 0)) {
57
+ throw new TypeError('"timeoutDuration" option must be a non-negative integer');
98
58
  }
99
- coolingDown() {
100
- return this.#validFor(this.#cooldownDuration);
59
+ const timeoutDuration = typeof timeoutOption === 'number' ? timeoutOption : 5000;
60
+ const cooldownDuration = validateDuration(opts.cooldownDuration, 30000, 'cooldownDuration');
61
+ const cacheMaxAge = validateDuration(opts.cacheMaxAge, 600000, 'cacheMaxAge');
62
+ const headers = new Headers(opts.headers);
63
+ if (USER_AGENT && !headers.has('User-Agent')) {
64
+ headers.set('User-Agent', USER_AGENT);
101
65
  }
102
- fresh() {
103
- return this.#validFor(this.#cacheMaxAge);
66
+ if (!headers.has('accept')) {
67
+ headers.set('accept', 'application/json, application/jwk-set+json');
104
68
  }
105
- jwks() {
106
- return this.#local?.jwks();
69
+ const fetchImpl = opts[customFetch];
70
+ const cache = opts[jwksCache];
71
+ let jwksTimestamp;
72
+ let pendingFetch;
73
+ let reloadSequence = 0;
74
+ let appliedSequence = 0;
75
+ let local;
76
+ if (cache && typeof cache === 'object') {
77
+ const { uat, jwks } = cache;
78
+ if (isFreshFor(uat, cacheMaxAge) && isJwkSet(jwks)) {
79
+ jwksTimestamp = uat;
80
+ local = createLocalJWKSet(jwks);
81
+ }
107
82
  }
108
- async getKey(protectedHeader, token) {
109
- if (!this.#local || !this.fresh()) {
110
- await this.reload();
83
+ const reload = async () => {
84
+ if (pendingFetch && isCloudflareWorkers()) {
85
+ pendingFetch = undefined;
86
+ }
87
+ if (!pendingFetch) {
88
+ const sequence = ++reloadSequence;
89
+ const current = (pendingFetch = fetchJwks(href, headers, AbortSignal.timeout(timeoutDuration), fetchImpl)
90
+ .then((json) => {
91
+ const next = createLocalJWKSet(json);
92
+ if (sequence <= appliedSequence) {
93
+ return;
94
+ }
95
+ local = next;
96
+ const updatedAt = Date.now();
97
+ if (cache) {
98
+ cache.uat = updatedAt;
99
+ cache.jwks = json;
100
+ }
101
+ jwksTimestamp = updatedAt;
102
+ appliedSequence = sequence;
103
+ })
104
+ .finally(() => {
105
+ if (pendingFetch === current) {
106
+ pendingFetch = undefined;
107
+ }
108
+ }));
109
+ }
110
+ await pendingFetch;
111
+ };
112
+ const remoteJWKSet = async (protectedHeader, token) => {
113
+ if (!local || !isFreshFor(jwksTimestamp, cacheMaxAge)) {
114
+ await reload();
111
115
  }
112
116
  try {
113
- return await this.#local(protectedHeader, token);
117
+ return await local(protectedHeader, token);
114
118
  }
115
119
  catch (err) {
116
- if (err instanceof JWKSNoMatchingKey) {
117
- if (this.coolingDown() === false) {
118
- await this.reload();
119
- return this.#local(protectedHeader, token);
120
- }
120
+ if (err instanceof JWKSNoMatchingKey && !isFreshFor(jwksTimestamp, cooldownDuration)) {
121
+ await reload();
122
+ return local(protectedHeader, token);
121
123
  }
122
124
  throw err;
123
125
  }
124
- }
125
- async reload() {
126
- if (this.#pendingFetch && isCloudflareWorkers()) {
127
- this.#pendingFetch = undefined;
128
- }
129
- this.#pendingFetch ||= fetchJwks(this.#url.href, this.#headers, AbortSignal.timeout(this.#timeoutDuration), this.#customFetch)
130
- .then((json) => {
131
- this.#local = createLocalJWKSet(json);
132
- if (this.#cache) {
133
- this.#cache.uat = Date.now();
134
- this.#cache.jwks = json;
135
- }
136
- this.#jwksTimestamp = Date.now();
137
- })
138
- .finally(() => {
139
- this.#pendingFetch = undefined;
140
- });
141
- await this.#pendingFetch;
142
- }
143
- }
144
- export function createRemoteJWKSet(url, options) {
145
- const set = new RemoteJWKSetImpl(url, options);
146
- const remoteJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
147
- Object.defineProperties(remoteJWKSet, {
126
+ };
127
+ return Object.defineProperties(remoteJWKSet, {
148
128
  coolingDown: {
149
- get: () => set.coolingDown(),
129
+ get: () => isFreshFor(jwksTimestamp, cooldownDuration),
150
130
  enumerable: true,
151
131
  },
152
132
  fresh: {
153
- get: () => set.fresh(),
133
+ get: () => isFreshFor(jwksTimestamp, cacheMaxAge),
154
134
  enumerable: true,
155
135
  },
156
136
  reload: {
157
- value: () => set.reload(),
137
+ value: reload,
158
138
  enumerable: true,
159
139
  },
160
140
  reloading: {
161
- get: () => set.pendingFetch(),
141
+ get: () => !!pendingFetch,
162
142
  enumerable: true,
163
143
  },
164
144
  jwks: {
165
- value: () => set.jwks(),
145
+ value: () => local?.jwks(),
166
146
  enumerable: true,
167
147
  },
168
148
  });
169
- return remoteJWKSet;
170
149
  }
@@ -1,21 +1,22 @@
1
- import { FlattenedSign } from '../flattened/sign.js';
2
- import { unencodedPayload } from '../../lib/jws_sign.js';
1
+ import { createCompactSignature } from '../../lib/jws_sign.js';
2
+ import { assertNotSet } from '../../lib/helpers.js';
3
3
  export class CompactSign {
4
- #flattened;
4
+ #payload;
5
5
  #protectedHeader;
6
6
  constructor(payload) {
7
- this.#flattened = new FlattenedSign(payload);
7
+ if (!(payload instanceof Uint8Array)) {
8
+ throw new TypeError('payload must be an instance of Uint8Array');
9
+ }
10
+ this.#payload = payload;
8
11
  }
9
12
  setProtectedHeader(protectedHeader) {
10
- this.#flattened.setProtectedHeader(protectedHeader);
13
+ assertNotSet(this.#protectedHeader, 'setProtectedHeader');
11
14
  this.#protectedHeader = protectedHeader;
12
15
  return this;
13
16
  }
14
17
  async sign(key, options) {
15
- if (unencodedPayload(this.#protectedHeader)) {
18
+ return createCompactSignature(this.#payload, this.#protectedHeader, options?.crit, key, () => {
16
19
  throw new TypeError('use the flattened module for creating JWS with b64: false');
17
- }
18
- const jws = await this.#flattened.sign(key, options);
19
- return `${jws.protected}.${jws.payload}.${jws.signature}`;
20
+ });
20
21
  }
21
22
  }
@@ -21,11 +21,12 @@ export class FlattenedSign {
21
21
  return this;
22
22
  }
23
23
  async sign(key, options) {
24
- return createSignature({
24
+ const [jws] = await createSignature({
25
25
  payload: this.#payload,
26
26
  protectedHeader: this.#protectedHeader,
27
27
  unprotectedHeader: this.#unprotectedHeader,
28
28
  crit: options?.crit,
29
29
  }, key);
30
+ return jws;
30
31
  }
31
32
  }
@@ -1,24 +1,25 @@
1
1
  import { JWSInvalid } from '../../util/errors.js';
2
2
  import { isObject } from '../../lib/type_checks.js';
3
- import { prepareVerify, verifySignature, verifyResult } from '../../lib/jws_verify.js';
3
+ import { encodeJsonUnencodedPayload, prepareVerify, snapshotJws, verifySignature, verifyResult, } from '../../lib/jws_verify.js';
4
4
  export async function flattenedVerify(jws, key, options) {
5
5
  if (!isObject(jws)) {
6
6
  throw new JWSInvalid('Flattened JWS must be an object');
7
7
  }
8
- if (jws.protected === undefined && jws.header === undefined) {
8
+ const snapshot = snapshotJws(jws);
9
+ if (snapshot.protected === undefined && snapshot.header === undefined) {
9
10
  throw new JWSInvalid('Flattened JWS must have either of the "protected" or "header" members');
10
11
  }
11
- if (jws.protected !== undefined && typeof jws.protected !== 'string') {
12
+ if (snapshot.protected !== undefined && typeof snapshot.protected !== 'string') {
12
13
  throw new JWSInvalid('JWS Protected Header incorrect type');
13
14
  }
14
- if (jws.payload === undefined) {
15
+ if (snapshot.payload === undefined) {
15
16
  throw new JWSInvalid('JWS Payload missing');
16
17
  }
17
- if (typeof jws.signature !== 'string') {
18
+ if (typeof snapshot.signature !== 'string') {
18
19
  throw new JWSInvalid('JWS Signature missing or incorrect type');
19
20
  }
20
- if (jws.header !== undefined && !isObject(jws.header)) {
21
+ if (snapshot.header !== undefined && !isObject(snapshot.header)) {
21
22
  throw new JWSInvalid('JWS Unprotected Header incorrect type');
22
23
  }
23
- return verifyResult(jws, await verifySignature(jws, prepareVerify(options), key));
24
+ return verifyResult(snapshot, await verifySignature(snapshot, prepareVerify(options), key, encodeJsonUnencodedPayload));
24
25
  }
@@ -51,20 +51,22 @@ export class GeneralSign {
51
51
  payload: '',
52
52
  };
53
53
  const encoded = [];
54
+ let b64;
54
55
  for (let i = 0; i < this.#signatures.length; i++) {
55
56
  const signature = this.#signatures[i];
56
57
  const [protectedHeader, unprotectedHeader, key, crit] = signature.state;
57
- const { payload, ...rest } = await createSignature({
58
+ const [{ payload, ...rest }, signatureB64] = await createSignature({
58
59
  payload: this.#payload,
59
60
  protectedHeader,
60
61
  unprotectedHeader,
61
62
  crit,
62
63
  encoded,
63
64
  }, key);
64
- if (i === 0) {
65
+ if (b64 === undefined) {
66
+ b64 = signatureB64;
65
67
  jws.payload = payload;
66
68
  }
67
- else if (jws.payload !== payload) {
69
+ else if (b64 !== signatureB64) {
68
70
  throw new JWSInvalid('inconsistent use of JWS Unencoded Payload (RFC7797)');
69
71
  }
70
72
  jws.signatures.push(rest);