diff --git a/README.md b/README.md index b8143e00fd..4bed677adc 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ TypeScript is supported for TypeScript version 2.9 and above. Check out these [code examples](examples) in JavaScript and TypeScript to get up and running quickly. +### Environment Variables + +`twilio-node` supports credential storage in environment variables. If no credentials are provided when instantiating the Twilio client (e.g., `const client = require('twilio')();`), the values in following env vars will be used: `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN`. + +If your environment requires SSL decryption, you can set the path to CA bundle in the env var `TWILIO_CA_BUNDLE`. + ## Docker Image The `Dockerfile` present in this repository and its respective `twilio/twilio-node` Docker image are currently used by Twilio for testing purposes only. diff --git a/examples/example.js b/examples/example.js index 6a3f904ba6..be2c7b5ef7 100644 --- a/examples/example.js +++ b/examples/example.js @@ -5,6 +5,10 @@ var Twilio = require('../lib'); var accountSid = process.env.TWILIO_ACCOUNT_SID; var token = process.env.TWILIO_AUTH_TOKEN; +// Uncomment the following line to specify a custom CA bundle for HTTPS requests: +// process.env.TWILIO_CA_BUNDLE = '/path/to/cert.pem'; +// You can also set this as a regular environment variable outside of the code + var twilio = new Twilio(accountSid, token); var i = 0; diff --git a/lib/base/RequestClient.js b/lib/base/RequestClient.js index e3a630380f..9d6410cddd 100644 --- a/lib/base/RequestClient.js +++ b/lib/base/RequestClient.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var http = require('request'); +var fs = require('fs'); var Q = require('q'); var Response = require('../http/response'); var Request = require('../http/request'); @@ -53,6 +54,13 @@ RequestClient.prototype.request = function(opts) { forever: opts.forever === false ? false : true, }; + if (process.env.TWILIO_CA_BUNDLE !== undefined) { + if (this.ca === undefined) { + this.ca = fs.readFileSync(process.env.TWILIO_CA_BUNDLE); + } + options.ca = this.ca; + } + if (!_.isNull(opts.data)) { options.formData = opts.data; } @@ -69,6 +77,7 @@ RequestClient.prototype.request = function(opts) { params: options.qs, data: options.formData, headers: options.headers, + ca: options.ca }; var _this = this; diff --git a/lib/http/request.js b/lib/http/request.js index 90e038a748..5e5eac0f4e 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -11,6 +11,7 @@ var Request = function(opts) { this.params = opts.params || this.ANY; this.data = opts.data || this.ANY; this.headers = opts.headers || this.ANY; + this.ca = opts.ca; }; Request.prototype.ANY = '*'; diff --git a/package-lock.json b/package-lock.json index a5c8b1341f..bdbbf39fea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1535,6 +1535,12 @@ "minimist": "0.0.8" } }, + "mock-fs": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.10.1.tgz", + "integrity": "sha512-w22rOL5ZYu6HbUehB5deurghGM0hS/xBVyHMGKOuQctkk93J9z9VEOhDsiWrXOprVNQpP9uzGKdl8v9mFspKuw==", + "dev": true + }, "module-not-found-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", diff --git a/package.json b/package.json index 73afd9a6b2..30836613f2 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "jasmine": "^3.4.0", "jsdoc": "^3.6.3", "jshint": "^2.10.2", + "mock-fs": "^4.10.1", "node-mocks-http": "^1.8.0", "proxyquire": "1.8.0", "typescript": "^2.8.3" diff --git a/spec/unit/base/RequestClient.spec.js b/spec/unit/base/RequestClient.spec.js index 61524375e0..2b5ada1382 100644 --- a/spec/unit/base/RequestClient.spec.js +++ b/spec/unit/base/RequestClient.spec.js @@ -1,3 +1,4 @@ +var mockfs = require('mock-fs'); var proxyquire = require('proxyquire'); describe('lastResponse and lastRequest defined', function() { @@ -85,3 +86,62 @@ describe('lastRequest defined, lastResponse undefined', function() { }); }); + +describe('User specified CA bundle', function() { + var client; + beforeEach(function() { + RequestClientMock = proxyquire('../../../lib/base/RequestClient', { + request: function (options, callback) { + callback('failed', null); + }, + }); + + client = new RequestClientMock(); + + options = { + method: 'GET', + uri: 'test-uri', + username: 'test-username', + password: 'test-password', + headers: {'test-header-key': 'test-header-value'}, + params: {'test-param-key': 'test-param-value'}, + data: {'test-data-key': 'test-data-value'} + }; + + mockfs({ + '/path/to/ca': { + 'test-ca.pem': 'test ca data' + } + }); + }); + + afterEach(function () { + mockfs.restore(); + }); + + it('should not modify CA if not specified', function() { + client.request(options); + expect(client.lastRequest.ca).toBeUndefined(); + }); + + it('should use CA if it is specified', function() { + process.env.TWILIO_CA_BUNDLE = '/path/to/ca/test-ca.pem'; + client.request(options); + expect(client.lastRequest.ca.toString()).toEqual('test ca data'); + delete process.env.TWILIO_CA_BUNDLE; + }); + + it('should cache the CA after loading it for the first time', function () { + process.env.TWILIO_CA_BUNDLE = '/path/to/ca/test-ca.pem'; + client.request(options); + mockfs({ + '/path/to/ca': { + 'test-ca.pem': null + } + }); + client.request(options); + expect(client.lastRequest.ca.toString()).toEqual('test ca data'); + delete process.env.TWILIO_CA_BUNDLE; + }) + +});