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

feat: add optional cookie jar support #85

Merged
merged 1 commit into from
Mar 23, 2020
Merged
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ The debug logger can be configured to be used for more than one library. In exam

``DEBUG=ibm-cloud-sdk-core:debug,other-lib:debug``

## Cookie Jar Support
By default, cookies are not supported in the SDK requests. If your SDK would benefit from this functionality, simply edit your code to instantiate a cookie jar (or instruct your users to do so) and pass it in the object containing configuration options to the `BaseService` class, as shown below.

```ts
import tough = require('tough-cookie');

class MyClass extends BaseService {
constructor(options: MyOptions) {
// pass the cookie jar object or simply pass the value `true`
// and a tough-cookie instance will be created by default
options.jar = new tough.CookieJar();
super(options);
}
}
```

The example above uses [Tough Cookie](https://www.npmjs.com/package/tough-cookie) to provide these capabilities, but other cookie jar libraries can be used.

## Issues
If you encounter an issue with this project, you are welcome to submit a [bug report](https://github.com/IBM/node-sdk-core/issues).
Before opening a new issue, please search for similar issues. It's possible that someone has already reported it.
Expand Down
2 changes: 2 additions & 0 deletions lib/base-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export interface UserOptions {
version?: string;
/** Set to `true` to allow unauthorized requests - not recommended for production use. */
disableSslVerification?: boolean;
/** Set your own cookie jar object */
jar?: any;
/** Deprecated. Use `serviceUrl` instead. */
url?: string;
/** Allow additional request config parameters */
Expand Down
13 changes: 11 additions & 2 deletions lib/request-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import axios from 'axios';
import axiosCookieJarSupport from 'axios-cookiejar-support';
import extend = require('extend');
import FormData = require('form-data');
import https = require('https');
Expand All @@ -27,7 +28,7 @@ const isBrowser = typeof window === 'object';
const globalTransactionId = 'x-global-transaction-id';

// Limit the type of axios configs to be customizable
const allowedAxiosConfig = ['transformRequest', 'transformResponse', 'paramsSerializer', 'paramsSerializer', 'timeout', 'withCredentials', 'adapter', 'responseType', 'responseEncoding', 'xsrfCookieName', 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'maxContentLength', 'validateStatus', 'maxRedirects', 'socketPath', 'httpAgent', 'httpsAgent', 'proxy', 'cancelToken'];
const allowedAxiosConfig = ['transformRequest', 'transformResponse', 'paramsSerializer', 'paramsSerializer', 'timeout', 'withCredentials', 'adapter', 'responseType', 'responseEncoding', 'xsrfCookieName', 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'maxContentLength', 'validateStatus', 'maxRedirects', 'socketPath', 'httpAgent', 'httpsAgent', 'proxy', 'cancelToken', 'jar'];

export class RequestWrapper {
private axiosInstance;
Expand Down Expand Up @@ -67,8 +68,16 @@ export class RequestWrapper {

this.axiosInstance = axios.create(axiosConfig);

// if a cookie jar is provided, wrap the axios instance and update defaults
if (axiosOptions.jar) {
axiosCookieJarSupport(this.axiosInstance);

this.axiosInstance.defaults.withCredentials = true;
this.axiosInstance.defaults.jar = axiosOptions.jar;
}

// set debug interceptors
if(process.env.NODE_DEBUG === 'axios' || process.env.DEBUG) {
if (process.env.NODE_DEBUG === 'axios' || process.env.DEBUG) {
this.axiosInstance.interceptors.request.use(config => {
logger.debug('Request:');
try {
Expand Down
107 changes: 85 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@types/jest": "^24.0.23",
"@types/node": "~10.14.19",
"axios": "^0.18.0",
"axios-cookiejar-support": "^0.5.1",
"camelcase": "^5.3.1",
"debug": "^4.1.1",
"dotenv": "^6.2.0",
Expand Down
9 changes: 9 additions & 0 deletions test/unit/base-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ describe('Base Service', () => {
expect(testService.baseOptions.disableSslVerification).toBe(true);
});

it('should store a cookie jar when set', () => {
const testService = new TestService({
authenticator: AUTHENTICATOR,
jar: true,
});

expect(testService.baseOptions.jar).toBe(true);
});

it('should default disableSslVerification to false', () => {
const testService = new TestService({
authenticator: AUTHENTICATOR,
Expand Down
62 changes: 62 additions & 0 deletions test/unit/cookiejar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';

// the `toBeInstanceOf` assertion compares for function reference equality
// importing our own `tough-cookie` dependency would create a different function
// reference and render the assertion unusable. the solution is to use the
// dependency within axios-cookiejar-support
const tough = require('axios-cookiejar-support/node_modules/tough-cookie');
const { RequestWrapper } = require('../../dist/lib/request-wrapper');

describe('cookie jar support', () => {
it('should not wrap the axios instance by default', () => {
const wrapper = new RequestWrapper();
expect(wrapper.axiosInstance.defaults.withCredentials).not.toBeDefined();
expect(wrapper.axiosInstance.interceptors.request.handlers.length).toBe(0);
});

it('passing a value for `jar` should produce interceptors and set flags', () => {
const wrapper = new RequestWrapper({ jar: true });
expect(wrapper.axiosInstance.defaults.withCredentials).toBe(true);
expect(wrapper.axiosInstance.interceptors.request.handlers.length).toBe(1);
});

it('given `true` for `jar`, the interceptors should create an instance of tough-cookie', () => {
const wrapper = new RequestWrapper({ jar: true });

expect(wrapper.axiosInstance.interceptors.request.handlers.length).toBe(1);
expect(wrapper.axiosInstance.interceptors.request.handlers[0].fulfilled).toBeInstanceOf(
Function
);

// should initially set the default to true
expect(wrapper.axiosInstance.defaults.jar).toBe(true);

// invoke the interceptor - it should be the one added by the cookie jar library
// it should see that `jar` is `true` and create a default instance of tough.CookieJar
// this would noramlly happen just before a request is sent
wrapper.axiosInstance.interceptors.request.handlers[0].fulfilled(
wrapper.axiosInstance.defaults
);

expect(wrapper.axiosInstance.defaults.jar).toBeInstanceOf(tough.CookieJar);
});

it('given arbitrary value for `jar`, the interceptor should use it as cookie jar', () => {
// the axios-cookiejar-support interceptor requires the jar object
// to have the method `getCookieString`
const mockCookieJar = { getCookieString: () => 'mock-string' };
const wrapper = new RequestWrapper({ jar: mockCookieJar });

// should still set interceptors and withCredentials flag
expect(wrapper.axiosInstance.interceptors.request.handlers.length).toBe(1);
expect(wrapper.axiosInstance.defaults.withCredentials).toBe(true);
expect(wrapper.axiosInstance.defaults.jar).toEqual(mockCookieJar);

// invoke the interceptor, the default jar should remain the same
wrapper.axiosInstance.interceptors.request.handlers[0].fulfilled(
wrapper.axiosInstance.defaults
);

expect(wrapper.axiosInstance.defaults.jar).toEqual(mockCookieJar);
});
});