@oari/jose 0.0.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.
Files changed (88) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +150 -0
  3. package/dist/types/index.d.ts +55 -0
  4. package/dist/types/jwe/compact/decrypt.d.ts +43 -0
  5. package/dist/types/jwe/compact/encrypt.d.ts +76 -0
  6. package/dist/types/jwe/flattened/decrypt.d.ts +53 -0
  7. package/dist/types/jwe/flattened/encrypt.d.ts +95 -0
  8. package/dist/types/jwe/general/decrypt.d.ts +64 -0
  9. package/dist/types/jwe/general/encrypt.d.ts +89 -0
  10. package/dist/types/jwk/embedded.d.ts +31 -0
  11. package/dist/types/jwk/thumbprint.d.ts +60 -0
  12. package/dist/types/jwks/local.d.ts +90 -0
  13. package/dist/types/jwks/remote.d.ts +306 -0
  14. package/dist/types/jws/compact/sign.d.ts +47 -0
  15. package/dist/types/jws/compact/verify.d.ts +45 -0
  16. package/dist/types/jws/flattened/sign.d.ts +53 -0
  17. package/dist/types/jws/flattened/verify.d.ts +50 -0
  18. package/dist/types/jws/general/sign.d.ts +67 -0
  19. package/dist/types/jws/general/verify.d.ts +61 -0
  20. package/dist/types/jwt/decrypt.d.ts +51 -0
  21. package/dist/types/jwt/encrypt.d.ts +105 -0
  22. package/dist/types/jwt/sign.d.ts +140 -0
  23. package/dist/types/jwt/unsecured.d.ts +70 -0
  24. package/dist/types/jwt/verify.d.ts +124 -0
  25. package/dist/types/key/export.d.ts +59 -0
  26. package/dist/types/key/generate_key_pair.d.ts +64 -0
  27. package/dist/types/key/generate_secret.d.ts +42 -0
  28. package/dist/types/key/import.d.ts +146 -0
  29. package/dist/types/types.d.ts +869 -0
  30. package/dist/types/util/base64url.d.ts +9 -0
  31. package/dist/types/util/decode_jwt.d.ts +25 -0
  32. package/dist/types/util/decode_protected_header.d.ts +24 -0
  33. package/dist/types/util/errors.d.ts +488 -0
  34. package/dist/webapi/index.js +32 -0
  35. package/dist/webapi/jwe/compact/decrypt.js +27 -0
  36. package/dist/webapi/jwe/compact/encrypt.js +27 -0
  37. package/dist/webapi/jwe/flattened/decrypt.js +159 -0
  38. package/dist/webapi/jwe/flattened/encrypt.js +167 -0
  39. package/dist/webapi/jwe/general/decrypt.js +31 -0
  40. package/dist/webapi/jwe/general/encrypt.js +182 -0
  41. package/dist/webapi/jwk/embedded.js +17 -0
  42. package/dist/webapi/jwk/thumbprint.js +68 -0
  43. package/dist/webapi/jwks/local.js +119 -0
  44. package/dist/webapi/jwks/remote.js +179 -0
  45. package/dist/webapi/jws/compact/sign.js +18 -0
  46. package/dist/webapi/jws/compact/verify.js +21 -0
  47. package/dist/webapi/jws/flattened/sign.js +87 -0
  48. package/dist/webapi/jws/flattened/verify.js +110 -0
  49. package/dist/webapi/jws/general/sign.js +70 -0
  50. package/dist/webapi/jws/general/verify.js +24 -0
  51. package/dist/webapi/jwt/decrypt.js +23 -0
  52. package/dist/webapi/jwt/encrypt.js +101 -0
  53. package/dist/webapi/jwt/sign.js +52 -0
  54. package/dist/webapi/jwt/unsecured.js +63 -0
  55. package/dist/webapi/jwt/verify.js +15 -0
  56. package/dist/webapi/key/export.js +11 -0
  57. package/dist/webapi/key/generate_key_pair.js +97 -0
  58. package/dist/webapi/key/generate_secret.js +40 -0
  59. package/dist/webapi/key/import.js +57 -0
  60. package/dist/webapi/lib/aesgcmkw.js +15 -0
  61. package/dist/webapi/lib/aeskw.js +25 -0
  62. package/dist/webapi/lib/asn1.js +243 -0
  63. package/dist/webapi/lib/base64.js +22 -0
  64. package/dist/webapi/lib/buffer_utils.js +43 -0
  65. package/dist/webapi/lib/check_key_type.js +127 -0
  66. package/dist/webapi/lib/content_encryption.js +217 -0
  67. package/dist/webapi/lib/crypto_key.js +136 -0
  68. package/dist/webapi/lib/deflate.js +44 -0
  69. package/dist/webapi/lib/ecdhes.js +52 -0
  70. package/dist/webapi/lib/helpers.js +19 -0
  71. package/dist/webapi/lib/invalid_key_input.js +27 -0
  72. package/dist/webapi/lib/is_key_like.js +17 -0
  73. package/dist/webapi/lib/jwk_to_key.js +107 -0
  74. package/dist/webapi/lib/jwt_claims_set.js +238 -0
  75. package/dist/webapi/lib/key_management.js +186 -0
  76. package/dist/webapi/lib/key_to_jwk.js +31 -0
  77. package/dist/webapi/lib/normalize_key.js +166 -0
  78. package/dist/webapi/lib/pbes2kw.js +42 -0
  79. package/dist/webapi/lib/rsaes.js +24 -0
  80. package/dist/webapi/lib/signing.js +74 -0
  81. package/dist/webapi/lib/type_checks.js +41 -0
  82. package/dist/webapi/lib/validate_algorithms.js +10 -0
  83. package/dist/webapi/lib/validate_crit.js +33 -0
  84. package/dist/webapi/util/base64url.js +30 -0
  85. package/dist/webapi/util/decode_jwt.js +32 -0
  86. package/dist/webapi/util/decode_protected_header.js +34 -0
  87. package/dist/webapi/util/errors.js +99 -0
  88. package/package.json +195 -0
@@ -0,0 +1,136 @@
1
+ const unusable = (name, prop = 'algorithm.name') => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
2
+ const isAlgorithm = (algorithm, name) => algorithm.name === name;
3
+ function getHashLength(hash) {
4
+ return parseInt(hash.name.slice(4), 10);
5
+ }
6
+ function checkHashLength(algorithm, expected) {
7
+ const actual = getHashLength(algorithm.hash);
8
+ if (actual !== expected)
9
+ throw unusable(`SHA-${expected}`, 'algorithm.hash');
10
+ }
11
+ function getNamedCurve(alg) {
12
+ switch (alg) {
13
+ case 'ES256':
14
+ return 'P-256';
15
+ case 'ES384':
16
+ return 'P-384';
17
+ case 'ES512':
18
+ return 'P-521';
19
+ default:
20
+ throw new Error('unreachable');
21
+ }
22
+ }
23
+ function checkUsage(key, usage) {
24
+ if (usage && !key.usages.includes(usage)) {
25
+ throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
26
+ }
27
+ }
28
+ export function checkSigCryptoKey(key, alg, usage) {
29
+ switch (alg) {
30
+ case 'HS256':
31
+ case 'HS384':
32
+ case 'HS512': {
33
+ if (!isAlgorithm(key.algorithm, 'HMAC'))
34
+ throw unusable('HMAC');
35
+ checkHashLength(key.algorithm, parseInt(alg.slice(2), 10));
36
+ break;
37
+ }
38
+ case 'RS256':
39
+ case 'RS384':
40
+ case 'RS512': {
41
+ if (!isAlgorithm(key.algorithm, 'RSASSA-PKCS1-v1_5'))
42
+ throw unusable('RSASSA-PKCS1-v1_5');
43
+ checkHashLength(key.algorithm, parseInt(alg.slice(2), 10));
44
+ break;
45
+ }
46
+ case 'PS256':
47
+ case 'PS384':
48
+ case 'PS512': {
49
+ if (!isAlgorithm(key.algorithm, 'RSA-PSS'))
50
+ throw unusable('RSA-PSS');
51
+ checkHashLength(key.algorithm, parseInt(alg.slice(2), 10));
52
+ break;
53
+ }
54
+ case 'Ed25519':
55
+ case 'EdDSA': {
56
+ if (!isAlgorithm(key.algorithm, 'Ed25519'))
57
+ throw unusable('Ed25519');
58
+ break;
59
+ }
60
+ case 'ML-DSA-44':
61
+ case 'ML-DSA-65':
62
+ case 'ML-DSA-87': {
63
+ if (!isAlgorithm(key.algorithm, alg))
64
+ throw unusable(alg);
65
+ break;
66
+ }
67
+ case 'ES256':
68
+ case 'ES384':
69
+ case 'ES512': {
70
+ if (!isAlgorithm(key.algorithm, 'ECDSA'))
71
+ throw unusable('ECDSA');
72
+ const expected = getNamedCurve(alg);
73
+ const actual = key.algorithm.namedCurve;
74
+ if (actual !== expected)
75
+ throw unusable(expected, 'algorithm.namedCurve');
76
+ break;
77
+ }
78
+ default:
79
+ throw new TypeError('CryptoKey does not support this operation');
80
+ }
81
+ checkUsage(key, usage);
82
+ }
83
+ export function checkEncCryptoKey(key, alg, usage) {
84
+ switch (alg) {
85
+ case 'A128GCM':
86
+ case 'A192GCM':
87
+ case 'A256GCM': {
88
+ if (!isAlgorithm(key.algorithm, 'AES-GCM'))
89
+ throw unusable('AES-GCM');
90
+ const expected = parseInt(alg.slice(1, 4), 10);
91
+ const actual = key.algorithm.length;
92
+ if (actual !== expected)
93
+ throw unusable(expected, 'algorithm.length');
94
+ break;
95
+ }
96
+ case 'A128KW':
97
+ case 'A192KW':
98
+ case 'A256KW': {
99
+ if (!isAlgorithm(key.algorithm, 'AES-KW'))
100
+ throw unusable('AES-KW');
101
+ const expected = parseInt(alg.slice(1, 4), 10);
102
+ const actual = key.algorithm.length;
103
+ if (actual !== expected)
104
+ throw unusable(expected, 'algorithm.length');
105
+ break;
106
+ }
107
+ case 'ECDH': {
108
+ switch (key.algorithm.name) {
109
+ case 'ECDH':
110
+ case 'X25519':
111
+ break;
112
+ default:
113
+ throw unusable('ECDH or X25519');
114
+ }
115
+ break;
116
+ }
117
+ case 'PBES2-HS256+A128KW':
118
+ case 'PBES2-HS384+A192KW':
119
+ case 'PBES2-HS512+A256KW':
120
+ if (!isAlgorithm(key.algorithm, 'PBKDF2'))
121
+ throw unusable('PBKDF2');
122
+ break;
123
+ case 'RSA-OAEP':
124
+ case 'RSA-OAEP-256':
125
+ case 'RSA-OAEP-384':
126
+ case 'RSA-OAEP-512': {
127
+ if (!isAlgorithm(key.algorithm, 'RSA-OAEP'))
128
+ throw unusable('RSA-OAEP');
129
+ checkHashLength(key.algorithm, parseInt(alg.slice(9), 10) || 1);
130
+ break;
131
+ }
132
+ default:
133
+ throw new TypeError('CryptoKey does not support this operation');
134
+ }
135
+ checkUsage(key, usage);
136
+ }
@@ -0,0 +1,44 @@
1
+ import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
2
+ import { concat } from './buffer_utils.js';
3
+ function supported(name) {
4
+ if (typeof globalThis[name] === 'undefined') {
5
+ throw new JOSENotSupported(`JWE "zip" (Compression Algorithm) Header Parameter requires the ${name} API.`);
6
+ }
7
+ }
8
+ export async function compress(input) {
9
+ supported('CompressionStream');
10
+ const cs = new CompressionStream('deflate-raw');
11
+ const writer = cs.writable.getWriter();
12
+ writer.write(input).catch(() => { });
13
+ writer.close().catch(() => { });
14
+ const chunks = [];
15
+ const reader = cs.readable.getReader();
16
+ for (;;) {
17
+ const { value, done } = await reader.read();
18
+ if (done)
19
+ break;
20
+ chunks.push(value);
21
+ }
22
+ return concat(...chunks);
23
+ }
24
+ export async function decompress(input, maxLength) {
25
+ supported('DecompressionStream');
26
+ const ds = new DecompressionStream('deflate-raw');
27
+ const writer = ds.writable.getWriter();
28
+ writer.write(input).catch(() => { });
29
+ writer.close().catch(() => { });
30
+ const chunks = [];
31
+ let length = 0;
32
+ const reader = ds.readable.getReader();
33
+ for (;;) {
34
+ const { value, done } = await reader.read();
35
+ if (done)
36
+ break;
37
+ chunks.push(value);
38
+ length += value.byteLength;
39
+ if (maxLength !== Infinity && length > maxLength) {
40
+ throw new JWEInvalid('Decompressed plaintext exceeded the configured limit');
41
+ }
42
+ }
43
+ return concat(...chunks);
44
+ }
@@ -0,0 +1,52 @@
1
+ import { encode, concat, uint32be } from './buffer_utils.js';
2
+ import { checkEncCryptoKey } from './crypto_key.js';
3
+ import { digest } from './helpers.js';
4
+ function lengthAndInput(input) {
5
+ return concat(uint32be(input.length), input);
6
+ }
7
+ async function concatKdf(Z, L, OtherInfo) {
8
+ const dkLen = L >> 3;
9
+ const hashLen = 32;
10
+ const reps = Math.ceil(dkLen / hashLen);
11
+ const dk = new Uint8Array(reps * hashLen);
12
+ for (let i = 1; i <= reps; i++) {
13
+ const hashInput = new Uint8Array(4 + Z.length + OtherInfo.length);
14
+ hashInput.set(uint32be(i), 0);
15
+ hashInput.set(Z, 4);
16
+ hashInput.set(OtherInfo, 4 + Z.length);
17
+ const hashResult = await digest('sha256', hashInput);
18
+ dk.set(hashResult, (i - 1) * hashLen);
19
+ }
20
+ return dk.slice(0, dkLen);
21
+ }
22
+ export async function deriveKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(), apv = new Uint8Array()) {
23
+ checkEncCryptoKey(publicKey, 'ECDH');
24
+ checkEncCryptoKey(privateKey, 'ECDH', 'deriveBits');
25
+ const algorithmID = lengthAndInput(encode(algorithm));
26
+ const partyUInfo = lengthAndInput(apu);
27
+ const partyVInfo = lengthAndInput(apv);
28
+ const suppPubInfo = uint32be(keyLength);
29
+ const suppPrivInfo = new Uint8Array();
30
+ const otherInfo = concat(algorithmID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
31
+ const Z = new Uint8Array(await crypto.subtle.deriveBits({
32
+ name: publicKey.algorithm.name,
33
+ public: publicKey,
34
+ }, privateKey, getEcdhBitLength(publicKey)));
35
+ return concatKdf(Z, keyLength, otherInfo);
36
+ }
37
+ function getEcdhBitLength(publicKey) {
38
+ if (publicKey.algorithm.name === 'X25519') {
39
+ return 256;
40
+ }
41
+ return (Math.ceil(parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3);
42
+ }
43
+ export function allowed(key) {
44
+ switch (key.algorithm.namedCurve) {
45
+ case 'P-256':
46
+ case 'P-384':
47
+ case 'P-521':
48
+ return true;
49
+ default:
50
+ return key.algorithm.name === 'X25519';
51
+ }
52
+ }
@@ -0,0 +1,19 @@
1
+ import { decode } from '../util/base64url.js';
2
+ export const unprotected = Symbol();
3
+ export function assertNotSet(value, name) {
4
+ if (value) {
5
+ throw new TypeError(`${name} can only be called once`);
6
+ }
7
+ }
8
+ export function decodeBase64url(value, label, ErrorClass) {
9
+ try {
10
+ return decode(value);
11
+ }
12
+ catch {
13
+ throw new ErrorClass(`Failed to base64url decode the ${label}`);
14
+ }
15
+ }
16
+ export async function digest(algorithm, data) {
17
+ const subtleDigest = `SHA-${algorithm.slice(-3)}`;
18
+ return new Uint8Array(await crypto.subtle.digest(subtleDigest, data));
19
+ }
@@ -0,0 +1,27 @@
1
+ function message(msg, actual, ...types) {
2
+ types = types.filter(Boolean);
3
+ if (types.length > 2) {
4
+ const last = types.pop();
5
+ msg += `one of type ${types.join(', ')}, or ${last}.`;
6
+ }
7
+ else if (types.length === 2) {
8
+ msg += `one of type ${types[0]} or ${types[1]}.`;
9
+ }
10
+ else {
11
+ msg += `of type ${types[0]}.`;
12
+ }
13
+ if (actual == null) {
14
+ msg += ` Received ${actual}`;
15
+ }
16
+ else if (typeof actual === 'function' && actual.name) {
17
+ msg += ` Received function ${actual.name}`;
18
+ }
19
+ else if (typeof actual === 'object' && actual != null) {
20
+ if (actual.constructor?.name) {
21
+ msg += ` Received an instance of ${actual.constructor.name}`;
22
+ }
23
+ }
24
+ return msg;
25
+ }
26
+ export const invalidKeyInput = (actual, ...types) => message('Key must be ', actual, ...types);
27
+ export const withAlg = (alg, actual, ...types) => message(`Key for the ${alg} algorithm must be `, actual, ...types);
@@ -0,0 +1,17 @@
1
+ export function assertCryptoKey(key) {
2
+ if (!isCryptoKey(key)) {
3
+ throw new Error('CryptoKey instance expected');
4
+ }
5
+ }
6
+ export const isCryptoKey = (key) => {
7
+ if (key?.[Symbol.toStringTag] === 'CryptoKey')
8
+ return true;
9
+ try {
10
+ return key instanceof CryptoKey;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ };
16
+ export const isKeyObject = (key) => key?.[Symbol.toStringTag] === 'KeyObject';
17
+ export const isKeyLike = (key) => isCryptoKey(key) || isKeyObject(key);
@@ -0,0 +1,107 @@
1
+ import { JOSENotSupported } from '../util/errors.js';
2
+ const unsupportedAlg = 'Invalid or unsupported JWK "alg" (Algorithm) Parameter value';
3
+ function subtleMapping(jwk) {
4
+ let algorithm;
5
+ let keyUsages;
6
+ switch (jwk.kty) {
7
+ case 'AKP': {
8
+ switch (jwk.alg) {
9
+ case 'ML-DSA-44':
10
+ case 'ML-DSA-65':
11
+ case 'ML-DSA-87':
12
+ algorithm = { name: jwk.alg };
13
+ keyUsages = jwk.priv ? ['sign'] : ['verify'];
14
+ break;
15
+ default:
16
+ throw new JOSENotSupported(unsupportedAlg);
17
+ }
18
+ break;
19
+ }
20
+ case 'RSA': {
21
+ switch (jwk.alg) {
22
+ case 'PS256':
23
+ case 'PS384':
24
+ case 'PS512':
25
+ algorithm = { name: 'RSA-PSS', hash: `SHA-${jwk.alg.slice(-3)}` };
26
+ keyUsages = jwk.d ? ['sign'] : ['verify'];
27
+ break;
28
+ case 'RS256':
29
+ case 'RS384':
30
+ case 'RS512':
31
+ algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${jwk.alg.slice(-3)}` };
32
+ keyUsages = jwk.d ? ['sign'] : ['verify'];
33
+ break;
34
+ case 'RSA-OAEP':
35
+ case 'RSA-OAEP-256':
36
+ case 'RSA-OAEP-384':
37
+ case 'RSA-OAEP-512':
38
+ algorithm = {
39
+ name: 'RSA-OAEP',
40
+ hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}`,
41
+ };
42
+ keyUsages = jwk.d ? ['decrypt', 'unwrapKey'] : ['encrypt', 'wrapKey'];
43
+ break;
44
+ default:
45
+ throw new JOSENotSupported(unsupportedAlg);
46
+ }
47
+ break;
48
+ }
49
+ case 'EC': {
50
+ switch (jwk.alg) {
51
+ case 'ES256':
52
+ case 'ES384':
53
+ case 'ES512':
54
+ algorithm = {
55
+ name: 'ECDSA',
56
+ namedCurve: { ES256: 'P-256', ES384: 'P-384', ES512: 'P-521' }[jwk.alg],
57
+ };
58
+ keyUsages = jwk.d ? ['sign'] : ['verify'];
59
+ break;
60
+ case 'ECDH-ES':
61
+ case 'ECDH-ES+A128KW':
62
+ case 'ECDH-ES+A192KW':
63
+ case 'ECDH-ES+A256KW':
64
+ algorithm = { name: 'ECDH', namedCurve: jwk.crv };
65
+ keyUsages = jwk.d ? ['deriveBits'] : [];
66
+ break;
67
+ default:
68
+ throw new JOSENotSupported(unsupportedAlg);
69
+ }
70
+ break;
71
+ }
72
+ case 'OKP': {
73
+ switch (jwk.alg) {
74
+ case 'Ed25519':
75
+ case 'EdDSA':
76
+ algorithm = { name: 'Ed25519' };
77
+ keyUsages = jwk.d ? ['sign'] : ['verify'];
78
+ break;
79
+ case 'ECDH-ES':
80
+ case 'ECDH-ES+A128KW':
81
+ case 'ECDH-ES+A192KW':
82
+ case 'ECDH-ES+A256KW':
83
+ algorithm = { name: jwk.crv };
84
+ keyUsages = jwk.d ? ['deriveBits'] : [];
85
+ break;
86
+ default:
87
+ throw new JOSENotSupported(unsupportedAlg);
88
+ }
89
+ break;
90
+ }
91
+ default:
92
+ throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value');
93
+ }
94
+ return { algorithm, keyUsages };
95
+ }
96
+ export async function jwkToKey(jwk) {
97
+ if (!jwk.alg) {
98
+ throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
99
+ }
100
+ const { algorithm, keyUsages } = subtleMapping(jwk);
101
+ const keyData = { ...jwk };
102
+ if (keyData.kty !== 'AKP') {
103
+ delete keyData.alg;
104
+ }
105
+ delete keyData.use;
106
+ return crypto.subtle.importKey('jwk', keyData, algorithm, jwk.ext ?? (jwk.d || jwk.priv ? false : true), jwk.key_ops ?? keyUsages);
107
+ }
@@ -0,0 +1,238 @@
1
+ import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from '../util/errors.js';
2
+ import { encoder, decoder } from './buffer_utils.js';
3
+ import { isObject } from './type_checks.js';
4
+ const epoch = (date) => Math.floor(date.getTime() / 1000);
5
+ const minute = 60;
6
+ const hour = minute * 60;
7
+ const day = hour * 24;
8
+ const week = day * 7;
9
+ const year = day * 365.25;
10
+ const REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;
11
+ export function secs(str) {
12
+ const matched = REGEX.exec(str);
13
+ if (!matched || (matched[4] && matched[1])) {
14
+ throw new TypeError('Invalid time period format');
15
+ }
16
+ const value = parseFloat(matched[2]);
17
+ const unit = matched[3].toLowerCase();
18
+ let numericDate;
19
+ switch (unit) {
20
+ case 'sec':
21
+ case 'secs':
22
+ case 'second':
23
+ case 'seconds':
24
+ case 's':
25
+ numericDate = Math.round(value);
26
+ break;
27
+ case 'minute':
28
+ case 'minutes':
29
+ case 'min':
30
+ case 'mins':
31
+ case 'm':
32
+ numericDate = Math.round(value * minute);
33
+ break;
34
+ case 'hour':
35
+ case 'hours':
36
+ case 'hr':
37
+ case 'hrs':
38
+ case 'h':
39
+ numericDate = Math.round(value * hour);
40
+ break;
41
+ case 'day':
42
+ case 'days':
43
+ case 'd':
44
+ numericDate = Math.round(value * day);
45
+ break;
46
+ case 'week':
47
+ case 'weeks':
48
+ case 'w':
49
+ numericDate = Math.round(value * week);
50
+ break;
51
+ default:
52
+ numericDate = Math.round(value * year);
53
+ break;
54
+ }
55
+ if (matched[1] === '-' || matched[4] === 'ago') {
56
+ return -numericDate;
57
+ }
58
+ return numericDate;
59
+ }
60
+ function validateInput(label, input) {
61
+ if (!Number.isFinite(input)) {
62
+ throw new TypeError(`Invalid ${label} input`);
63
+ }
64
+ return input;
65
+ }
66
+ const normalizeTyp = (value) => {
67
+ if (value.includes('/')) {
68
+ return value.toLowerCase();
69
+ }
70
+ return `application/${value.toLowerCase()}`;
71
+ };
72
+ const checkAudiencePresence = (audPayload, audOption) => {
73
+ if (typeof audPayload === 'string') {
74
+ return audOption.includes(audPayload);
75
+ }
76
+ if (Array.isArray(audPayload)) {
77
+ return audOption.some(Set.prototype.has.bind(new Set(audPayload)));
78
+ }
79
+ return false;
80
+ };
81
+ export function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
82
+ let payload;
83
+ try {
84
+ payload = JSON.parse(decoder.decode(encodedPayload));
85
+ }
86
+ catch {
87
+ }
88
+ if (!isObject(payload)) {
89
+ throw new JWTInvalid('JWT Claims Set must be a top-level JSON object');
90
+ }
91
+ const { typ } = options;
92
+ if (typ &&
93
+ (typeof protectedHeader.typ !== 'string' ||
94
+ normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) {
95
+ throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, 'typ', 'check_failed');
96
+ }
97
+ const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;
98
+ const presenceCheck = [...requiredClaims];
99
+ if (maxTokenAge !== undefined)
100
+ presenceCheck.push('iat');
101
+ if (audience !== undefined)
102
+ presenceCheck.push('aud');
103
+ if (subject !== undefined)
104
+ presenceCheck.push('sub');
105
+ if (issuer !== undefined)
106
+ presenceCheck.push('iss');
107
+ for (const claim of new Set(presenceCheck.reverse())) {
108
+ if (!(claim in payload)) {
109
+ throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, 'missing');
110
+ }
111
+ }
112
+ if (issuer &&
113
+ !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) {
114
+ throw new JWTClaimValidationFailed('unexpected "iss" claim value', payload, 'iss', 'check_failed');
115
+ }
116
+ if (subject && payload.sub !== subject) {
117
+ throw new JWTClaimValidationFailed('unexpected "sub" claim value', payload, 'sub', 'check_failed');
118
+ }
119
+ if (audience &&
120
+ !checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience)) {
121
+ throw new JWTClaimValidationFailed('unexpected "aud" claim value', payload, 'aud', 'check_failed');
122
+ }
123
+ let tolerance;
124
+ switch (typeof options.clockTolerance) {
125
+ case 'string':
126
+ tolerance = secs(options.clockTolerance);
127
+ break;
128
+ case 'number':
129
+ tolerance = options.clockTolerance;
130
+ break;
131
+ case 'undefined':
132
+ tolerance = 0;
133
+ break;
134
+ default:
135
+ throw new TypeError('Invalid clockTolerance option type');
136
+ }
137
+ const { currentDate } = options;
138
+ const now = epoch(currentDate || new Date());
139
+ if ((payload.iat !== undefined || maxTokenAge) && typeof payload.iat !== 'number') {
140
+ throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, 'iat', 'invalid');
141
+ }
142
+ if (payload.nbf !== undefined) {
143
+ if (typeof payload.nbf !== 'number') {
144
+ throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, 'nbf', 'invalid');
145
+ }
146
+ if (payload.nbf > now + tolerance) {
147
+ throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, 'nbf', 'check_failed');
148
+ }
149
+ }
150
+ if (payload.exp !== undefined) {
151
+ if (typeof payload.exp !== 'number') {
152
+ throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, 'exp', 'invalid');
153
+ }
154
+ if (payload.exp <= now - tolerance) {
155
+ throw new JWTExpired('"exp" claim timestamp check failed', payload, 'exp', 'check_failed');
156
+ }
157
+ }
158
+ if (maxTokenAge) {
159
+ const age = now - payload.iat;
160
+ const max = typeof maxTokenAge === 'number' ? maxTokenAge : secs(maxTokenAge);
161
+ if (age - tolerance > max) {
162
+ throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, 'iat', 'check_failed');
163
+ }
164
+ if (age < 0 - tolerance) {
165
+ throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, 'iat', 'check_failed');
166
+ }
167
+ }
168
+ return payload;
169
+ }
170
+ export class JWTClaimsBuilder {
171
+ #payload;
172
+ constructor(payload) {
173
+ if (!isObject(payload)) {
174
+ throw new TypeError('JWT Claims Set MUST be an object');
175
+ }
176
+ this.#payload = structuredClone(payload);
177
+ }
178
+ data() {
179
+ return encoder.encode(JSON.stringify(this.#payload));
180
+ }
181
+ get iss() {
182
+ return this.#payload.iss;
183
+ }
184
+ set iss(value) {
185
+ this.#payload.iss = value;
186
+ }
187
+ get sub() {
188
+ return this.#payload.sub;
189
+ }
190
+ set sub(value) {
191
+ this.#payload.sub = value;
192
+ }
193
+ get aud() {
194
+ return this.#payload.aud;
195
+ }
196
+ set aud(value) {
197
+ this.#payload.aud = value;
198
+ }
199
+ set jti(value) {
200
+ this.#payload.jti = value;
201
+ }
202
+ set nbf(value) {
203
+ if (typeof value === 'number') {
204
+ this.#payload.nbf = validateInput('setNotBefore', value);
205
+ }
206
+ else if (value instanceof Date) {
207
+ this.#payload.nbf = validateInput('setNotBefore', epoch(value));
208
+ }
209
+ else {
210
+ this.#payload.nbf = epoch(new Date()) + secs(value);
211
+ }
212
+ }
213
+ set exp(value) {
214
+ if (typeof value === 'number') {
215
+ this.#payload.exp = validateInput('setExpirationTime', value);
216
+ }
217
+ else if (value instanceof Date) {
218
+ this.#payload.exp = validateInput('setExpirationTime', epoch(value));
219
+ }
220
+ else {
221
+ this.#payload.exp = epoch(new Date()) + secs(value);
222
+ }
223
+ }
224
+ set iat(value) {
225
+ if (value === undefined) {
226
+ this.#payload.iat = epoch(new Date());
227
+ }
228
+ else if (value instanceof Date) {
229
+ this.#payload.iat = validateInput('setIssuedAt', epoch(value));
230
+ }
231
+ else if (typeof value === 'string') {
232
+ this.#payload.iat = validateInput('setIssuedAt', epoch(new Date()) + secs(value));
233
+ }
234
+ else {
235
+ this.#payload.iat = validateInput('setIssuedAt', value);
236
+ }
237
+ }
238
+ }