Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds HTTPS proxy support for Okta client #307

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -86,4 +87,4 @@
"tsd": {
"directory": "test/type"
}
}
}
1 change: 1 addition & 0 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class Client extends GeneratedApiClient {
}

this.http = new Http({
httpsProxy: clientConfig.httpsProxy,
cacheStore: clientConfig.cacheStore,
cacheMiddleware: clientConfig.cacheMiddleware,
defaultCacheMiddlewareResponseBufferSize: clientConfig.defaultCacheMiddlewareResponseBufferSize,
Expand Down
8 changes: 8 additions & 0 deletions src/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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 = () => {
Expand Down
1 change: 1 addition & 0 deletions src/types/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export declare class Client extends ParameterizedOperationsClient {
cacheStore?: CacheStorage,
cacheMiddleware?: typeof defaultCacheMiddleware | unknown
defaultCacheMiddlewareResponseBufferSize?: number,
httpsProxy?: string | unknown, // https://github.com/TooTallNate/node-agent-base/issues/56
});

requestExecutor: RequestExecutor;
Expand Down
2 changes: 2 additions & 0 deletions src/types/http.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ export declare class Http {
oauth: OAuth,
cacheStore?: CacheStorage,
cacheMiddleware?: typeof defaultCacheMiddleware | unknown,
httpsProxy?: string | unknown, // https://github.com/TooTallNate/node-agent-base/issues/56
});
defaultHeaders: Record<string, unknown>;
requestExecutor: RequestExecutor;
cacheStore: CacheStorage;
cacheMiddleware: typeof defaultCacheMiddleware | unknown;
agent: any; // https://github.com/TooTallNate/node-agent-base/issues/56
oauth: OAuth;
prepareRequest(request: RequestOptions): Promise<RequestOptions>;
http(uri: string, request?: RequestOptions, context?: {
Expand Down
1 change: 1 addition & 0 deletions src/types/request-options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ import { RequestInit } from 'node-fetch';
export interface RequestOptions extends RequestInit {
startTime?: Date,
url?: string,
agent?: any, // https://github.com/TooTallNate/node-agent-base/issues/56
}
53 changes: 53 additions & 0 deletions test/jest/http.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({});
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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'
})
}),
});
});
});
});
});
});