From 59f8c1b469b578c6d404889e8c74a0d60843e182 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Thu, 21 Nov 2024 10:05:14 +0100 Subject: [PATCH] BREAKING CHANGE: use fetch (#322) * feat: migrate * BREAKING CHANGE: remove node-fetch in favour of fetch --- .taprc | 1 - package.json | 11 +-- src/error.d.ts | 2 - src/get-jwks.d.ts | 4 +- src/get-jwks.js | 11 +-- test/cache.spec.js | 24 ++--- test/fast-jwt-integration.spec.js | 10 +- test/fastify-jwt-integrations.spec.js | 18 ++-- test/getJwk.spec.js | 136 +++++++++++++------------- test/getJwkDiscovery.spec.js | 88 ++++++++--------- test/getJwksUri.spec.js | 44 ++++----- test/getPublicKey.spec.js | 10 +- test/getPublicKeyDiscovery.spec.js | 10 +- 13 files changed, 177 insertions(+), 192 deletions(-) delete mode 100644 .taprc diff --git a/.taprc b/.taprc deleted file mode 100644 index c5acd1e..0000000 --- a/.taprc +++ /dev/null @@ -1 +0,0 @@ -100: true diff --git a/package.json b/package.json index 5bd3904..a6f5f9a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "directory": "./test/types" }, "scripts": { - "test": "tap test/*.spec.js && tsd", + "test": "node --test test/*.spec.js && tsd", "lint": "eslint ." }, "repository": { @@ -33,22 +33,19 @@ "homepage": "https://github.com/nearform/get-jwks#readme", "dependencies": { "jwk-to-pem": "^2.0.4", - "lru-cache": "^11.0.0", - "node-fetch": "^2.6.1" + "lru-cache": "^11.0.0" }, "devDependencies": { "@fastify/jwt": "^8.0.0", "@types/node": "^22.0.0", - "@types/node-fetch": "^2.6.2", "eslint": "^8.6.0", "fast-jwt": "^4.0.0", "fastify": "^4.0.3", "jsonwebtoken": "^9.0.0", - "nock": "^13.0.7", + "nock": "^v14.0.0-beta.16", "prettier": "^3.0.0", "sinon": "^19.0.2", - "tap": "^16.0.0", "tsd": "^0.31.0", "typescript": "^5.0.2" } -} +} \ No newline at end of file diff --git a/src/error.d.ts b/src/error.d.ts index d152174..c28dbd8 100644 --- a/src/error.d.ts +++ b/src/error.d.ts @@ -1,5 +1,3 @@ -import type { Response } from 'node-fetch' - export enum errorCode { OPENID_CONFIGURATION_REQUEST_FAILED = 'OPENID_CONFIGURATION_REQUEST_FAILED', JWKS_REQUEST_FAILED = 'JWKS_REQUEST_FAILED', diff --git a/src/get-jwks.d.ts b/src/get-jwks.d.ts index 56b163d..7279416 100644 --- a/src/get-jwks.d.ts +++ b/src/get-jwks.d.ts @@ -1,5 +1,4 @@ import type { LRUCache } from 'lru-cache' -import type { Agent } from 'https' type GetPublicKeyOptions = { domain?: string @@ -24,8 +23,7 @@ type GetJwksOptions = { issuersWhitelist?: string[] providerDiscovery?: boolean jwksPath?: string - agent?: Agent - timeout?: number + fetchOptions?: RequestInit } declare namespace buildGetJwks { diff --git a/src/get-jwks.js b/src/get-jwks.js index 585d10e..92fd4a8 100644 --- a/src/get-jwks.js +++ b/src/get-jwks.js @@ -1,6 +1,5 @@ 'use strict' -const fetch = require('node-fetch') const { LRUCache } = require('lru-cache') const jwkToPem = require('jwk-to-pem') @@ -20,14 +19,13 @@ function ensureNoLeadingSlash(path) { function buildGetJwks(options = {}) { const max = options.max || 100 const ttl = options.ttl || ONE_MINUTE - const timeout = options.timeout || FIVE_SECONDS const issuersWhitelist = (options.issuersWhitelist || []).map(ensureTrailingSlash) const checkIssuer = options.checkIssuer const providerDiscovery = options.providerDiscovery || false const jwksPath = options.jwksPath ? ensureNoLeadingSlash(options.jwksPath) : false - const agent = options.agent || null + const fetchOptions = { timeout: FIVE_SECONDS, ...options.fetchOptions } const staleCache = new LRUCache({ max: max * 2, ttl }) const cache = new LRUCache({ max, @@ -38,10 +36,7 @@ function buildGetJwks(options = {}) { async function getJwksUri(normalizedDomain) { const response = await fetch( `${normalizedDomain}.well-known/openid-configuration`, - { - agent, - timeout, - } + fetchOptions, ) const body = await response.json() @@ -111,7 +106,7 @@ function buildGetJwks(options = {}) { ? await getJwksUri(normalizedDomain) : `${normalizedDomain}.well-known/jwks.json` - const response = await fetch(jwksUri, { agent, timeout }) + const response = await fetch(jwksUri, fetchOptions) const body = await response.json() if (!response.ok) { diff --git a/test/cache.spec.js b/test/cache.spec.js index 3394ccb..dc287ea 100644 --- a/test/cache.spec.js +++ b/test/cache.spec.js @@ -1,6 +1,6 @@ 'use strict' -const t = require('tap') +const {test} = require('node:test') const nock = require('nock') const jwkToPem = require('jwk-to-pem') @@ -8,7 +8,7 @@ const { jwks, domain } = require('./constants') const buildGetJwks = require('../src/get-jwks') -t.test( +test( 'if there is already a key in cache, it should not make a http request', async t => { const getJwks = buildGetJwks() @@ -20,14 +20,14 @@ t.test( const publicKey = await getJwks.getPublicKey({ domain, alg, kid }) const jwk = await getJwks.getJwk({ domain, alg, kid }) - t.ok(publicKey) - t.ok(jwk) - t.equal(publicKey, jwkToPem(jwk)) - t.same(jwk, localKey) + t.assert.ok(publicKey) + t.assert.ok(jwk) + t.assert.equal(publicKey, jwkToPem(jwk)) + t.assert.equal(jwk, localKey) } ) -t.test( +test( 'if initialized without any cache settings it should use default values', async t => { nock('https://localhost/').get('/.well-known/jwks.json').reply(200, jwks) @@ -37,10 +37,10 @@ t.test( const publicKey = await getJwks.getPublicKey({ domain, alg, kid }) const jwk = await getJwks.getJwk({ domain, alg, kid }) - t.ok(publicKey) - t.ok(jwk) - t.ok(getJwks.cache) - t.equal(cache.max, 100) - t.equal(cache.ttl, 60000) + t.assert.ok(publicKey) + t.assert.ok(jwk) + t.assert.ok(getJwks.cache) + t.assert.equal(cache.max, 100) + t.assert.equal(cache.ttl, 60000) } ) diff --git a/test/fast-jwt-integration.spec.js b/test/fast-jwt-integration.spec.js index 7ee0845..5c842a1 100644 --- a/test/fast-jwt-integration.spec.js +++ b/test/fast-jwt-integration.spec.js @@ -1,22 +1,22 @@ 'use strict' -const t = require('tap') +const {beforeEach, afterEach, test} = require('node:test') const nock = require('nock') const { createVerifier } = require('fast-jwt') const { jwks, token } = require('./constants') const buildGetJwks = require('../src/get-jwks') -t.beforeEach(() => { +beforeEach(() => { nock.disableNetConnect() }) -t.afterEach(() => { +afterEach(() => { nock.cleanAll() nock.enableNetConnect() }) -t.test('fast-jwt integration tests', async t => { +test('fast-jwt integration tests', async t => { const domain = 'https://localhost/' nock(domain).get('/.well-known/jwks.json').reply(200, jwks) @@ -33,5 +33,5 @@ t.test('fast-jwt integration tests', async t => { }) const payload = await verifyWithPromise(token) - t.equal(payload.name, 'Jane Doe') + t.assert.equal(payload.name, 'Jane Doe') }) diff --git a/test/fastify-jwt-integrations.spec.js b/test/fastify-jwt-integrations.spec.js index 1c3960a..dad9061 100644 --- a/test/fastify-jwt-integrations.spec.js +++ b/test/fastify-jwt-integrations.spec.js @@ -1,6 +1,6 @@ 'use strict' -const t = require('tap') +const { beforeEach, afterEach, test } = require('node:test') const nock = require('nock') const Fastify = require('fastify') const fjwt = require('@fastify/jwt') @@ -8,16 +8,16 @@ const fjwt = require('@fastify/jwt') const { oidcConfig, jwks, token, domain } = require('./constants') const buildGetJwks = require('../src/get-jwks') -t.beforeEach(() => { +beforeEach(() => { nock.disableNetConnect() }) -t.afterEach(() => { +afterEach(() => { nock.cleanAll() nock.enableNetConnect() }) -t.test('@fastify/jwt integration tests', async t => { +test('@fastify/jwt integration tests', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, jwks) const fastify = Fastify() @@ -52,11 +52,11 @@ t.test('@fastify/jwt integration tests', async t => { }, }) - t.equal(response.statusCode, 200) - t.equal(response.body, 'Jane Doe') + t.assert.equal(response.statusCode, 200) + t.assert.equal(response.body, 'Jane Doe') }) -t.test('@fastify/jwt integration tests with providerDiscovery', async t => { +test('@fastify/jwt integration tests with providerDiscovery', async t => { nock(domain) .get('/.well-known/openid-configuration') .once() @@ -94,6 +94,6 @@ t.test('@fastify/jwt integration tests with providerDiscovery', async t => { }, }) - t.equal(response.statusCode, 200) - t.equal(response.body, 'Jane Doe') + t.assert.equal(response.statusCode, 200) + t.assert.equal(response.body, 'Jane Doe') }) diff --git a/test/getJwk.spec.js b/test/getJwk.spec.js index fceaeb9..f7f64b9 100644 --- a/test/getJwk.spec.js +++ b/test/getJwk.spec.js @@ -1,22 +1,22 @@ 'use strict' const nock = require('nock') -const t = require('tap') +const {beforeEach, afterEach, test, describe} = require('node:test') const { jwks, domain } = require('./constants') const buildGetJwks = require('../src/get-jwks') const { GetJwksError, errorCode } = require('../src/error') -t.beforeEach(() => { +beforeEach(() => { nock.disableNetConnect() }) -t.afterEach(() => { +afterEach(() => { nock.cleanAll() nock.enableNetConnect() }) -t.test('rejects if the request fails', async t => { +test('rejects if the request fails', async t => { nock(domain).get('/.well-known/jwks.json').reply(500, { msg: 'boom' }) const [{ alg, kid }] = jwks.keys @@ -28,32 +28,32 @@ t.test('rejects if the request fails', async t => { body: { msg: 'boom' }, } - await t.rejects(getJwks.getJwk({ domain, alg, kid }), expectedError) + await t.assert.rejects(getJwks.getJwk({ domain, alg, kid }), expectedError) }) -t.test('rejects if alg and kid do not match', async t => { +test('rejects if alg and kid do not match', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, jwks) const getJwks = buildGetJwks() - await t.rejects( + await t.assert.rejects( getJwks.getJwk({ domain, alg: 'NOT', kid: 'FOUND' }), - 'No matching JWK found in the set.', + new GetJwksError('JWK_NOT_FOUND', 'No matching JWK found in the set.'), ) }) -t.test('returns a jwk if alg and kid match', async t => { +test('returns a jwk if alg and kid match', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, jwks) const getJwks = buildGetJwks() const key = jwks.keys[0] const jwk = await getJwks.getJwk({ domain, alg: key.alg, kid: key.kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) }) -t.test('returns a jwk if alg and kid match and path is specified', async t => { +test('returns a jwk if alg and kid match and path is specified', async t => { nock(domain).get('/otherdir/jwks.json').reply(200, jwks) const getJwks = buildGetJwks({ jwksPath: '/otherdir/jwks.json' }) const key = jwks.keys[0] @@ -64,22 +64,22 @@ t.test('returns a jwk if alg and kid match and path is specified', async t => { kid: key.kid, }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) }) -t.test('returns a jwk if no alg is provided and kid match', async t => { +test('returns a jwk if no alg is provided and kid match', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, jwks) const getJwks = buildGetJwks() const key = jwks.keys[2] const jwk = await getJwks.getJwk({ domain, kid: key.kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) }) -t.test( +test( 'returns a jwk if no alg is provided and kid match but jwk has alg', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, jwks) @@ -88,12 +88,12 @@ t.test( const jwk = await getJwks.getJwk({ domain, kid: key.kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) }, ) -t.test('caches a successful response', async t => { +test('caches a successful response', async t => { nock(domain).get('/.well-known/jwks.json').once().reply(200, jwks) const getJwks = buildGetJwks() @@ -103,62 +103,62 @@ t.test('caches a successful response', async t => { await getJwks.getJwk({ domain, alg, kid }) const jwk = await getJwks.getJwk({ domain, alg, kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) }) -t.test('does not cache a failed response', async t => { +test('does not cache a failed response', async t => { nock(domain).get('/.well-known/jwks.json').once().reply(500, { msg: 'boom' }) nock(domain).get('/.well-known/jwks.json').once().reply(200, jwks) const [{ alg, kid }] = jwks.keys const getJwks = buildGetJwks() - await t.rejects(getJwks.getJwk({ domain, alg, kid })) - await t.resolves(getJwks.getJwk({ domain, alg, kid })) + await t.assert.rejects(getJwks.getJwk({ domain, alg, kid })) + await getJwks.getJwk({ domain, alg, kid }) }) -t.test('rejects if response is an empty object', async t => { +test('rejects if response is an empty object', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, {}) const getJwks = buildGetJwks() const [{ alg, kid }] = jwks.keys - return t.rejects( + return t.assert.rejects( getJwks.getJwk({ domain, alg, kid }), - 'No JWKS found in the response.', + new GetJwksError('NO_JWKS', 'No JWKS found in the response.'), ) }) -t.test('rejects if no JWKS are found in the response', async t => { +test('rejects if no JWKS are found in the response', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, { keys: [] }) const getJwks = buildGetJwks() const [{ alg, kid }] = jwks.keys - return t.rejects( + return t.assert.rejects( getJwks.getJwk({ domain, alg, kid }), - 'No JWKS found in the response.', + new GetJwksError('NO_JWKS', 'No JWKS found in the response.'), ) }) -t.test('supports domain without trailing slash', async t => { +test('supports domain without trailing slash', async t => { nock(domain).get('/.well-known/jwks.json').reply(200, jwks) const getJwks = buildGetJwks() const [{ alg, kid }] = jwks.keys const key = await getJwks.getJwk({ domain: 'https://localhost', alg, kid }) - t.ok(key) + t.assert.ok(key) }) -t.test('supports path without leading slash', async t => { +test('supports path without leading slash', async t => { nock(domain).get('/otherdir/jwks.json').reply(200, jwks) const getJwks = buildGetJwks({ jwksPath: 'otherdir/jwks.json' }) const [{ alg, kid }] = jwks.keys const key = await getJwks.getJwk({ domain: 'https://localhost', alg, kid }) - t.ok(key) + t.assert.ok(key) }) -t.test('does not execute concurrent requests', () => { +test('does not execute concurrent requests', () => { nock(domain).get('/.well-known/jwks.json').once().reply(200, jwks) const getJwks = buildGetJwks() @@ -170,7 +170,7 @@ t.test('does not execute concurrent requests', () => { ]) }) -t.test('returns a stale cached value if request fails', async t => { +test('returns a stale cached value if request fails', async t => { // allow 2 requests, third will throw an error nock(domain).get('/.well-known/jwks.json').twice().reply(200, jwks) nock(domain).get('/.well-known/jwks.json').once().reply(500, { boom: true }) @@ -201,11 +201,11 @@ t.test('returns a stale cached value if request fails', async t => { kid: key1.kid, }) - t.strictSame(key, key1) + t.assert.deepStrictEqual(key, key1) }) -t.test('allowed domains', async t => { - t.test('allows any domain by default', async t => { +describe('allowed domains', () => { + test('allows any domain by default', async t => { const domain = 'https://example.com' nock(domain).get('/.well-known/jwks.json').reply(200, jwks) @@ -214,7 +214,7 @@ t.test('allowed domains', async t => { const [{ alg, kid }] = jwks.keys - t.ok(await getJwks.getJwk({ domain, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain, alg, kid })) }) const allowedCombinations = [ @@ -229,7 +229,7 @@ t.test('allowed domains', async t => { ] allowedCombinations.forEach(([allowedDomain, domainFromToken]) => { - t.test( + test( `allows domain ${allowedDomain} requested with ${domainFromToken}`, async t => { nock(allowedDomain).get('/.well-known/jwks.json').reply(200, jwks) @@ -240,12 +240,12 @@ t.test('allowed domains', async t => { const [{ alg, kid }] = jwks.keys - t.ok(await getJwks.getJwk({ domain: domainFromToken, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain: domainFromToken, alg, kid })) }, ) }) - t.test('allows multiple domains', async t => { + test('allows multiple domains', async t => { const domain1 = 'https://example1.com' const domain2 = 'https://example2.com' @@ -256,11 +256,11 @@ t.test('allowed domains', async t => { const [{ alg, kid }] = jwks.keys - t.ok(await getJwks.getJwk({ domain: domain1, alg, kid })) - t.ok(await getJwks.getJwk({ domain: domain2, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain: domain1, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain: domain2, alg, kid })) }) - t.test('checks token issuer', async t => { + test('checks token issuer', async t => { const domain = 'https://example.com/realms/REALM_NAME' nock(domain).get('/.well-known/jwks.json').reply(200, jwks) @@ -275,10 +275,10 @@ t.test('allowed domains', async t => { const [{ alg, kid }] = jwks.keys - t.ok(await getJwks.getJwk({ domain, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain, alg, kid })) }) - t.test('forbids invalid issuer', async t => { + test('forbids invalid issuer', async t => { const getJwks = buildGetJwks({ checkIssuer: (issuer) => { const url = new URL(issuer) @@ -289,51 +289,51 @@ t.test('allowed domains', async t => { const [{ alg, kid }] = jwks.keys - return t.rejects( + return t.assert.rejects( getJwks.getJwk({ domain, alg, kid }), 'Issuer is not allowed.', ) }) - t.test('forbids domain outside of the allow list', async t => { + test('forbids domain outside of the allow list', async t => { const getJwks = buildGetJwks({ issuersWhitelist: ['https://example.com/'], }) const [{ alg, kid }] = jwks.keys - return t.rejects( + return t.assert.rejects( getJwks.getJwk({ domain, alg, kid }), - 'The domain is not allowed.', + new GetJwksError('DOMAIN_NOT_ALLOWED', 'The domain is not allowed.'), ) }) }) -t.test('timeout', async t => { +describe('timeout', () => { const domain = 'https://example.com' const [{ alg, kid }] = jwks.keys - t.beforeEach(() => - nock(domain).get('/.well-known/jwks.json').reply(200, jwks), - ) - let timeout - const buildGetJwks = t.mock('../src/get-jwks', { - 'node-fetch': (init, options) => { + + beforeEach(() => { + global.fetch = (url, options) => { timeout = options.timeout - return require('node-fetch')(init, options) - }, + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(jwks) + }) + } }) - t.test('timeout defaults to 5 seconds', async t => { + test('timeout defaults to 5 seconds', async t => { const getJwks = buildGetJwks() await getJwks.getJwk({ domain, alg, kid }) - t.equal(timeout, 5000) + t.assert.equal(timeout, 5000) }) - t.test('ensures that timeout is set to 10 seconds', async t => { - const getJwks = buildGetJwks({ timeout: 10000 }) + test('ensures that timeout is set to 10 seconds', async t => { + const getJwks = buildGetJwks({ fetchOptions: { timeout: 10000 } }) await getJwks.getJwk({ domain, alg, kid }) - t.equal(timeout, 10000) + t.assert.equal(timeout, 10000) }) }) diff --git a/test/getJwkDiscovery.spec.js b/test/getJwkDiscovery.spec.js index 2f7da60..00576b4 100644 --- a/test/getJwkDiscovery.spec.js +++ b/test/getJwkDiscovery.spec.js @@ -1,22 +1,22 @@ 'use strict' const nock = require('nock') -const t = require('tap') +const {beforeEach, afterEach, test, describe} = require('node:test') const { oidcConfig, jwks, domain } = require('./constants') const buildGetJwks = require('../src/get-jwks') const { errorCode, GetJwksError } = require('../src/error') -t.beforeEach(() => { +beforeEach(() => { nock.disableNetConnect() }) -t.afterEach(() => { +afterEach(() => { nock.cleanAll() nock.enableNetConnect() }) -t.test('rejects if the discovery request fails', async t => { +test('rejects if the discovery request fails', async t => { nock(domain) .get('/.well-known/openid-configuration') .reply(500, { msg: 'baam' }) @@ -29,10 +29,10 @@ t.test('rejects if the discovery request fails', async t => { code: errorCode.OPENID_CONFIGURATION_REQUEST_FAILED, body: { msg: 'baam' }, } - await t.rejects(getJwks.getJwk({ domain, alg, kid }), expectedError) + await t.assert.rejects(getJwks.getJwk({ domain, alg, kid }), expectedError) }) -t.test('rejects if the request fails', async t => { +test('rejects if the request fails', async t => { nock(domain).get('/.well-known/openid-configuration').reply(200, oidcConfig) nock(domain).get('/.well-known/certs').reply(500, { msg: 'boom' }) @@ -46,10 +46,10 @@ t.test('rejects if the request fails', async t => { } expectedError.body = { msg: 'boom' } - await t.rejects(getJwks.getJwk({ domain, alg, kid }), expectedError) + await t.assert.rejects(getJwks.getJwk({ domain, alg, kid }), expectedError) }) -t.test('returns a jwk if alg and kid match for discovery', async t => { +test('returns a jwk if alg and kid match for discovery', async t => { nock(domain).get('/.well-known/openid-configuration').reply(200, oidcConfig) nock(domain).get('/.well-known/certs').reply(200, jwks) const getJwks = buildGetJwks({ providerDiscovery: true }) @@ -57,11 +57,11 @@ t.test('returns a jwk if alg and kid match for discovery', async t => { const jwk = await getJwks.getJwk({ domain, alg: key.alg, kid: key.kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) }) -t.test( +test( 'returns a jwk if no alg is provided and kid match for discovery', async t => { nock(domain).get('/.well-known/openid-configuration').reply(200, oidcConfig) @@ -71,12 +71,12 @@ t.test( const jwk = await getJwks.getJwk({ domain, kid: key.kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) } ) -t.test( +test( 'returns a jwk if no alg is provided and kid match for discovery but jwk has alg', async t => { nock(domain).get('/.well-known/openid-configuration').reply(200, oidcConfig) @@ -86,12 +86,12 @@ t.test( const jwk = await getJwks.getJwk({ domain, kid: key.kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) } ) -t.test('caches a successful response for discovery', async t => { +test('caches a successful response for discovery', async t => { nock(domain).get('/.well-known/openid-configuration').reply(200, oidcConfig) nock(domain).get('/.well-known/certs').reply(200, jwks) @@ -102,11 +102,11 @@ t.test('caches a successful response for discovery', async t => { await getJwks.getJwk({ domain, alg, kid }) const jwk = await getJwks.getJwk({ domain, alg, kid }) - t.ok(jwk) - t.same(jwk, key) + t.assert.ok(jwk) + t.assert.deepStrictEqual(jwk, key) }) -t.test('does not cache a failed response for discovery', async t => { +test('does not cache a failed response for discovery', async t => { nock(domain) .get('/.well-known/openid-configuration') .twice() @@ -117,35 +117,35 @@ t.test('does not cache a failed response for discovery', async t => { const [{ alg, kid }] = jwks.keys const getJwks = buildGetJwks({ providerDiscovery: true }) - await t.rejects(getJwks.getJwk({ domain, alg, kid })) - await t.resolves(getJwks.getJwk({ domain, alg, kid })) + await t.assert.rejects(getJwks.getJwk({ domain, alg, kid })) + await getJwks.getJwk({ domain, alg, kid }) }) -t.test('rejects if response is an empty object for discovery', async t => { +test('rejects if response is an empty object for discovery', async t => { nock(domain).get('/.well-known/openid-configuration').reply(200, oidcConfig) nock(domain).get('/.well-known/certs').reply(200, {}) const getJwks = buildGetJwks({ providerDiscovery: true }) const [{ alg, kid }] = jwks.keys - return t.rejects( + return t.assert.rejects( getJwks.getJwk({ domain, alg, kid }), - 'No JWKS found in the response.' + new GetJwksError('NO_JWKS', 'No JWKS found in the response.') ) }) -t.test('rejects if no JWKS are found in the response', async t => { +test('rejects if no JWKS are found in the response', async t => { nock(domain).get('/.well-known/openid-configuration').reply(200, oidcConfig) nock(domain).get('/.well-known/certs').reply(200, { keys: [] }) const getJwks = buildGetJwks({ providerDiscovery: true }) const [{ alg, kid }] = jwks.keys - return t.rejects( + return t.assert.rejects( getJwks.getJwk({ domain, alg, kid }), - 'No JWKS found in the response.' + new GetJwksError('NO_JWKS', 'No JWKS found in the response.') ) }) -t.test('supports domain without trailing slash for discovery', async t => { +test('supports domain without trailing slash for discovery', async t => { nock(domain) .get('/.well-known/openid-configuration') .once() @@ -155,10 +155,10 @@ t.test('supports domain without trailing slash for discovery', async t => { const [{ alg, kid }] = jwks.keys const key = await getJwks.getJwk({ domain: 'https://localhost', alg, kid }) - t.ok(key) + t.assert.ok(key) }) -t.test('does not execute concurrent requests for discovery', () => { +test('does not execute concurrent requests for discovery', () => { nock(domain) .get('/.well-known/openid-configuration') .once() @@ -174,7 +174,7 @@ t.test('does not execute concurrent requests for discovery', () => { ]) }) -t.test( +test( 'returns a stale cached value if request fails for discovery', async t => { // allow 2 requests, third will throw an error @@ -211,12 +211,12 @@ t.test( kid: key1.kid, }) - t.strictSame(key, key1) + t.assert.deepStrictEqual(key, key1) } ) -t.test('allowed domains for discovery', async t => { - t.test('allows any domain by default for discovery ', async t => { +describe('allowed domains for discovery', () => { + test('allows any domain by default for discovery ', async t => { const domain = 'https://example.com' nock(domain) @@ -230,7 +230,7 @@ t.test('allowed domains for discovery', async t => { const [{ alg, kid }] = jwks.keys - t.ok(await getJwks.getJwk({ domain, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain, alg, kid })) }) const allowedCombinations = [ @@ -245,7 +245,7 @@ t.test('allowed domains for discovery', async t => { ] allowedCombinations.forEach(([allowedIssuer, domainFromToken]) => { - t.test( + test( `allows domain ${allowedIssuer} requested with ${domainFromToken} for discovery`, async t => { const allowedIssuerSlash = allowedIssuer.endsWith('/') @@ -265,12 +265,12 @@ t.test('allowed domains for discovery', async t => { const [{ alg, kid }] = jwks.keys - t.ok(await getJwks.getJwk({ domain: domainFromToken, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain: domainFromToken, alg, kid })) } ) }) - t.test('allows multiple domains for discovery', async t => { + test('allows multiple domains for discovery', async t => { const domain1 = 'https://example1.com' const domain2 = 'https://example2.com' nock(domain1) @@ -295,11 +295,11 @@ t.test('allowed domains for discovery', async t => { const [{ alg, kid }] = jwks.keys - t.ok(await getJwks.getJwk({ domain: domain1, alg, kid })) - t.ok(await getJwks.getJwk({ domain: domain2, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain: domain1, alg, kid })) + t.assert.ok(await getJwks.getJwk({ domain: domain2, alg, kid })) }) - t.test('forbids domain outside of the allow list', async t => { + test('forbids domain outside of the allow list', async t => { const getJwks = buildGetJwks({ providerDiscovery: true, issuersWhitelist: ['https://example.com/'], @@ -307,9 +307,9 @@ t.test('allowed domains for discovery', async t => { const [{ alg, kid }] = jwks.keys - return t.rejects( + return t.assert.rejects( getJwks.getJwk({ domain, alg, kid }), - 'The domain is not allowed.' + new GetJwksError('DOMAIN_NOT_ALLOWED', 'The domain is not allowed.') ) }) }) diff --git a/test/getJwksUri.spec.js b/test/getJwksUri.spec.js index b304617..635ac9f 100644 --- a/test/getJwksUri.spec.js +++ b/test/getJwksUri.spec.js @@ -1,6 +1,6 @@ 'use strict' -const t = require('tap') +const { beforeEach, afterEach, test, describe } = require('node:test') const nock = require('nock') const { domain } = require('./constants') @@ -8,16 +8,16 @@ const { domain } = require('./constants') const buildGetJwks = require('../src/get-jwks') const { GetJwksError, errorCode } = require('../src/error') -t.beforeEach(() => { +beforeEach(() => { nock.disableNetConnect() }) -t.afterEach(() => { +afterEach(() => { nock.cleanAll() nock.enableNetConnect() }) -t.test('throw error if the discovery request fails', async t => { +test('throw error if the discovery request fails', async t => { nock(domain) .get('/.well-known/openid-configuration') .reply(500, { msg: 'baam' }) @@ -29,10 +29,10 @@ t.test('throw error if the discovery request fails', async t => { body: { msg: 'baam' }, } - await t.rejects(getJwks.getJwksUri(domain), expectedError) + await t.assert.rejects(getJwks.getJwksUri(domain), expectedError) }) -t.test( +test( 'throw error if the discovery request has no jwks_uri property', async t => { nock(domain) @@ -45,34 +45,32 @@ t.test( code: errorCode.NO_JWKS_URI, } - await t.rejects(getJwks.getJwksUri(domain), expectedError) + await t.assert.rejects(getJwks.getJwksUri(domain), expectedError) }, ) -t.test('timeout', async t => { - t.beforeEach(() => - nock(domain) - .get('/.well-known/openid-configuration') - .reply(200, { jwks_uri: 'http://localhost' }), - ) - +describe('timeout', () => { let timeout - const buildGetJwks = t.mock('../src/get-jwks', { - 'node-fetch': (input, options) => { + + beforeEach(() => { + global.fetch = (url, options) => { timeout = options.timeout - return require('node-fetch')(input, options) - }, + return Promise.resolve({ + ok: true, + json: () => Promise.resolve({ jwks_uri: 'http://localhost' }) + }) + } }) - t.test('timeout defaults to 5 seconds', async t => { + test('timeout defaults to 5 seconds', async t => { const getJwks = buildGetJwks() await getJwks.getJwksUri(domain) - t.equal(timeout, 5000) + t.assert.equal(timeout, 5000) }) - t.test('ensures that timeout is set to 10 seconds', async t => { - const getJwks = buildGetJwks({ timeout: 10000 }) + test('ensures that timeout is set to 10 seconds', async t => { + const getJwks = buildGetJwks({fetchOptions: {timeout: 10000 }}) await getJwks.getJwksUri(domain) - t.equal(timeout, 10000) + t.assert.equal(timeout, 10000) }) }) diff --git a/test/getPublicKey.spec.js b/test/getPublicKey.spec.js index ca4a2ce..d2b9a88 100644 --- a/test/getPublicKey.spec.js +++ b/test/getPublicKey.spec.js @@ -1,13 +1,13 @@ 'use strict' -const t = require('tap') +const {test} = require('node:test') const jwkToPem = require('jwk-to-pem') const sinon = require('sinon') const { jwks } = require('./constants') const buildGetJwks = require('../src/get-jwks') -t.test('it provides the result of getJwk to jwkToPem', async t => { +test('it provides the result of getJwk to jwkToPem', async t => { const getJwks = buildGetJwks() const [jwk] = jwks.keys @@ -18,14 +18,14 @@ t.test('it provides the result of getJwk to jwkToPem', async t => { const pem = await getJwks.getPublicKey(signature) - t.equal(pem, jwkToPem(jwk)) + t.assert.equal(pem, jwkToPem(jwk)) sinon.assert.calledOnceWithExactly(getJwkStub, signature) }) -t.test('it rejects if getJwk rejects', t => { +test('it rejects if getJwk rejects', t => { const getJwks = buildGetJwks() sinon.stub(getJwks, 'getJwk').rejects(new Error('boom')) - return t.rejects(getJwks.getPublicKey('whatever'), 'boom') + return t.assert.rejects(getJwks.getPublicKey('whatever'), new Error('boom')) }) diff --git a/test/getPublicKeyDiscovery.spec.js b/test/getPublicKeyDiscovery.spec.js index e3acac3..132638a 100644 --- a/test/getPublicKeyDiscovery.spec.js +++ b/test/getPublicKeyDiscovery.spec.js @@ -1,13 +1,13 @@ 'use strict' -const t = require('tap') +const {test} = require('node:test') const jwkToPem = require('jwk-to-pem') const sinon = require('sinon') const { jwks } = require('./constants') const buildGetJwks = require('../src/get-jwks') -t.test( +test( 'it provides the result of getJwk to jwkToPem for discovery', async t => { const getJwks = buildGetJwks({ providerDiscovery: true }) @@ -20,15 +20,15 @@ t.test( const pem = await getJwks.getPublicKey(signature) - t.equal(pem, jwkToPem(jwk)) + t.assert.equal(pem, jwkToPem(jwk)) sinon.assert.calledOnceWithExactly(getJwkStub, signature) } ) -t.test('it rejects if getJwk rejects for discovery', t => { +test('it rejects if getJwk rejects for discovery', t => { const getJwks = buildGetJwks({ providerDiscovery: true }) sinon.stub(getJwks, 'getJwk').rejects(new Error('boom')) - return t.rejects(getJwks.getPublicKey('whatever'), 'boom') + return t.assert.rejects(getJwks.getPublicKey('whatever'), new Error('boom')) })