From dddd103fb47803f5c5178eb05a5549da03b78c1e Mon Sep 17 00:00:00 2001 From: Radomir Drndarski <90759187+radomird@users.noreply.github.com> Date: Fri, 29 Oct 2021 16:39:15 +0200 Subject: [PATCH] Support passphrase protected keys (#117) * Added password protected keys for testing and updated generator script * Added unit test for the pp key * Updated key type checks in signer * chore: Fixed linting issues * Updated key handling in signer; Updated privateKeyMatcher in crypto; Added tests * Fixed tests for Node v10 * Updated readme with details about the new key type * Removed support for node v10, added v16 and v17 to the CI * Update .github/workflows/ci.yml * Replaced deprecated test methods; Addressed PR comments * Update README.md Co-authored-by: Shogun * Update README.md Co-authored-by: Shogun Co-authored-by: Simone Busoli Co-authored-by: Shogun --- .github/workflows/ci.yml | 2 +- README.md | 18 ++- benchmarks/keys/generate-keys.js | 7 +- benchmarks/keys/pprs-512-private.key | 54 +++++++++ benchmarks/keys/pprs-512-public.key | 14 +++ src/crypto.js | 5 +- src/signer.js | 7 +- test/compatibility.spec.js | 8 +- test/compliance.spec.js | 10 +- test/crypto.spec.js | 32 +++--- test/decoder.spec.js | 4 +- test/signer.spec.js | 67 ++++++++--- test/verifier.spec.js | 164 ++++++++++++++------------- 13 files changed, 262 insertions(+), 130 deletions(-) create mode 100644 benchmarks/keys/pprs-512-private.key create mode 100644 benchmarks/keys/pprs-512-public.key diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87ae784..dc319f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [12, 14, 16, 17] steps: - name: Checkout uses: actions/checkout@v2.3.5 diff --git a/README.md b/README.md index c45313f..75d40e2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ npm install fast-jwt Create a signer function by calling `createSigner` and providing one or more of the following options: -- `key`: A string or a buffer containing the secret for `HS*` algorithms or the PEM encoded public key for `RS*`, `PS*`, `ES*` and `EdDSA` algorithms. The key can also be a function accepting a Node style callback or a function returning a promise. This is the only mandatory option. This is the only mandatory option, which must NOT be provided if the token algorithm is `none`. +- `key`: A string or a buffer containing the secret for `HS*` algorithms, the PEM encoded public key for `RS*`, `PS*`, `ES*` and `EdDSA` algorithms or it can be an object containing passphrase protected private key (more details below). The key can also be a function accepting a Node style callback or a function returning a promise. This is the only mandatory option, which must NOT be provided if the token algorithm is `none`. - `algorithm`: The algorithm to use to sign the token. The default is autodetected from the key, using `RS256` for RSA private keys, `HS256` for plain secrets and the correspondent `ES` or `EdDSA` algorithms for EC or Ed\* private keys. - `mutatePayload`: If the original payload must be modified in place (via `Object.assign`) and thus will result changed to the caller funciton. - `expiresIn`: Time span (in milliseconds) after which the token expires, added as the `exp` claim in the payload. This will override any existing value in the claim. @@ -42,6 +42,14 @@ The payload must be an object. If the `key` option is a function, the signer will also accept a Node style callback and will return a promise, supporting therefore both callback and async/await styles. +If the `key` is a passphrase protected private key, then it must be an object with the following structure: +```js +{ + key: '', + passphrase: '' +} +``` + #### Example ```javascript @@ -66,6 +74,14 @@ async function test() { const token = await signWithPromise({ a: 1, b: 2, c: 3 }) // => eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjoyLCJjIjozLCJpYXQiOjE1Nzk1MjEyMTJ9.mIcxteEVjbh2MnKQ3EQlojZojGSyA_guqRBYHQURcfnCSSBTT2OShF8lo9_ogjAv-5oECgmCur_cDWB7x3X53g } + +// Using password protected private key +const signSync = createSigner({ + key: { + key: '', + passphrase: '' + }) +const token = signSync({ a: 1, b: 2, c: 3 }) ``` ### createDecoder diff --git a/benchmarks/keys/generate-keys.js b/benchmarks/keys/generate-keys.js index 489ad30..0528dfd 100755 --- a/benchmarks/keys/generate-keys.js +++ b/benchmarks/keys/generate-keys.js @@ -4,9 +4,11 @@ const { generateKeyPair } = require('crypto') const { writeFileSync } = require('fs') const { resolve } = require('path') +const passProtectedKeyPassphrase = 'secret' const configurations = { es: { 256: 'prime256v1', 384: 'secp384r1', 512: 'secp521r1' }, rs: { 512: null }, + pprs: { 512: null }, ps: { 512: null }, ed: { 25519: null, 448: null } } @@ -38,6 +40,7 @@ for (const [prefix, configuration] of Object.entries(configurations)) { } } else { for (const [bits, namedCurve] of Object.entries(configuration)) { + const isPasswordProtectedPrivateKey = prefix === 'pprs' let type = 'pkcs8' let format = 'pem' @@ -60,7 +63,9 @@ for (const [prefix, configuration] of Object.entries(configurations)) { }, privateKeyEncoding: { type, - format + format, + cipher: isPasswordProtectedPrivateKey ? 'aes-256-cbc' : undefined, + passphrase: isPasswordProtectedPrivateKey ? passProtectedKeyPassphrase : undefined } }, (err, publicKey, privateKey) => { diff --git a/benchmarks/keys/pprs-512-private.key b/benchmarks/keys/pprs-512-private.key new file mode 100644 index 0000000..58015cc --- /dev/null +++ b/benchmarks/keys/pprs-512-private.key @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/benchmarks/keys/pprs-512-public.key b/benchmarks/keys/pprs-512-public.key new file mode 100644 index 0000000..e3e2b84 --- /dev/null +++ b/benchmarks/keys/pprs-512-public.key @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvOnLWomPr6gAkw/XlfrV +xDR5VKwNawoMv/+TuTMxs3qHgEDcurW2NZZIv1JP66naSQ4YTbqAiKERVWC7UFnp +ufu9I1ZN3DbQcyF39doLl0cy3zEynqbLNqfubRVMGni4mzmOO2XKgYpoObyy8Txm +5RK2jj1iB2OFxsvOT1+F3ig5eInepfWfhEn3wviUR4d5jpS6D/vUdXsN+6XWKiDw +/3BNpi8uTApNMNI6cKGKSV5jO4QELdYytDGN2/wt9GOAd9jKza42rlpcE7UcaSWx +FEg9rFiojdQnOp3bh3SJVkiirVQR28ViNUi7X7rI15KheS6MyhxNIt+A2aeBJ1am +zH5YEhrrzeVYMZ4rvOEDQZ3twjp2uWdoNbxMe2th+wODSV5YLFddbNVrcZOyD2/C +hijVHbjrpLXqFMzjl06d8IS7+uIsTWnwysFHxYFrLA9t4DHECtaq1TuM+nY6NP4m +M0Dqf8KbSdkkBm6wmo6Ly4LsC6t5Sm33RuaPSt96TmghWvRfJiqttBjIoyk37ife +XpuilIp5dAutLMfcYRG3sDopRPJTou1AbqtS9xRzKHgo2a0ikzSNutiu+z2hlgRr +TdzxlaixBfqBNFBj6YZkI+OREnAeg98uoXUcdfCSrU83+BU888GLL/qIU8mMmvln +d8fYnS1P/u66gXdxUCPnIN8CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/src/crypto.js b/src/crypto.js index 61f0722..0302ed3 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -24,7 +24,7 @@ const useNewCrypto = typeof directSign === 'function' const base64UrlMatcher = /[=+/]/g const encoderMap = { '=': '', '+': '-', '/': '_' } -const privateKeyPemMatcher = /^-----BEGIN(?: (RSA|EC))? PRIVATE KEY-----/ +const privateKeyPemMatcher = /^-----BEGIN(?: (RSA|EC|ENCRYPTED))? PRIVATE KEY-----/ const publicKeyPemMatcher = '-----BEGIN PUBLIC KEY-----' const privateKeysCache = new Cache(1000) const publicKeysCache = new Cache(1000) @@ -116,7 +116,8 @@ function performDetectPrivateKeyAlgoritm(key) { let curveId switch (pemData[1]) { - case 'RSA': // pkcs1 format - Can only be a RSA key + case 'RSA': // pkcs1 format - Can only be RSA or an ENCRYPTED (RSA) key + case 'ENCRYPTED': return 'RS256' case 'EC': // sec1 format - Can only be a EC key keyData = ECPrivateKey.decode(key, 'pem', { label: 'EC PRIVATE KEY' }) diff --git a/src/signer.js b/src/signer.js index 4911dac..e0b6ab7 100644 --- a/src/signer.js +++ b/src/signer.js @@ -205,6 +205,7 @@ module.exports = function createSigner(options) { } const keyType = typeof key + const isKeyPasswordProtected = (keyType === 'object') && key && key.key && key.passphrase if (algorithm === 'none') { if (key) { @@ -213,17 +214,17 @@ module.exports = function createSigner(options) { 'The key option must not be provided when the algorithm option is "none".' ) } - } else if (!key || (keyType !== 'string' && !(key instanceof Buffer) && keyType !== 'function')) { + } else if (!key || (keyType !== 'string' && !(key instanceof Buffer) && keyType !== 'function' && !isKeyPasswordProtected)) { throw new TokenError( TokenError.codes.invalidOption, - 'The key option must be a string, a buffer or a function returning the algorithm secret or private key.' + 'The key option must be a string, a buffer, an object containing key/passphrase properties or a function returning the algorithm secret or private key.' ) } // Convert the key to a string when not a function, in order to be able to detect if (key && keyType !== 'function') { // Detect the private key - If the algorithm was known, just verify they match, otherwise assign it - const availableAlgorithm = detectPrivateKeyAlgorithm(key) + const availableAlgorithm = detectPrivateKeyAlgorithm(isKeyPasswordProtected ? key.key : key) if (algorithm) { checkIsCompatibleAlgorithm(algorithm, availableAlgorithm) diff --git a/test/compatibility.spec.js b/test/compatibility.spec.js index 5c66add..d42bb53 100644 --- a/test/compatibility.spec.js +++ b/test/compatibility.spec.js @@ -44,7 +44,7 @@ for (const type of ['HS', 'ES', 'RS', 'PS']) { const verify = createVerifier({ algorithm, key: publicKey.toString() }) const token = jsonwebtokenSign({ a: 1, b: 2, c: 3 }, privateKey.toString(), { algorithm, noTimestamp: true }) - t.strictDeepEqual(verify(token), { a: 1, b: 2, c: 3 }) + t.strictSame(verify(token), { a: 1, b: 2, c: 3 }) t.end() }) @@ -53,7 +53,7 @@ for (const type of ['HS', 'ES', 'RS', 'PS']) { const signer = createSigner({ algorithm, key: privateKey, noTimestamp: true }) const token = signer({ a: 1, b: 2, c: 3 }) - t.strictDeepEqual(jsonwebtokenVerify(token, publicKey, { algorithm }), { a: 1, b: 2, c: 3 }) + t.strictSame(jsonwebtokenVerify(token, publicKey, { algorithm }), { a: 1, b: 2, c: 3 }) t.end() }) } @@ -70,7 +70,7 @@ if (useNewCrypto) { } }) - t.strictDeepEqual(verify(token), { a: 1, b: 2, c: 3 }) + t.strictSame(verify(token), { a: 1, b: 2, c: 3 }) t.end() }) @@ -79,7 +79,7 @@ if (useNewCrypto) { const signer = createSigner({ key: privateKeys[curve], noTimestamp: true }) const token = signer({ a: 1, b: 2, c: 3 }) - t.strictDeepEqual(joseVerify(token, asKey(publicKeys[curve])), { a: 1, b: 2, c: 3 }) + t.strictSame(joseVerify(token, asKey(publicKeys[curve])), { a: 1, b: 2, c: 3 }) t.end() }) } diff --git a/test/compliance.spec.js b/test/compliance.spec.js index acc6fd3..a35dce8 100644 --- a/test/compliance.spec.js +++ b/test/compliance.spec.js @@ -85,7 +85,7 @@ test('HS256', t => { const verified = createVerifier({ key })(token) - t.deepEqual(verified, payload) + t.same(verified, payload) t.equal(token, expectedToken) t.end() @@ -104,7 +104,7 @@ test('RS256', t => { const verified = createVerifier({ key: rsaPublicKey })(token) - t.deepEqual(verified, payload) + t.same(verified, payload) t.equal(token, expectedToken) t.end() @@ -123,7 +123,7 @@ test('PS384', t => { const verified = createVerifier({ key: rsaPublicKey })(token) - t.deepEqual(verified, payload) + t.same(verified, payload) // Since PS algorithm uses random data, we cannot match the signature t.equal(token.replace(/\..+/, ''), expectedToken.replace(/\..+/, '')) @@ -143,7 +143,7 @@ test('ES512', t => { const verified = createVerifier({ key: ecPublicKey })(token) - t.deepEqual(verified, payload) + t.same(verified, payload) // Since ES algorithm uses random data, we cannot match the signature t.equal(token.replace(/\..+/, ''), expectedToken.replace(/\..+/, '')) @@ -173,7 +173,7 @@ if (useNewCrypto) { const verified = createVerifier({ key: ed25519PublicKey })(token) - t.deepEqual(verified, payload) + t.same(verified, payload) t.equal(token, expectedToken) t.end() diff --git a/test/crypto.spec.js b/test/crypto.spec.js index 4955f04..b34bb9b 100644 --- a/test/crypto.spec.js +++ b/test/crypto.spec.js @@ -107,7 +107,7 @@ for (const type of ['Ed25519', 'Ed448']) { const privateKey = privateKeys[type] test(`detectPrivateKeyAlgorithm - ${type} keys should be recognized as EdDSA`, t => { - t.strictDeepEqual(detectPrivateKeyAlgorithm(privateKey), 'EdDSA') + t.strictSame(detectPrivateKeyAlgorithm(privateKey), 'EdDSA') t.end() }) @@ -186,7 +186,7 @@ for (const type of ['HS', 'ES', 'RS', 'PS']) { const publicKey = publicKeys[type === 'ES' ? algorithm : type] test(`detectPublicKeyAlgorithms - ${type} keys should be recognized as ${detectedAlgorithm}`, t => { - t.strictDeepEqual(detectPublicKeyAlgorithms(publicKey), detectedAlgorithm) + t.strictSame(detectPublicKeyAlgorithms(publicKey), detectedAlgorithm) t.end() }) @@ -201,14 +201,14 @@ for (const type of ['Ed25519', 'Ed448']) { const publicKey = publicKeys[type] test(`detectPublicKeyAlgorithms - ${type} keys should be recognized as EdDSA`, t => { - t.strictDeepEqual(detectPublicKeyAlgorithms(publicKey), ['EdDSA']) + t.strictSame(detectPublicKeyAlgorithms(publicKey), ['EdDSA']) t.end() }) } test('detectPublicKeyAlgorithms - empty key should return "none"', t => { - t.equals(detectPublicKeyAlgorithms(), 'none') + t.equal(detectPublicKeyAlgorithms(), 'none') t.end() }) @@ -279,8 +279,8 @@ for (const bits of [256, 384, 512]) { const verified = createVerifier({ key: 'secretsecretsecret' })(token) t.equal(verified.payload, 'PAYLOAD') - t.true(verified.iat >= start) - t.true(verified.iat <= Date.now() / 1000) + t.ok(verified.iat >= start) + t.ok(verified.iat <= Date.now() / 1000) t.end() }) @@ -293,8 +293,8 @@ for (const bits of [256, 384, 512]) { const verified = createVerifier({ key: 'secretsecretsecret' })(token) t.equal(verified.payload, 'PAYLOAD') - t.true(verified.iat >= start) - t.true(verified.iat <= Date.now() / 1000) + t.ok(verified.iat >= start) + t.ok(verified.iat <= Date.now() / 1000) t.end() }) @@ -317,8 +317,8 @@ for (const type of ['ES', 'RS', 'PS']) { const verified = createVerifier({ key: publicKey })(token) t.equal(verified.payload, 'PAYLOAD') - t.true(verified.iat >= start) - t.true(verified.iat <= Date.now() / 1000) + t.ok(verified.iat >= start) + t.ok(verified.iat <= Date.now() / 1000) t.end() }) @@ -330,8 +330,8 @@ for (const type of ['ES', 'RS', 'PS']) { const verified = createVerifier({ algorithms: [algorithm], key: publicKey.toString('utf-8') })(token) t.equal(verified.payload, 'PAYLOAD') - t.true(verified.iat >= start) - t.true(verified.iat <= Date.now() / 1000) + t.ok(verified.iat >= start) + t.ok(verified.iat <= Date.now() / 1000) t.end() }) @@ -367,8 +367,8 @@ if (useNewCrypto) { const verified = createVerifier({ algorithms: ['EdDSA'], key: publicKey })(token) t.equal(verified.payload, 'PAYLOAD') - t.true(verified.iat >= start) - t.true(verified.iat <= Date.now() / 1000) + t.ok(verified.iat >= start) + t.ok(verified.iat <= Date.now() / 1000) t.end() }) @@ -380,8 +380,8 @@ if (useNewCrypto) { const verified = createVerifier({ algorithms: ['EdDSA'], key: publicKey.toString('utf-8') })(token) t.equal(verified.payload, 'PAYLOAD') - t.true(verified.iat >= start) - t.true(verified.iat <= Date.now() / 1000) + t.ok(verified.iat >= start) + t.ok(verified.iat <= Date.now() / 1000) t.end() }) diff --git a/test/decoder.spec.js b/test/decoder.spec.js index 6ccf3cc..b665f59 100644 --- a/test/decoder.spec.js +++ b/test/decoder.spec.js @@ -15,9 +15,9 @@ const nonJwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVEFBIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9LIiwiaWF0Ijo5ODc2NTQzMjEwfQ.Tauq025SLRNP4qTYsr_FHXwjQ_ZTsAjBGwE-2h6if4k' test('should return a valid token', t => { - t.strictDeepEqual(defaultDecoder(token), { sub: '1234567890', name: 'OK', iat: 9876543210 }) + t.strictSame(defaultDecoder(token), { sub: '1234567890', name: 'OK', iat: 9876543210 }) - t.strictDeepEqual(completeDecoder(Buffer.from(token, 'utf-8')), { + t.strictSame(completeDecoder(Buffer.from(token, 'utf-8')), { header: { alg: 'HS256', typ: 'JWT' diff --git a/test/signer.spec.js b/test/signer.spec.js index 2823045..b524c08 100644 --- a/test/signer.spec.js +++ b/test/signer.spec.js @@ -4,7 +4,7 @@ const { readFileSync } = require('fs') const { resolve } = require('path') const { test } = require('tap') -const { createSigner, createVerifier, TokenError } = require('../src') +const { createSigner, createVerifier, TokenError, createDecoder } = require('../src') const { useNewCrypto } = require('../src/crypto') const privateKeys = { @@ -13,6 +13,7 @@ const privateKeys = { ES384: readFileSync(resolve(__dirname, '../benchmarks/keys/es-384-private.key')), ES512: readFileSync(resolve(__dirname, '../benchmarks/keys/es-512-private.key')), RS: readFileSync(resolve(__dirname, '../benchmarks/keys/rs-512-private.key')), + PPRS: readFileSync(resolve(__dirname, '../benchmarks/keys/pprs-512-private.key')), PS: readFileSync(resolve(__dirname, '../benchmarks/keys/ps-512-private.key')), Ed25519: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-25519-private.key')), Ed448: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-448-private.key')) @@ -24,6 +25,7 @@ const publicKeys = { ES384: readFileSync(resolve(__dirname, '../benchmarks/keys/es-384-public.key')), ES512: readFileSync(resolve(__dirname, '../benchmarks/keys/es-512-public.key')), RS: readFileSync(resolve(__dirname, '../benchmarks/keys/rs-512-public.key')), + PPRS: readFileSync(resolve(__dirname, '../benchmarks/keys/pprs-512-public.key')), PS: readFileSync(resolve(__dirname, '../benchmarks/keys/ps-512-public.key')), Ed25519: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-25519-public.key')), Ed448: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-448-public.key')) @@ -96,9 +98,28 @@ test('it correctly returns a token - callback - key as promise', t => { }) }) -test('it correctly autodetects the algorithm depending on the secret provided', t => { +test('it correctly returns a token - key as passphrase protected key', async t => { + const payload = { a: 1 } + if (useNewCrypto) { + const signedToken = sign(payload, { key: { key: privateKeys.PPRS, passphrase: 'secret' } }) + const decoder = createDecoder() + const result = decoder(signedToken) + + t.equal(payload.a, result.a) + } else { + t.throws(() => sign(payload, { key: { key: privateKeys.PPRS, passphrase: 'secret' } }), { + message: 'Cannot create the signature.', + originalError: { + message: 'The "key" argument must be one of type string, Buffer, TypedArray, or DataView. Received type object' + } + }) + } +}) + +test('it correctly autodetects the algorithm depending on the secret provided', async t => { const hsVerifier = createVerifier({ complete: true, key: publicKeys.HS }) const rsVerifier = createVerifier({ complete: true, key: publicKeys.RS }) + const pprsVerifier = createVerifier({ complete: true, key: publicKeys.PPRS }) const psVerifier = createVerifier({ complete: true, key: publicKeys.PS }) const es256Verifier = createVerifier({ complete: true, key: publicKeys.ES256 }) const es384Verifier = createVerifier({ complete: true, key: publicKeys.ES384 }) @@ -108,39 +129,41 @@ test('it correctly autodetects the algorithm depending on the secret provided', let token = createSigner({ key: privateKeys.HS })({ a: 1 }) let verification = hsVerifier(token) - t.is(verification.header.alg, 'HS256') + t.equal(verification.header.alg, 'HS256') token = createSigner({ key: privateKeys.RS })({ a: 1 }) verification = rsVerifier(token) - t.is(verification.header.alg, 'RS256') + t.equal(verification.header.alg, 'RS256') token = createSigner({ key: privateKeys.PS })({ a: 1 }) verification = psVerifier(token) - t.is(verification.header.alg, 'RS256') + t.equal(verification.header.alg, 'RS256') token = createSigner({ key: privateKeys.ES256 })({ a: 1 }) verification = es256Verifier(token) - t.is(verification.header.alg, 'ES256') + t.equal(verification.header.alg, 'ES256') token = createSigner({ key: privateKeys.ES384 })({ a: 1 }) verification = es384Verifier(token) - t.is(verification.header.alg, 'ES384') + t.equal(verification.header.alg, 'ES384') token = createSigner({ key: privateKeys.ES512 })({ a: 1 }) verification = es512Verifier(token) - t.is(verification.header.alg, 'ES512') + t.equal(verification.header.alg, 'ES512') if (useNewCrypto) { + token = createSigner({ key: { key: privateKeys.PPRS, passphrase: 'secret' } })({ a: 1 }) + verification = pprsVerifier(token) + t.equal(verification.header.alg, 'RS256') + token = createSigner({ key: privateKeys.Ed25519 })({ a: 1 }) verification = es25519Verifier(token) - t.is(verification.header.alg, 'EdDSA') + t.equal(verification.header.alg, 'EdDSA') token = createSigner({ key: privateKeys.Ed448 })({ a: 1 }) verification = es448Verifier(token) - t.is(verification.header.alg, 'EdDSA') + t.equal(verification.header.alg, 'EdDSA') } - - t.end() }) test('it correctly set a timestamp', t => { @@ -381,7 +404,7 @@ test('it correctly handle errors - callback', t => { noTimestamp: true }, (error, token) => { - t.true(error instanceof TokenError) + t.ok(error instanceof TokenError) t.equal(error.message, 'Cannot fetch key.') t.end() @@ -399,7 +422,7 @@ test('it correctly validates the key received from the callback', t => { noTimestamp: true }, (error, token) => { - t.true(error instanceof TokenError) + t.ok(error instanceof TokenError) t.equal( error.message, 'The key returned from the callback must be a string or a buffer containing a secret or a private key.' @@ -421,7 +444,7 @@ test('it correctly handle errors - evented callback', t => { algorithm: 'RS256' }, (error, token) => { - t.true(error instanceof TokenError) + t.ok(error instanceof TokenError) t.equal(error.message, 'Invalid private key provided for algorithm RS256.') t.end() @@ -433,8 +456,8 @@ test('returns a promise according to key option', t => { const s1 = createSigner({ key: 'secret' })({ a: 'PAYLOAD' }) const s2 = createSigner({ key: async () => 'secret' })({ a: 'PAYLOAD' }) - t.true(typeof s1.then === 'undefined') - t.true(typeof s2.then === 'function') + t.ok(typeof s1.then === 'undefined') + t.ok(typeof s2.then === 'function') s2.then( () => false, @@ -481,7 +504,15 @@ test('options validation - algorithm', t => { test('options validation - key', t => { t.throws(() => createSigner({ key: 123 }), { - message: 'The key option must be a string, a buffer or a function returning the algorithm secret or private key.' + message: 'The key option must be a string, a buffer, an object containing key/passphrase properties or a function returning the algorithm secret or private key.' + }) + + t.throws(() => createSigner({ key: { key: privateKeys.PPRS } }), { + message: 'The key option must be a string, a buffer, an object containing key/passphrase properties or a function returning the algorithm secret or private key.' + }) + + t.throws(() => createSigner({ key: { passphrase: 'secret' } }), { + message: 'The key option must be a string, a buffer, an object containing key/passphrase properties or a function returning the algorithm secret or private key.' }) t.throws(() => createSigner({ algorithm: 'none', key: 123 }), { diff --git a/test/verifier.spec.js b/test/verifier.spec.js index 83b22bf..93d83e9 100644 --- a/test/verifier.spec.js +++ b/test/verifier.spec.js @@ -16,6 +16,7 @@ const privateKeys = { ES384: readFileSync(resolve(__dirname, '../benchmarks/keys/es-384-private.key')), ES512: readFileSync(resolve(__dirname, '../benchmarks/keys/es-512-private.key')), RS: readFileSync(resolve(__dirname, '../benchmarks/keys/rs-512-private.key')), + PPRS: readFileSync(resolve(__dirname, '../benchmarks/keys/pprs-512-private.key')), PS: readFileSync(resolve(__dirname, '../benchmarks/keys/ps-512-private.key')), Ed25519: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-25519-private.key')), Ed448: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-448-private.key')) @@ -27,6 +28,7 @@ const publicKeys = { ES384: readFileSync(resolve(__dirname, '../benchmarks/keys/es-384-public.key')), ES512: readFileSync(resolve(__dirname, '../benchmarks/keys/es-512-public.key')), RS: readFileSync(resolve(__dirname, '../benchmarks/keys/rs-512-public.key')), + PPRS: readFileSync(resolve(__dirname, '../benchmarks/keys/pprs-512-public.key')), PS: readFileSync(resolve(__dirname, '../benchmarks/keys/ps-512-public.key')), Ed25519: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-25519-public.key')), Ed448: readFileSync(resolve(__dirname, '../benchmarks/keys/ed-448-public.key')) @@ -38,14 +40,14 @@ function verify(token, options, callback) { } test('it correctly verifies a token - sync', t => { - t.strictDeepEqual( + t.strictSame( verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', { noTimestamp: true }), { a: 1 } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjIwMDAwMDAwMDAsImV4cCI6MjEwMDAwMDAwMH0.vrIO0e4YNXgzqdj7RcTqmP8AlCuvfYoxJCkma78eILA', { clockTimestamp: 2010000000 } @@ -53,7 +55,7 @@ test('it correctly verifies a token - sync', t => { { a: 1, iat: 2000000000, exp: 2100000000 } ) - t.strictDeepEqual( + t.strictSame( verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', { checkTyp: 'jwt', noTimestamp: true @@ -61,7 +63,7 @@ test('it correctly verifies a token - sync', t => { { a: 1 } ) - t.strictDeepEqual( + t.strictSame( verify('eyJhbGciOiJIUzI1NiIsInR5cCI6ImFwcGxpY2F0aW9uL2p3dCJ9.eyJhIjoxfQ.1ptuaNj5R0owE-5663LpMknK3eRgZVDHkMkOKkxlteM', { checkTyp: 'jwt' }), @@ -90,7 +92,7 @@ test('it correctly verifies a token - sync', t => { } ) - t.strictDeepEqual( + t.strictSame( verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', { noTimestamp: true, complete: true @@ -103,7 +105,7 @@ test('it correctly verifies a token - sync', t => { ) if (useNewCrypto) { - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImt0eSI6Ik9LUCIsImNydiI6IkVkMjU1MTkifQ.eyJhIjoxfQ.n4isU7JqaKRVOyx2ni7b_iaAzB75pAUCW6CetcoClhtJ5yDM7YkNMbKqmDUhTKMpupAcztIjX8m4mZwpA33HAA', { key: publicKeys.Ed25519 } @@ -130,7 +132,7 @@ test('it correctly verifies a token - sync', t => { }) test('it correctly verifies a token - async - key with callback', async t => { - t.strictDeepEqual( + t.strictSame( await verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', { key: (_h, callback) => setTimeout(() => callback(null, 'secret'), 10), noTimestamp: true @@ -138,7 +140,7 @@ test('it correctly verifies a token - async - key with callback', async t => { { a: 1 } ) - t.strictDeepEqual( + t.strictSame( await verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', { algorithms: ['HS256'], key: (_h, callback) => setTimeout(() => callback(null, 'secret'), 10), @@ -154,7 +156,7 @@ test('it correctly verifies a token - async - key with callback', async t => { }) test('it correctly verifies a token - async - key as promise', async t => { - t.strictDeepEqual( + t.strictSame( await verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', { key: async () => Buffer.from('secret', 'utf-8'), noTimestamp: true @@ -164,7 +166,7 @@ test('it correctly verifies a token - async - key as promise', async t => { }) test('it correctly verifies a token - async - static key', async t => { - t.strictDeepEqual( + t.strictSame( await verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', { noTimestamp: true }), @@ -178,12 +180,20 @@ test('it correctly verifies a token - callback - key as promise', t => { { key: async () => Buffer.from('secret', 'utf-8'), noTimestamp: true }, (error, payload) => { t.type(error, 'null') - t.strictDeepEqual(payload, { a: 1 }) + t.strictSame(payload, { a: 1 }) t.end() } ) }) +test('it correctly verifies a token - token signed with encrypted private key', async t => { + const payload = verify( + 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjE2MzU0MjY2OTl9.c5VeTRDL43sMxEk4pV7AV6nGJeRbJYw6tdKGfzq6bvjT-ai29gQc7baTAmoo16EuboUwBHoz_OEOtwsePetoc0wKtDoXY7t6dBWznV2Z4_7YSrnt2U62FZrlVDoLPYJRRHhB6sR2YyidoUWzfs821_SpeTeT4Ls-tlqWjIGkpUDktZiPKYIt9LkLFgZDaCBeQr39BMCagD3p0yGYIWZJNsIQKNvvUHjtF4Io9buPwKKA6FAfYgM5c1aTAkhhnRjZSjW0vu-Osxlbu-XO0-IF-0c4eGgf2LAh_jGM4bF1nQmExKI9Q0IpvbPD8pSzcIPndiHdgGxrJy7X9GktN6Vi2DQazcIXtjBIaBNO4VKew5GNIbSb-lHyeO7WBENE3WrVImS_9_i3z81M-F0w1C6MqmnKZ3qKLna3OG1pYU4mVQ2rvBNdHuVOrtJyE0IiCDQS-RKaKM0lOprHy_B6_TNRp_Y9oBCVOY1Kr8fczigfArwSlPai051AncK-zfHZwvP7_uBKitncmNDjr19xiLa79Fbm6mkSA8tZindDvBml1ZF9apNF51CCdO-ce9yqj3Aem2n1VXHLuq9sdIk_mlSZn9aLDOPUI22DcdhcSsySdKdWSf9F7dj5c1J9ppwxTxK3LHjIeiaCJWCmKvfu73j_rpKzbFzzwotQ3bsRave8gdY', + { key: publicKeys.PPRS } + ) + t.strictSame(payload.a, 1) +}) + test('it rejects invalid tokens', async t => { t.throws(() => verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-aaa', {}), { message: 'The token signature is invalid.' @@ -242,7 +252,7 @@ test('it correctly handle errors - callback', t => { } }, (error, token) => { - t.true(error instanceof TokenError) + t.ok(error instanceof TokenError) t.equal(error.message, 'Cannot fetch key.') t.end() @@ -259,7 +269,7 @@ test('it correctly handle errors - evented callback', t => { } }, (error, token) => { - t.true(error instanceof TokenError) + t.ok(error instanceof TokenError) t.equal(error.message, 'The token signature is invalid.') t.end() @@ -316,7 +326,7 @@ test('it validates if the token is active unless explicitily disabled', t => { { message: 'The token will be active at 2033-05-18T03:33:20.000Z.' } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjAsIm5iZiI6MjAwMDAwMDAwMH0.PlCCCgSnL38HaOY1-bkWnz-LX9WW2b772Zs3oxQJIv4', { @@ -340,7 +350,7 @@ test('it validates if the token has not expired (via exp) unless explicitily dis { message: 'The token has expired at 1970-01-01T00:01:41.000Z.' } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjEwMCwiZXhwIjoxMDF9.ULKqTsvUYm7iNOKA6bP5NXsa1A8vofgPIGiC182Vf_Q', { @@ -364,7 +374,7 @@ test('it validates if the token has not expired (via maxAge) only if explicitily { message: 'The token has expired at 1970-01-01T00:05:00.000Z.' } ) - t.strictDeepEqual( + t.strictSame( verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjEwMH0.5V5yFNSqmn0w6yDR1vUbykF36WwdQmADMTLJwiJtx8w'), { a: 1, iat: 100 } ) @@ -403,7 +413,7 @@ test('it validates the jti claim only if explicitily enabled', t => { { message: 'The jti claim value is not allowed.' } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedJti: 'JTI' } @@ -418,7 +428,7 @@ test('it validates the jti claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedJti: ['ABX', 'JTI'] } @@ -433,7 +443,7 @@ test('it validates the jti claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedJti: ['ABX', /^J/] } @@ -492,7 +502,7 @@ test('it validates the aud claim only if explicitily enabled', t => { { message: 'None of aud claim values are allowed.' } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEIiwiRFVBMiJdLCJpc3MiOiJJU1MiLCJzdWIiOiJTVUIiLCJub25jZSI6Ik5PTkNFIn0.lhu5t694BY0QmF7SChUw7Z9nUPtupWCkhrQ2rqN06GU', { allowedAud: 'AUD' } @@ -507,7 +517,7 @@ test('it validates the aud claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedAud: ['ABX', 'AUD1'] } @@ -522,7 +532,7 @@ test('it validates the aud claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedAud: ['ABX', /^D/] } @@ -571,7 +581,7 @@ test('it validates the iss claim only if explicitily enabled', t => { { message: 'The iss claim value is not allowed.' } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedIss: 'ISS' } @@ -586,7 +596,7 @@ test('it validates the iss claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedIss: ['ABX', 'ISS'] } @@ -601,7 +611,7 @@ test('it validates the iss claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedIss: ['ABX', /^I/] } @@ -650,7 +660,7 @@ test('it validates the sub claim only if explicitily enabled', t => { { message: 'The sub claim value is not allowed.' } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedSub: 'SUB' } @@ -665,7 +675,7 @@ test('it validates the sub claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedSub: ['ABX', 'SUB'] } @@ -680,7 +690,7 @@ test('it validates the sub claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedSub: ['ABX', /^S/] } @@ -729,7 +739,7 @@ test('it validates the nonce claim only if explicitily enabled', t => { { message: 'The nonce claim value is not allowed.' } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedNonce: 'NONCE' } @@ -744,7 +754,7 @@ test('it validates the nonce claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedNonce: ['ABX', 'NONCE'] } @@ -759,7 +769,7 @@ test('it validates the nonce claim only if explicitily enabled', t => { } ) - t.strictDeepEqual( + t.strictSame( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', { allowedNonce: ['ABX', /^N/] } @@ -862,9 +872,9 @@ test('caching - sync', t => { const verifier = createVerifier({ key: 'secret', cache: true }) t.equal(verifier.cache.size, 0) - t.strictDeepEqual(verifier(token), { a: 1 }) + t.strictSame(verifier(token), { a: 1 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier(token), { a: 1 }) + t.strictSame(verifier(token), { a: 1 }) t.equal(verifier.cache.size, 1) t.throws(() => verifier(invalidToken), { message: 'The token signature is invalid.' }) @@ -872,8 +882,8 @@ test('caching - sync', t => { t.throws(() => verifier(invalidToken), { message: 'The token signature is invalid.' }) t.equal(verifier.cache.size, 2) - t.strictDeepEqual(verifier.cache.get(hashToken(token))[0], { a: 1 }) - t.true(verifier.cache.get(hashToken(invalidToken))[0] instanceof TokenError) + t.strictSame(verifier.cache.get(hashToken(token))[0], { a: 1 }) + t.ok(verifier.cache.get(hashToken(invalidToken))[0] instanceof TokenError) t.end() }) @@ -885,9 +895,9 @@ test('caching - async', async t => { const verifier = createVerifier({ key: async () => 'secret', cache: true }) t.equal(verifier.cache.size, 0) - t.strictDeepEqual(await verifier(token), { a: 1 }) + t.strictSame(await verifier(token), { a: 1 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(await verifier(token), { a: 1 }) + t.strictSame(await verifier(token), { a: 1 }) t.equal(verifier.cache.size, 1) await t.rejects(async () => verifier(invalidToken), { message: 'The token signature is invalid.' }) @@ -895,8 +905,8 @@ test('caching - async', async t => { await t.rejects(async () => verifier(invalidToken), { message: 'The token signature is invalid.' }) t.equal(verifier.cache.size, 2) - t.strictDeepEqual(verifier.cache.get(hashToken(token))[0], { a: 1 }) - t.true(verifier.cache.get(hashToken(invalidToken))[0] instanceof TokenError) + t.strictSame(verifier.cache.get(hashToken(token))[0], { a: 1 }) + t.ok(verifier.cache.get(hashToken(invalidToken))[0] instanceof TokenError) }) for (const type of ['HS', 'ES', 'RS', 'PS']) { @@ -914,7 +924,7 @@ for (const type of ['HS', 'ES', 'RS', 'PS']) { .update(token) .digest('hex') - t.strictDeepEqual(verifier(token), { a: 1 }) + t.strictSame(verifier(token), { a: 1 }) t.equal(verifier.cache.size, 1) t.equal(Array.from(verifier.cache.keys())[0], hash) @@ -932,7 +942,7 @@ if (useNewCrypto) { .update(token) .digest('hex') - t.strictDeepEqual(verifier(token), { a: 1 }) + t.strictSame(verifier(token), { a: 1 }) t.equal(verifier.cache.size, 1) t.equal(Array.from(verifier.cache.keys())[0], hash) @@ -952,7 +962,7 @@ if (useNewCrypto) { .update(token) .digest('hex') - t.strictDeepEqual(verifier(token), { a: 1 }) + t.strictSame(verifier(token), { a: 1 }) t.equal(verifier.cache.size, 1) t.equal(Array.from(verifier.cache.keys())[0], hash) @@ -968,15 +978,15 @@ test('caching - should be able to manipulate cache directy', t => { const token = signer({ a: 1 }) t.equal(verifier.cache.size, 0) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, exp: 200 }) + t.strictSame(verifier(token), { a: 1, iat: 100, exp: 200 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, exp: 200 }, 0, 200000]) + t.strictSame(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, exp: 200 }, 0, 200000]) verifier.cache.clear() t.equal(verifier.cache.size, 0) verifier.cache.set(token, 'WHATEVER') - t.strictDeepEqual(verifier.cache.get(token), 'WHATEVER') + t.strictSame(verifier.cache.get(token), 'WHATEVER') verifier.cache.set(token, null) - t.strictDeepEqual(verifier.cache.get(token), null) + t.strictSame(verifier.cache.get(token), null) clock.uninstall() @@ -992,11 +1002,11 @@ test('caching - should correctly expire cached token using the exp claim', t => // First of all, make a token and verify it's cached t.equal(verifier.cache.size, 0) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, exp: 200 }) + t.strictSame(verifier(token), { a: 1, iat: 100, exp: 200 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, exp: 200 }) + t.strictSame(verifier(token), { a: 1, iat: 100, exp: 200 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, exp: 200 }, 0, 200000]) + t.strictSame(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, exp: 200 }, 0, 200000]) // Now advance to expired time clock.tick(200000) @@ -1004,7 +1014,7 @@ test('caching - should correctly expire cached token using the exp claim', t => // The token should now be expired and the cache should have been updated to reflect it t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:03:20.000Z.' }) t.equal(verifier.cache.size, 1) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:03:20.000Z.' }) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:03:20.000Z.' }) @@ -1015,12 +1025,12 @@ test('caching - should correctly expire cached token using the exp claim', t => verifier.cache.clear() t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:03:20.000Z.' }) t.equal(verifier.cache.size, 1) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) const verifierWithTimestamp = createVerifier({ key: 'secret', cache: true, clockTimestamp: 100000 }) - t.strictDeepEqual(verifierWithTimestamp(token), { a: 1, iat: 100, exp: 200 }) + t.strictSame(verifierWithTimestamp(token), { a: 1, iat: 100, exp: 200 }) t.equal(verifierWithTimestamp.cache.size, 1) - t.strictDeepEqual(verifierWithTimestamp.cache.get(hashToken(token)), [{ a: 1, iat: 100, exp: 200 }, 0, 200000]) + t.strictSame(verifierWithTimestamp.cache.get(hashToken(token)), [{ a: 1, iat: 100, exp: 200 }, 0, 200000]) t.end() }) @@ -1034,11 +1044,11 @@ test('caching - should correctly expire cached token using the maxAge claim', t // First of all, make a token and verify it's cached t.equal(verifier.cache.size, 0) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100 }) + t.strictSame(verifier(token), { a: 1, iat: 100 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100 }) + t.strictSame(verifier(token), { a: 1, iat: 100 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100 }, 0, 200000]) + t.strictSame(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100 }, 0, 200000]) // Now advance to expired time clock.tick(200000) @@ -1046,7 +1056,7 @@ test('caching - should correctly expire cached token using the maxAge claim', t // The token should now be expired and the cache should have been updated to reflect it t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:03:20.000Z.' }) t.equal(verifier.cache.size, 1) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:03:20.000Z.' }) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:03:20.000Z.' }) @@ -1066,17 +1076,17 @@ test('caching - should correctly expire not yet cached token using the nbf claim t.throws(() => verifier(token), { message: 'The token will be active at 1970-01-01T00:05:00.000Z.' }) t.equal(verifier.cache.size, 1) t.throws(() => verifier(token), { message: 'The token will be active at 1970-01-01T00:05:00.000Z.' }) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) // Now advance to expired time clock.tick(200000) // The token should now be active and the cache should have been updated to reflect it - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, nbf: 300 }) + t.strictSame(verifier(token), { a: 1, iat: 100, nbf: 300 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, nbf: 300 }) + t.strictSame(verifier(token), { a: 1, iat: 100, nbf: 300 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300 }, 300000, 900000]) + t.strictSame(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300 }, 300000, 900000]) clock.uninstall() t.end() @@ -1094,17 +1104,17 @@ test('caching - should be able to consider both nbf and exp field at the same ti t.throws(() => verifier(token), { message: 'The token will be active at 1970-01-01T00:05:00.000Z.' }) t.equal(verifier.cache.size, 1) t.throws(() => verifier(token), { message: 'The token will be active at 1970-01-01T00:05:00.000Z.' }) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) // Now advance to activation time clock.tick(200000) // The token should now be active and the cache should have been updated to reflect it - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300, exp: 500 }, 300000, 500000]) + t.strictSame(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300, exp: 500 }, 300000, 500000]) // Now advance again after the expiry time clock.tick(210000) @@ -1112,7 +1122,7 @@ test('caching - should be able to consider both nbf and exp field at the same ti // The token should now be expired and the cache should have been updated to reflect it t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:08:20.000Z.' }) t.equal(verifier.cache.size, 1) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:08:20.000Z.' }) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:08:20.000Z.' }) @@ -1134,24 +1144,24 @@ test('caching - should ignore the nbf and exp when asked to', t => { t.throws(() => verifier(token), { message: 'The token will be active at 1970-01-01T00:05:00.000Z.' }) t.equal(verifier.cache.size, 1) t.throws(() => verifier(token), { message: 'The token will be active at 1970-01-01T00:05:00.000Z.' }) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) // For the verifier which ignores notBefore, the token is already active - t.strictDeepEqual(verifierNoNbf(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifierNoNbf(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifierNoNbf.cache.size, 1) - t.strictDeepEqual(verifierNoNbf(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifierNoNbf(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifierNoNbf.cache.size, 1) - t.strictDeepEqual(verifierNoNbf.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300, exp: 500 }, 0, 500000]) + t.strictSame(verifierNoNbf.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300, exp: 500 }, 0, 500000]) // Now advance to activation time clock.tick(200000) // The token should now be active and the cache should have been updated to reflect it - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifier(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifier.cache.size, 1) - t.strictDeepEqual(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300, exp: 500 }, 300000, 500000]) + t.strictSame(verifier.cache.get(hashToken(token)), [{ a: 1, iat: 100, nbf: 300, exp: 500 }, 300000, 500000]) // Now advance again after the expiry time clock.tick(210000) @@ -1159,16 +1169,16 @@ test('caching - should ignore the nbf and exp when asked to', t => { // The token should now be expired and the cache should have been updated to reflect it t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:08:20.000Z.' }) t.equal(verifier.cache.size, 1) - t.true(verifier.cache.get(hashToken(token))[0] instanceof TokenError) + t.ok(verifier.cache.get(hashToken(token))[0] instanceof TokenError) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:08:20.000Z.' }) t.throws(() => verifier(token), { message: 'The token has expired at 1970-01-01T00:08:20.000Z.' }) // For the verifier which ignores expiration, the token is still active - t.strictDeepEqual(verifierNoExp(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifierNoExp(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifierNoExp.cache.size, 1) - t.strictDeepEqual(verifierNoExp(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) + t.strictSame(verifierNoExp(token), { a: 1, iat: 100, nbf: 300, exp: 500 }) t.equal(verifierNoExp.cache.size, 1) - t.strictDeepEqual(verifierNoExp.cache.get(hashToken(token)), [ + t.strictSame(verifierNoExp.cache.get(hashToken(token)), [ { a: 1, iat: 100, nbf: 300, exp: 500 }, 300000, 1110000