From 4e80c87a49cca988b1148e126cc4ac40b211ea47 Mon Sep 17 00:00:00 2001 From: "denys.oblohin" Date: Wed, 23 Feb 2022 16:57:48 +0200 Subject: [PATCH 1/5] Adds httpsProxy --- README.md | 17 +++++++++++ package.json | 5 ++-- src/client.js | 1 + src/http.js | 8 +++++ src/types/client.d.ts | 2 ++ src/types/http.d.ts | 3 ++ src/types/request-options.d.ts | 2 ++ test/jest/http.test.js | 53 ++++++++++++++++++++++++++++++++++ 8 files changed, 89 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a45413a82..4bcf96ec8 100644 --- a/README.md +++ b/README.md @@ -806,6 +806,23 @@ const client = new okta.Client({ }) ``` +## Proxy + +If you need to use a proxy, you can configure it with `httpsProxy` property. +```javascript +const okta = require('@okta/okta-sdk-nodejs'); + +const client = new okta.Client({ + orgUrl: 'https://dev-1234.oktapreview.com/', + token: 'xYzabc', // Obtained from Developer Dashboard + httpsProxy: 'http://proxy.example.net:8080/' +}); +``` + +When the proxy configuration is not overridden as shown above, Okta client relies on the proxy configuration defined by standard environment variable `https_proxy` or its uppercase variant `HTTPS_PROXY`. + +To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syntax. + ## TypeScript usage ### 4.5.x diff --git a/package.json b/package.json index 719de15f6..f8eee67c9 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "dependencies": { "deep-copy": "^1.4.2", "form-data": "^4.0.0", + "https-proxy-agent": "^5.0.0", "isomorphic-fetch": "^3.0.0", "js-yaml": "^4.1.0", "lodash": "^4.17.20", @@ -42,6 +43,7 @@ "safe-flat": "^2.0.2" }, "devDependencies": { + "@faker-js/faker": "^5.5.3", "@okta/openapi": "^2.10.0", "@types/chai": "^4.2.22", "@types/mocha": "^9.0.0", @@ -53,7 +55,6 @@ "eslint": "^8.2.0", "eslint-plugin-jest": "^25.2.4", "fake-fs": "^0.5.0", - "@faker-js/faker": "^5.5.3", "globby": "^11.0.4", "ink-docstrap": "^1.3.2", "jest": "^27.3.1", @@ -86,4 +87,4 @@ "tsd": { "directory": "test/type" } -} \ No newline at end of file +} diff --git a/src/client.js b/src/client.js index 4c172e24d..bf6eaa16a 100644 --- a/src/client.js +++ b/src/client.js @@ -76,6 +76,7 @@ class Client extends GeneratedApiClient { } this.http = new Http({ + httpsProxy: clientConfig.httpsProxy, cacheStore: clientConfig.cacheStore, cacheMiddleware: clientConfig.cacheMiddleware, defaultCacheMiddlewareResponseBufferSize: clientConfig.defaultCacheMiddlewareResponseBufferSize, diff --git a/src/http.js b/src/http.js index 18e3c268b..2ab5531cf 100644 --- a/src/http.js +++ b/src/http.js @@ -16,6 +16,7 @@ const OktaApiError = require('./api-error'); const HttpError = require('./http-error'); const MemoryStore = require('./memory-store'); const defaultCacheMiddleware = require('./default-cache-middleware'); +const HttpsProxyAgent = require('https-proxy-agent'); /** * It's like fetch :) plus some extra convenience methods. @@ -55,6 +56,10 @@ class Http { this.defaultCacheMiddlewareResponseBufferSize = httpConfig.defaultCacheMiddlewareResponseBufferSize; } this.oauth = httpConfig.oauth; + const proxy = httpConfig.httpsProxy || process.env.https_proxy || process.env.HTTPS_PROXY; + if (proxy) { + this.agent = new HttpsProxyAgent(proxy); + } } prepareRequest(request) { @@ -74,6 +79,9 @@ class Http { request.url = uri; request.headers = Object.assign({}, this.defaultHeaders, request.headers); request.method = request.method || 'get'; + if (this.agent) { + request.agent = this.agent; + } let retriedOnAuthError = false; const execute = () => { diff --git a/src/types/client.d.ts b/src/types/client.d.ts index d1c71ba3a..ec10402d0 100644 --- a/src/types/client.d.ts +++ b/src/types/client.d.ts @@ -16,6 +16,7 @@ import { Http } from './http'; import { RequestExecutor } from './request-executor'; import { defaultCacheMiddleware } from './default-cache-middleware'; import { CacheStorage } from './memory-store'; +import { HttpsProxyAgentOptions } from 'https-proxy-agent'; export declare class Client extends ParameterizedOperationsClient { @@ -30,6 +31,7 @@ export declare class Client extends ParameterizedOperationsClient { cacheStore?: CacheStorage, cacheMiddleware?: typeof defaultCacheMiddleware | unknown defaultCacheMiddlewareResponseBufferSize?: number, + httpsProxy?: string | HttpsProxyAgentOptions, }); requestExecutor: RequestExecutor; diff --git a/src/types/http.d.ts b/src/types/http.d.ts index 4ba20298e..bde35e839 100644 --- a/src/types/http.d.ts +++ b/src/types/http.d.ts @@ -16,6 +16,7 @@ import { RequestExecutor } from './request-executor'; import { CacheStorage } from './memory-store'; import { defaultCacheMiddleware } from './default-cache-middleware'; import { RequestOptions } from './request-options'; +import { HttpsProxyAgent, HttpsProxyAgentOptions } from 'https-proxy-agent'; interface RequestContext { isCollection?: boolean, @@ -29,11 +30,13 @@ export declare class Http { oauth: OAuth, cacheStore?: CacheStorage, cacheMiddleware?: typeof defaultCacheMiddleware | unknown, + httpsProxy?: string | HttpsProxyAgentOptions, }); defaultHeaders: Record; requestExecutor: RequestExecutor; cacheStore: CacheStorage; cacheMiddleware: typeof defaultCacheMiddleware | unknown; + agent: typeof HttpsProxyAgent | unknown; oauth: OAuth; prepareRequest(request: RequestOptions): Promise; http(uri: string, request?: RequestOptions, context?: { diff --git a/src/types/request-options.d.ts b/src/types/request-options.d.ts index 11709c060..446b2f545 100644 --- a/src/types/request-options.d.ts +++ b/src/types/request-options.d.ts @@ -11,9 +11,11 @@ */ import { RequestInit } from 'node-fetch'; +import { HttpsProxyAgent } from 'https-proxy-agent'; /** An extension of node-fetch's request options. */ export interface RequestOptions extends RequestInit { startTime?: Date, url?: string, + agent?: HttpsProxyAgent, } diff --git a/test/jest/http.test.js b/test/jest/http.test.js index 97e92a7ca..6dfa60f86 100644 --- a/test/jest/http.test.js +++ b/test/jest/http.test.js @@ -4,9 +4,19 @@ const MemoryStore = require('../../src/memory-store'); const defaultCacheMiddleware = require('../../src/default-cache-middleware'); const OktaApiError = require('../../src/api-error'); const HttpError = require('../../src/http-error'); +const HttpsProxyAgent = require('https-proxy-agent'); describe('Http class', () => { describe('constructor', () => { + const OLD_ENV = process.env; + beforeEach(() => { + jest.resetModules(); + process.env = { ...OLD_ENV }; + }); + afterAll(() => { + process.env = OLD_ENV; + }); + it('creates empty defaultHeaders object', () => { const http = new Http({}); expect(http.defaultHeaders).toEqual({}); @@ -39,6 +49,30 @@ describe('Http class', () => { const http = new Http({ oauth }); expect(http.oauth).toBe(oauth); }); + it('accepts a "httpsProxy"', () => { + const http = new Http({ httpsProxy: 'http://proxy.example.net:8080/' }); + expect(http.agent).toBeInstanceOf(HttpsProxyAgent); + expect(http.agent.proxy.host).toBe("proxy.example.net"); + }); + it('uses a "https_proxy" env var', () => { + process.env.https_proxy = 'http://proxy.example.net:8080/'; + const http = new Http({ }); + expect(http.agent).toBeInstanceOf(HttpsProxyAgent); + expect(http.agent.proxy.host).toBe("proxy.example.net"); + }); + it('uses a "HTTPS_PROXY" env var', () => { + process.env.HTTPS_PROXY = 'http://proxy.example.net:8080/'; + const http = new Http({ }); + expect(http.agent).toBeInstanceOf(HttpsProxyAgent); + expect(http.agent.proxy.host).toBe("proxy.example.net"); + }); + it('uses a "httpsProxy" over "https_proxy"/"HTTPS_PROXY" env vars', () => { + process.env.https_proxy = 'http://proxy1.example.net:8080/'; + process.env.HTTPS_PROXY = 'http://proxy2.example.net:8080/'; + const http = new Http({ httpsProxy: 'http://proxy.example.net:8080/' }); + expect(http.agent).toBeInstanceOf(HttpsProxyAgent); + expect(http.agent.proxy.host).toBe("proxy.example.net"); + }); }); describe('errorFilter', () => { it('should resolve promise for status in 200 - 300 range', () => { @@ -476,5 +510,24 @@ describe('Http class', () => { }); }); }); + + describe('proxy', () => { + it('should use proxy agent in fetch method', () => { + const http = new Http({ requestExecutor, httpsProxy: 'http://proxy.example.net:8080/' }); + return http.http('http://fakey.local') + .then(() => { + expect(requestExecutor.fetch).toHaveBeenCalledWith({ + headers: {}, + method: 'get', + url: 'http://fakey.local', + agent: expect.objectContaining({ + proxy: expect.objectContaining({ + host: 'proxy.example.net' + }) + }), + }); + }); + }); + }); }); }); From 6d17f99931cad3f34da029878e46c9c3b4b90d88 Mon Sep 17 00:00:00 2001 From: "denys.oblohin" Date: Wed, 23 Feb 2022 18:16:18 +0200 Subject: [PATCH 2/5] lint fix --- test/jest/http.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/jest/http.test.js b/test/jest/http.test.js index 6dfa60f86..07c5cd385 100644 --- a/test/jest/http.test.js +++ b/test/jest/http.test.js @@ -52,26 +52,26 @@ describe('Http class', () => { it('accepts a "httpsProxy"', () => { const http = new Http({ httpsProxy: 'http://proxy.example.net:8080/' }); expect(http.agent).toBeInstanceOf(HttpsProxyAgent); - expect(http.agent.proxy.host).toBe("proxy.example.net"); + expect(http.agent.proxy.host).toBe('proxy.example.net'); }); it('uses a "https_proxy" env var', () => { process.env.https_proxy = 'http://proxy.example.net:8080/'; const http = new Http({ }); expect(http.agent).toBeInstanceOf(HttpsProxyAgent); - expect(http.agent.proxy.host).toBe("proxy.example.net"); + expect(http.agent.proxy.host).toBe('proxy.example.net'); }); it('uses a "HTTPS_PROXY" env var', () => { process.env.HTTPS_PROXY = 'http://proxy.example.net:8080/'; const http = new Http({ }); expect(http.agent).toBeInstanceOf(HttpsProxyAgent); - expect(http.agent.proxy.host).toBe("proxy.example.net"); + expect(http.agent.proxy.host).toBe('proxy.example.net'); }); it('uses a "httpsProxy" over "https_proxy"/"HTTPS_PROXY" env vars', () => { process.env.https_proxy = 'http://proxy1.example.net:8080/'; process.env.HTTPS_PROXY = 'http://proxy2.example.net:8080/'; const http = new Http({ httpsProxy: 'http://proxy.example.net:8080/' }); expect(http.agent).toBeInstanceOf(HttpsProxyAgent); - expect(http.agent.proxy.host).toBe("proxy.example.net"); + expect(http.agent.proxy.host).toBe('proxy.example.net'); }); }); describe('errorFilter', () => { From 10a5d3fc54b986cc77bafa2e03cf0d8b4c903df2 Mon Sep 17 00:00:00 2001 From: "denys.oblohin" Date: Wed, 23 Feb 2022 18:47:54 +0200 Subject: [PATCH 3/5] fix tsc --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8eee67c9..621f4e5c8 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:integration:ssws": "TS_NODE_PROJECT=./test/it/tsconfig.json MOCHA_FILE=./test-reports/it-results.xml OKTA_CLIENT_AUTHORIZATIONMODE=SSWS nyc --reporter=text --reporter=html mocha -r ts-node/register -r tsconfig-paths/register --reporter=mocha-junit-reporter --reporter-options toConsole=true --retries 2 test/it/*.ts --no-timeouts", "test:integration:oauth": "TS_NODE_PROJECT=./test/it/tsconfig.json OKTA_CLIENT_AUTHORIZATIONMODE=PrivateKey nyc --reporter=text --reporter=html mocha -r ts-node/register -r tsconfig-paths/register test/it/user-get.ts --no-timeouts", "test:unit": "MOCHA_FILE=./test-reports/junit-results.xml nyc --reporter=text --reporter=html mocha --reporter=mocha-junit-reporter test/unit/*.js --no-timeouts", - "test:types": "tsd && tsc --noEmit --isolatedModules --importsNotUsedAsValues error src/types/**/*.*", + "test:types": "tsd && tsc --noEmit --isolatedModules --allowSyntheticDefaultImports --importsNotUsedAsValues error src/types/**/*.*", "test": "yarn eslint && yarn test:types && yarn test:unit && yarn test:integration && yarn jest", "aftertest": "mocha test/delete-resources.js --no-timeouts" }, From 401b538b85f0642331b249381fd97d8af15349a9 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Mon, 28 Feb 2022 10:51:47 -0500 Subject: [PATCH 4/5] remove allowSyntheticDefaultImports flag for types test to keep strict rules for downstream apps --- package.json | 2 +- src/types/client.d.ts | 3 +-- src/types/http.d.ts | 5 ++--- src/types/request-options.d.ts | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 621f4e5c8..f8eee67c9 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:integration:ssws": "TS_NODE_PROJECT=./test/it/tsconfig.json MOCHA_FILE=./test-reports/it-results.xml OKTA_CLIENT_AUTHORIZATIONMODE=SSWS nyc --reporter=text --reporter=html mocha -r ts-node/register -r tsconfig-paths/register --reporter=mocha-junit-reporter --reporter-options toConsole=true --retries 2 test/it/*.ts --no-timeouts", "test:integration:oauth": "TS_NODE_PROJECT=./test/it/tsconfig.json OKTA_CLIENT_AUTHORIZATIONMODE=PrivateKey nyc --reporter=text --reporter=html mocha -r ts-node/register -r tsconfig-paths/register test/it/user-get.ts --no-timeouts", "test:unit": "MOCHA_FILE=./test-reports/junit-results.xml nyc --reporter=text --reporter=html mocha --reporter=mocha-junit-reporter test/unit/*.js --no-timeouts", - "test:types": "tsd && tsc --noEmit --isolatedModules --allowSyntheticDefaultImports --importsNotUsedAsValues error src/types/**/*.*", + "test:types": "tsd && tsc --noEmit --isolatedModules --importsNotUsedAsValues error src/types/**/*.*", "test": "yarn eslint && yarn test:types && yarn test:unit && yarn test:integration && yarn jest", "aftertest": "mocha test/delete-resources.js --no-timeouts" }, diff --git a/src/types/client.d.ts b/src/types/client.d.ts index ec10402d0..8b5d1ee02 100644 --- a/src/types/client.d.ts +++ b/src/types/client.d.ts @@ -16,7 +16,6 @@ import { Http } from './http'; import { RequestExecutor } from './request-executor'; import { defaultCacheMiddleware } from './default-cache-middleware'; import { CacheStorage } from './memory-store'; -import { HttpsProxyAgentOptions } from 'https-proxy-agent'; export declare class Client extends ParameterizedOperationsClient { @@ -31,7 +30,7 @@ export declare class Client extends ParameterizedOperationsClient { cacheStore?: CacheStorage, cacheMiddleware?: typeof defaultCacheMiddleware | unknown defaultCacheMiddlewareResponseBufferSize?: number, - httpsProxy?: string | HttpsProxyAgentOptions, + httpsProxy?: string | unknown, // https://github.com/TooTallNate/node-agent-base/issues/56 }); requestExecutor: RequestExecutor; diff --git a/src/types/http.d.ts b/src/types/http.d.ts index bde35e839..5c4aff326 100644 --- a/src/types/http.d.ts +++ b/src/types/http.d.ts @@ -16,7 +16,6 @@ import { RequestExecutor } from './request-executor'; import { CacheStorage } from './memory-store'; import { defaultCacheMiddleware } from './default-cache-middleware'; import { RequestOptions } from './request-options'; -import { HttpsProxyAgent, HttpsProxyAgentOptions } from 'https-proxy-agent'; interface RequestContext { isCollection?: boolean, @@ -30,13 +29,13 @@ export declare class Http { oauth: OAuth, cacheStore?: CacheStorage, cacheMiddleware?: typeof defaultCacheMiddleware | unknown, - httpsProxy?: string | HttpsProxyAgentOptions, + httpsProxy?: string | unknown, // https://github.com/TooTallNate/node-agent-base/issues/56 }); defaultHeaders: Record; requestExecutor: RequestExecutor; cacheStore: CacheStorage; cacheMiddleware: typeof defaultCacheMiddleware | unknown; - agent: typeof HttpsProxyAgent | unknown; + agent: any; // https://github.com/TooTallNate/node-agent-base/issues/56 oauth: OAuth; prepareRequest(request: RequestOptions): Promise; http(uri: string, request?: RequestOptions, context?: { diff --git a/src/types/request-options.d.ts b/src/types/request-options.d.ts index 446b2f545..5e502eb40 100644 --- a/src/types/request-options.d.ts +++ b/src/types/request-options.d.ts @@ -11,11 +11,10 @@ */ import { RequestInit } from 'node-fetch'; -import { HttpsProxyAgent } from 'https-proxy-agent'; /** An extension of node-fetch's request options. */ export interface RequestOptions extends RequestInit { startTime?: Date, url?: string, - agent?: HttpsProxyAgent, + agent?: any, // https://github.com/TooTallNate/node-agent-base/issues/56 } From 170db518feb02004a805b8868ec096b7bb25a39d Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Mon, 28 Feb 2022 10:52:49 -0500 Subject: [PATCH 5/5] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7d18f8f..344bccb03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Okta Node SDK Changelog +## 6.4.0 + +### Features + +- [#307](https://github.com/okta/okta-sdk-nodejs/pull/307) Supports HTTPS proxy for Okta http client + ## 6.3.1 ### Bug Fixes