From 5b2783acabd529aa9c906c23830a5401a3a4e062 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 11 Feb 2021 11:07:01 -0800 Subject: [PATCH] Simplify request wrapper --- README.md | 13 +-- index.d.ts | 33 ++----- package-lock.json | 202 +++----------------------------------- package.json | 11 +-- src/JwksClient.js | 26 ++--- src/wrappers/cache.js | 3 +- src/wrappers/request.js | 81 ++++++++------- tests/jwksClient.tests.js | 138 +++----------------------- tests/request.tests.js | 151 ++++++++++++++++++---------- 9 files changed, 198 insertions(+), 460 deletions(-) diff --git a/README.md b/README.md index 0515747e..11d82791 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,7 @@ const client = jwksClient({ strictSsl: true, // Default value jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json', requestHeaders: {}, // Optional - requestAgentOptions: {}, // Optional - timeout: 30000, // Defaults to 30s - proxy: '[protocol]://[username]:[pass]@[address]:[port]', // Optional + timeout: 30000 // Defaults to 30s }); const kid = 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg'; @@ -95,13 +93,14 @@ certificate authority to establish TLS communication with the `jwks_uri`. ```js const jwksClient = require("jwks-rsa"); +const https = require('https'); const client = jwksClient({ strictSsl: true, // Default value jwksUri: 'https://my-enterprise-id-provider/.well-known/jwks.json', requestHeaders: {}, // Optional - requestAgentOptions: { + requestAgent: new https.Agent({ ca: fs.readFileSync(caFile) - } + }) }); ``` @@ -110,9 +109,7 @@ documentation](https://github.com/request/request#using-optionsagentoptions). ### Proxy configuration -There are two ways to configure the usage of a proxy: - - Provide the ```proxy``` option when initialiting the client as shown above - - Provide the ```HTTP_PROXY```, ```HTTPS_PROXY``` and ```NO_PROXY``` environment variables +You can configure using a proxy with a [custom http(s) agent](https://github.com/TooTallNate/node-https-proxy-agent). ### Loading keys from local file, environment variable, or other externals diff --git a/index.d.ts b/index.d.ts index c65932ec..100dee14 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,12 +1,10 @@ import { SecretCallback, SecretCallbackLong } from 'express-jwt'; -import { AgentOptions as HttpAgentOptions } from 'http'; -import { AgentOptions as HttpsAgentOptions } from 'https'; -declare function JwksRsa(options: JwksRsa.ClientOptions): JwksRsa.JwksClient; +declare function JwksRsa(options: JwksRsa.Options): JwksRsa.JwksClient; declare namespace JwksRsa { class JwksClient { - constructor(options: ClientOptions | ClientOptionsWithObject); + constructor(options: Options | OptionsWithObject); getKeys(cb: (err: Error | null, keys: unknown) => void): void; getKeysAsync(): Promise; @@ -20,7 +18,7 @@ declare namespace JwksRsa { [key: string]: string; } - interface ClientOptions { + interface Options { jwksUri: string; rateLimit?: boolean; cache?: boolean; @@ -28,14 +26,14 @@ declare namespace JwksRsa { cacheMaxAge?: number; jwksRequestsPerMinute?: number; proxy?: string; - strictSsl?: boolean; requestHeaders?: Headers; timeout?: number; - requestAgentOptions?: HttpAgentOptions | HttpsAgentOptions; + requestAgent?: any; + fetcher?(string): Promise; getKeysInterceptor?(cb: (err: Error | null, keys: SigningKey[]) => void): void; } - interface ClientOptionsWithObject extends Omit { + interface OptionsWithObject extends Omit { /** * @deprecated jwksObject should not be used. Use getKeysInterceptor as a replacement */ @@ -48,19 +46,6 @@ declare namespace JwksRsa { publicKey: string; } - interface Options { - jwksUri: string; - rateLimit?: boolean; - cache?: boolean; - cacheMaxEntries?: number; - cacheMaxAge?: number; - jwksRequestsPerMinute?: number; - strictSsl?: boolean; - requestHeaders?: Headers; - requestAgentOptions?: HttpAgentOptions | HttpsAgentOptions; - handleSigningKeyError?(err: Error, cb: (err: Error) => void): any; - } - interface RsaSigningKey { kid: string; getPublicKey(): string; @@ -73,13 +58,13 @@ declare namespace JwksRsa { function passportJwtSecret(options: ExpressJwtOptions): SecretCallback; - interface ExpressJwtOptions extends ClientOptions { + interface ExpressJwtOptions extends Options { handleSigningKeyError?: (err: Error | null, cb: (err: Error | null) => void) => void; } function hapiJwt2Key(options: HapiJwtOptions): (decodedToken: DecodedToken, cb: HapiCallback) => void; - interface HapiJwtOptions extends ClientOptions { + interface HapiJwtOptions extends Options { handleSigningKeyError?: (err: Error | null, cb: HapiCallback) => void; } @@ -98,7 +83,7 @@ declare namespace JwksRsa { function koaJwtSecret(options: KoaJwtOptions): (header: TokenHeader) => Promise; - interface KoaJwtOptions extends ClientOptions { + interface KoaJwtOptions extends Options { handleSigningKeyError?(err: Error | null): Promise; } diff --git a/package-lock.json b/package-lock.json index 31a23161..bf824f92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -535,11 +535,6 @@ "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha1-zLkURTYBeaBOf+av94wA/8Hur4I=" - }, "@types/body-parser": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", @@ -617,12 +612,12 @@ "dev": true }, "@types/nock": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@types/nock/-/nock-10.0.3.tgz", - "integrity": "sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz", + "integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==", "dev": true, "requires": { - "@types/node": "*" + "nock": "*" } }, "@types/node": { @@ -683,14 +678,6 @@ "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", "dev": true }, - "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha1-gIAH5OWGfeywq2qy+Sj721pZbbQ=", - "requires": { - "debug": "4" - } - }, "aggregate-error": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", @@ -772,55 +759,6 @@ "sprintf-js": "~1.0.2" } }, - "args": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", - "integrity": "sha1-S/KY35CkeZoJUhNixXknjML912E=", - "dev": true, - "requires": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha1-AylVJ9WL081Kp1Nj81sujZe+L0I=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", @@ -2076,12 +2014,6 @@ } } }, - "basic-auth-parser": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz", - "integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE=", - "dev": true - }, "binary-extensions": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz", @@ -2267,12 +2199,6 @@ "integrity": "sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=", "dev": true }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -4063,12 +3989,6 @@ "integrity": "sha1-T5RBKoLbMvNuOwuXQfipf+sDH34=", "dev": true }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -4286,25 +4206,6 @@ "toidentifier": "1.0.0" } }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha1-ioyO9/WTLM+VPClsqCkblap0qjo=", - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha1-4qkFQqu2inYuCghQ9sntrf2FBrI=", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5077,12 +4978,6 @@ "integrity": "sha1-ud83XitNowQ5GNSGIlIMLAt58DI=", "dev": true }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -5491,12 +5386,6 @@ } } }, - "mri": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", - "integrity": "sha1-fLHdG5tAkF8frAU6viW2cg9EdEo=", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5577,60 +5466,15 @@ "dev": true }, "nock": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", - "integrity": "sha1-5tkO56aLjPwqt/YSfn2Zqn0T0RE=", + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.7.tgz", + "integrity": "sha512-WBz73VYIjdbO6BwmXODRQLtn7B5tldA9pNpWJe5QTtTEscQlY5KXU4srnGzBOK2fWakkXj69gfTnXGzmrsaRWw==", "dev": true, "requires": { - "chai": "^4.1.2", "debug": "^4.1.0", - "deep-equal": "^1.0.0", "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.5", - "mkdirp": "^0.5.0", - "propagate": "^1.0.0", - "qs": "^6.5.1", - "semver": "^5.5.0" - }, - "dependencies": { - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha1-dgqnLPION5XoSxKHfODoNzeqKeU=", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=", - "dev": true - } + "lodash.set": "^4.3.2", + "propagate": "^2.0.0" } }, "node-environment-flags": { @@ -6280,12 +6124,6 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true - }, "pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", @@ -6385,22 +6223,11 @@ "dev": true }, "propagate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", - "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, - "proxy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz", - "integrity": "sha1-4M++EcCnqLI4/S1xNN5OKGdXjn8=", - "dev": true, - "requires": { - "args": "5.0.1", - "basic-auth-parser": "0.0.2", - "debug": "^4.1.1" - } - }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -6411,11 +6238,6 @@ "ipaddr.js": "1.9.0" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", diff --git a/package.json b/package.json index 0f5704af..50b105b7 100644 --- a/package.json +++ b/package.json @@ -11,18 +11,14 @@ "@types/express-jwt": "0.0.42", "axios": "^0.21.1", "debug": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", "jose": "^2.0.2", "limiter": "^1.1.5", - "lru-memoizer": "^2.1.2", - "ms": "^2.1.2", - "proxy-from-env": "^1.1.0" + "lru-memoizer": "^2.1.2" }, "devDependencies": { "@types/chai": "^4.2.11", "@types/mocha": "^5.2.7", - "@types/nock": "^10.0.3", + "@types/nock": "^11.0.0", "@types/node": "^14.14.12", "babel-cli": "^6.9.0", "babel-core": "^6.9.0", @@ -38,11 +34,10 @@ "koa": "^2.12.1", "koa-jwt": "^3.6.0", "mocha": "^6.2.3", - "nock": "^10.0.6", + "nock": "^13.0.0", "nyc": "^15.1.0", "passport": "^0.4.1", "passport-jwt": "^4.0.0", - "proxy": "^1.0.2", "rimraf": "^2.7.1", "supertest": "^3.4.2", "ts-node": "^8.10.2", diff --git a/src/JwksClient.js b/src/JwksClient.js index 9ea83f79..911d4492 100644 --- a/src/JwksClient.js +++ b/src/JwksClient.js @@ -50,23 +50,17 @@ export class JwksClient { this.logger(`Fetching keys from '${this.options.jwksUri}'`); request({ uri: this.options.jwksUri, - strictSSL: this.options.strictSsl, headers: this.options.requestHeaders, - agentOptions: this.options.requestAgentOptions, - proxy: this.options.proxy, - timeout: this.options.timeout - }, (err, res) => { - if (err) { - const errorResponse = err.response; - this.logger('Failure:', errorResponse && errorResponse.data || err); - if (errorResponse) { - return cb(new JwksError(errorResponse.data || errorResponse.statusText || `Http Error ${errorResponse.status}`)); - } - return cb(err); - } - - this.logger('Keys:', res.data.keys); - return cb(null, res.data.keys); + agent: this.options.requestAgent, + timeout: this.options.timeout, + fetcher: this.options.fetcher + }).then((res) => { + this.logger('Keys:', res.keys); + return cb(null, res.keys); + }).catch((err) => { + const { errorMsg } = err; + this.logger('Failure:', errorMsg || err); + return cb(errorMsg ? new JwksError(errorMsg) : err); }); } diff --git a/src/wrappers/cache.js b/src/wrappers/cache.js index 9779e7bb..2028ed53 100644 --- a/src/wrappers/cache.js +++ b/src/wrappers/cache.js @@ -1,8 +1,7 @@ -import ms from 'ms'; import debug from 'debug'; import memoizer from 'lru-memoizer'; -export default function(client, { cacheMaxEntries = 5, cacheMaxAge = ms('10m') } = options) { +export default function(client, { cacheMaxEntries = 5, cacheMaxAge = 600000 } = options) { const logger = debug('jwks'); const getSigningKey = client.getSigningKey; diff --git a/src/wrappers/request.js b/src/wrappers/request.js index cf140e62..2c243762 100644 --- a/src/wrappers/request.js +++ b/src/wrappers/request.js @@ -1,40 +1,53 @@ + import http from 'http'; import https from 'https'; -import url from 'url'; -import httpProxyAgent from 'http-proxy-agent'; -import httpsProxyAgent from 'https-proxy-agent'; -import { request } from 'axios'; -import { getProxyForUrl } from 'proxy-from-env'; +import urlUtil from 'url'; -export default function(options, cb) { - const requestOptions = { - url: options.uri, - headers: options.headers, - timeout: options.timeout - }; +export default (options) => { + if (options.fetcher) { + return options.fetcher(options.uri); + } - const proxyUrl = options.proxy || getProxyForUrl(options.uri); - if (proxyUrl || options.agentOptions || options.strictSSL != undefined) { - const agentOptions = { - ...(options.strictSSL != undefined) && { rejectUnauthorized: options.strictSSL }, - ...(options.headers && { headers: options.headers }), - ...options.agentOptions - }; + return new Promise((resolve, reject) => { + const { + host, + path, + port, + protocol + } = urlUtil.parse(options.uri); - if (proxyUrl) { - // Axios proxy workaround: https://github.com/axios/axios/issues/2072 - const proxyOptions = url.parse(proxyUrl); - requestOptions.proxy = false; //proxyParsed - const proxyAgentOptions = { ...agentOptions, ...proxyOptions }; - requestOptions.httpAgent = new httpProxyAgent(proxyAgentOptions); - requestOptions.httpsAgent = new httpsProxyAgent(proxyAgentOptions); - } else { - requestOptions.httpAgent = new http.Agent(agentOptions); - requestOptions.httpsAgent = new https.Agent(agentOptions); - } - } + const requestOptions = { + host, + path, + port, + method: 'GET', + headers: { + 'Content-Type': 'application/json', + ...options.headers + }, + ...(options.timeout && { timeout: options.timeout }), + ...(options.agent && { agent: options.agent }) + }; - request(requestOptions) - .then(response => cb(null, response)) - .catch(err => cb(err)); -} + const httpRequestLib = protocol === 'https:' ? https : http; + httpRequestLib.request(requestOptions, (res) => { + let rawData = ''; + res.setEncoding('utf8'); + res.on('data', (chunk) => { rawData += chunk; }); + res.on('end', () => { + if (res.statusCode < 200 || res.statusCode >= 300) { + const errorMsg = res.body && (res.body.message || res.body) || res.statusMessage || `Http Error ${res.statusCode}`; + reject({ errorMsg }); + } else { + try { + resolve(rawData && JSON.parse(rawData)); + } catch (error) { + reject(error); + } + } + }); + }) + .on('error', (e) => reject(e)) + .end(); + }); +}; diff --git a/tests/jwksClient.tests.js b/tests/jwksClient.tests.js index d674c384..6d6dc111 100644 --- a/tests/jwksClient.tests.js +++ b/tests/jwksClient.tests.js @@ -1,5 +1,5 @@ import http from 'http'; -import Proxy from 'proxy'; + import nock from 'nock'; import { expect } from 'chai'; @@ -18,7 +18,7 @@ describe('JwksClient', () => { it('should handle errors', done => { nock(jwksHost) .get('/.well-known/jwks.json') - .reply(500, 'Unknown Server Error'); + .reply(500); const client = new JwksClient({ jwksUri: `${jwksHost}/.well-known/jwks.json` @@ -26,7 +26,7 @@ describe('JwksClient', () => { client.getKeys(err => { expect(err).not.to.be.null; - expect(err.message).to.equal('Unknown Server Error'); + expect(err.message).to.equal('Http Error 500'); done(); }); }); @@ -78,44 +78,6 @@ describe('JwksClient', () => { }); }); - it('should set request agentOptions when provided', done => { - nock(jwksHost) - .get('/.well-known/jwks.json') - .reply(function() { - expect(this.req.agentOptions).not.to.be.null; - expect(this.req.agentOptions['ca']).to.be.equal('loadCA()'); - return 200; - }); - - const client = new JwksClient({ - jwksUri: `${jwksHost}/.well-known/jwks.json`, - requestAgentOptions: { - ca: 'loadCA()' - } - }); - - client.getKeys(() => { - done(); - }); - }); - - it('should not set request agentOptions by default', done => { - nock(jwksHost) - .get('/.well-known/jwks.json') - .reply(function() { - expect(this.req).to.not.have.property('agentOptions'); - return 200; - }); - - const client = new JwksClient({ - jwksUri: `${jwksHost}/.well-known/jwks.json` - }); - - client.getKeys(() => { - done(); - }); - }); - it('should send extra header', done => { nock(jwksHost) .get('/.well-known/jwks.json') @@ -163,7 +125,7 @@ describe('JwksClient', () => { .get('/.well-known/jwks.json') .reply(function() { expect(this.req.headers).not.to.be.null; - expect(this.req.headers['accept']).not.to.be.undefined; + expect(this.req.headers['content-type']).not.to.be.undefined; expect(this.req.headers['host']).not.to.be.undefined; expect(Object.keys(this.req.headers).length).to.be.equal(2); return ( @@ -197,89 +159,13 @@ describe('JwksClient', () => { done(); }); }); - - describe('when using a proxy', () => { - let server; - let proxy; - let proxyPort; - let serverPort; - let serverAddress; - let proxyAddress; - - before((done) => { - server = http.createServer(); - server.listen(() => { - serverPort = server.address().port; - serverAddress = `http://localhost:${serverPort}`; - done(); - }); - }); - - before((done) => { - proxy = Proxy(); - proxy.listen(() => { - proxyPort = proxy.address().port; - proxyAddress = `http://localhost:${proxyPort}`; - done(); - }); - }); - - after((done) => { - server.once('close', () => done()); - server.close(); - }); - - after((done) => { - proxy.once('close', () => done()); - proxy.close(); - }); - - it('should properly handle a proxied request', (done) => { - const expectedKeys = { - keys: [ - { - alg: 'RS256', - kty: 'RSA', - use: 'sig', - x5c: [ 'pk1' ], - kid: 'ABC' - }, - { - alg: 'RS256', - kty: 'RSA', - use: 'sig', - x5c: [], - kid: '123' - } - ] - }; - server.once('request', (req, res) => res.end(JSON.stringify(expectedKeys))); - - const client = new JwksClient({ jwksUri: serverAddress, proxy: proxyAddress }); - client.getKeys((err, keys) => { - expect(keys).to.eql(expectedKeys.keys); - done(); - }); - }); - - it('should fail when proxy address is not reachable', (done) => { - proxyAddress = 'http://wrongAddress'; - server.once('request', (req, res) => res.end(JSON.stringify(expectedKeys))); - - const client = new JwksClient({ jwksUri: serverAddress, proxy: proxyAddress }); - client.getKeys((err) => { - expect(err.code).to.eql('ENOTFOUND'); - done(); - }); - }); - }); }); describe('#getKeysAsync', () => { it('should handle errors when async', done => { nock(jwksHost) .get('/.well-known/jwks.json') - .reply(500, 'Unknown Server Error'); + .reply(500); const client = new JwksClient({ jwksUri: `${jwksHost}/.well-known/jwks.json` @@ -288,7 +174,7 @@ describe('JwksClient', () => { client.getKeysAsync() .catch(err => { expect(err).not.to.be.null; - expect(err.message).to.equal('Unknown Server Error'); + expect(err.message).to.equal('Http Error 500'); done(); }); }); @@ -336,7 +222,7 @@ describe('JwksClient', () => { it('should handle errors', done => { nock(jwksHost) .get('/.well-known/jwks.json') - .reply(500, 'Unknown Server Error'); + .reply(500); const client = new JwksClient({ jwksUri: `${jwksHost}/.well-known/jwks.json` @@ -344,7 +230,7 @@ describe('JwksClient', () => { client.getSigningKeys(err => { expect(err).not.to.be.null; - expect(err.message).to.equal('Unknown Server Error'); + expect(err.message).to.equal('Http Error 500'); done(); }); }); @@ -623,7 +509,7 @@ describe('JwksClient', () => { it('should handle errors when async', done => { nock(jwksHost) .get('/.well-known/jwks.json') - .reply(500, 'Unknown Server Error'); + .reply(500); const client = new JwksClient({ jwksUri: `${jwksHost}/.well-known/jwks.json` @@ -632,7 +518,7 @@ describe('JwksClient', () => { client.getSigningKeysAsync() .catch(err => { expect(err).not.to.be.null; - expect(err.message).to.equal('Unknown Server Error'); + expect(err.message).to.equal('Http Error 500'); done(); }); }); @@ -696,7 +582,7 @@ describe('JwksClient', () => { it('should handle error when async', done => { nock(jwksHost) .get('/.well-known/jwks.json') - .reply(500, 'Unknown Server Error'); + .reply(500); const client = new JwksClient({ jwksUri: `${jwksHost}/.well-known/jwks.json` @@ -705,7 +591,7 @@ describe('JwksClient', () => { client.getSigningKeyAsync('') .catch(err => { expect(err).not.to.be.null; - expect(err.message).to.equal('Unknown Server Error'); + expect(err.message).to.equal('Http Error 500'); done(); }); }); diff --git a/tests/request.tests.js b/tests/request.tests.js index 65ae0307..ff82a802 100644 --- a/tests/request.tests.js +++ b/tests/request.tests.js @@ -1,69 +1,116 @@ -import axios from 'axios'; import { expect } from 'chai'; - +import nock from 'nock'; import request from '../src/wrappers/request'; describe('Request wrapper tests', () => { - - const uri = 'https://foo/bar'; - const originalAxiosRequest = axios.request; - const mockedAxiosRequest = (options) => { - return Promise.resolve(options); - }; - before(() => { - axios.request = mockedAxiosRequest; + const jwksHost = 'http://my-authz-server'; + const uri = `${jwksHost}/.well-known/jwks.json`; + const jwksJson = { + keys: [ + { + alg: 'RS256', + kty: 'RSA', + use: 'sig', + x5c: [ 'pk1' ], + kid: 'ABC' + }, + { + alg: 'RS256', + kty: 'RSA', + use: 'sig', + x5c: [], + kid: '123' + } + ] + }; + + beforeEach(() => { + nock.cleanAll(); }); - after(() => { - axios.request = originalAxiosRequest; + it('should make a successful request to specified uri', (done) => { + nock(jwksHost) + .get('/.well-known/jwks.json') + .reply(200, jwksJson); + + request({ uri }) + .then((data) => { + expect(data).to.deep.equal(jwksJson); + done(); + }) + .catch(done) }); - describe('default export', () => { - describe('should pass through strictSSL option properly', () => { - it('should create agentOptions if strictSSL === true', (done) => { - request({ uri, strictSSL: true }, (err, options) => { - expect(options.httpsAgent).to.be.defined; - expect(options.httpsAgent.options.rejectUnauthorized).to.be.true; - done(err); - }); + it('should handle errors', (done) => { + const errorMsg = 'Server response error!!'; + nock(jwksHost) + .get('/.well-known/jwks.json') + .reply(500, function () { this.req.response.statusMessage = errorMsg; }) + + request({ uri }) + .then(() => done('Shoul dhave thrown error')) + .catch((err) => { + expect(err.errorMsg).to.eql(errorMsg); + done(); }); + }); - it('should create agentOptions if strictSSL === false', (done) => { - request({ uri, strictSSL: false }, (err, options) => { - expect(options.httpsAgent).to.be.defined; - expect(options.httpsAgent.options.rejectUnauthorized).to.be.false; - done(err); - }); - }); + it('should set a timeout when specified', (done) => { + const timeout = 999999; - it('should not create agentOptions if strictSSL === undefined', (done) => { - request({ uri }, (err, options) => { - expect(options.httpsAgent).to.be.undefined; - done(err); - }); - }); + nock(jwksHost) + .get('/.well-known/jwks.json') + .reply(200, function () { + const { options } = this.req; + expect(options.timeout).to.equal(timeout) + done(); + }) + + request({ uri, timeout }); + }); - const expectAgent = (agent, host, port, protocol) => { - expect(agent.proxy.host).to.equal(host); - expect(agent.proxy.port).to.equal(port); - expect(agent.proxy.protocol).to.equal(protocol); - }; + it('should set modify headers when specified in options', (done) => { + const headers = { 'test': '123' }; - it('should pass the "proxy" option', (done) => { - request({ uri, proxy: 'http://dummy-proxy.org:123' }, (err, options) => { - expectAgent(options.httpsAgent, 'dummy-proxy.org', 123, 'http:'); - done(err); - }); - }); + nock(jwksHost) + .get('/.well-known/jwks.json') + .reply(200, function () { + const { options } = this.req; + expect(options.headers.test).to.equal(headers.test) + done(); + }) + + request({ uri, headers }); + }); - it('should read the proxy config from the environment', (done) => { - process.env.HTTPS_PROXY = 'http://another-dummy-proxy.org:456'; - request({ uri }, (err, options) => { - expectAgent(options.httpsAgent, 'another-dummy-proxy.org', 456, 'http:'); - process.env.HTTPS_PROXY = undefined; - done(err); - }); - }); + it('should set an agent when specified', (done) => { + const agent = { testAgent: true }; + + nock(jwksHost) + .get('/.well-known/jwks.json') + .reply(200, function () { + const { options } = this.req; + expect(options.agent).to.equal(agent) + done(); + }) + + request({ uri, agent }); + }); + + describe('when fetcher is specified', () => { + it('should use the specified fetcher to make the request', (done) => { + request({ + uri, + fetcher: (url) => new Promise((resolve, reject) => { + expect(url).to.equal(uri) + resolve(jwksJson); + }) + }) + .then((data) => { + expect(data).to.deep.equal(jwksJson); + done(); + }) + .catch(done) }); }); });