diff --git a/README.md b/README.md index 3cba598f31e69b01de55cee539a161fe06208da8..ae390cc94f3f14691578f1bf041bce6755818457 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ axios.get('/user?ID=12345') .catch(function (response) { console.log(response); }); - + // Optionally the request above could also be done as axios.get('/user', { params: { @@ -174,7 +174,7 @@ These are the available config options for making requests. Only the `url` is re // The last function in the array must return a string or an ArrayBuffer transformRequest: [function (data) { // Do whatever you want to transform the data - + return data; }], @@ -182,7 +182,7 @@ These are the available config options for making requests. Only the `url` is re // it is passed to then/catch transformResponse: [function (data) { // Do whatever you want to transform the data - + return data; }], @@ -215,6 +215,14 @@ These are the available config options for making requests. Only the `url` is re // should be made using credentials withCredentials: false, // default + // `auth` indicates that HTTP Basic auth should be used, and supplies credentials. + // This will set an `Authorization` header, overwriting any existing + // `Authorization` custom headers you have set using `headers`. + auth: { + username: 'janedoe', + password: 's00pers3cret' + } + // `responseType` indicates the type of data that the server will respond with // options are 'arraybuffer', 'blob', 'document', 'json', 'text' responseType: 'json', // default @@ -238,7 +246,7 @@ The response for a request contains the following information. // `status` is the HTTP status code from the server response status: 200, - + // `statusText` is the HTTP status message from the server response statusText: 'OK', diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 30e3ccc7ea8bcd5b9e0f2cb3e8e40a0be674e815..740a9dcbe702bd6883812e6e6339ba58c2d1f3fb 100644 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -46,6 +46,14 @@ module.exports = function httpAdapter(resolve, reject, config) { headers['Content-Length'] = data.length; } + // HTTP basic authentication + var auth = undefined; + if (config.auth) { + var username = config.auth.user || config.auth.username; + var password = config.auth.pass || config.auth.password; + auth = username + ':' + password; + } + // Parse url var parsed = url.parse(config.url); var options = { @@ -54,7 +62,8 @@ module.exports = function httpAdapter(resolve, reject, config) { path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''), method: config.method, headers: headers, - agent: config.agent + agent: config.agent, + auth: auth }; // Create the request diff --git a/lib/adapters/xhr.js b/lib/adapters/xhr.js index 5cf542949839a4fdc6f82a8c823a0e52285d1146..21badcf65d97a589826cd194a7bf4b87a4f429cb 100644 --- a/lib/adapters/xhr.js +++ b/lib/adapters/xhr.js @@ -8,6 +8,7 @@ var buildURL = require('./../helpers/buildURL'); var parseHeaders = require('./../helpers/parseHeaders'); var transformData = require('./../helpers/transformData'); var isURLSameOrigin = require('./../helpers/isURLSameOrigin'); +var btoa = window.btoa || require('./../helpers/btoa.js') module.exports = function xhrAdapter(resolve, reject, config) { // Transform request data @@ -39,10 +40,17 @@ module.exports = function xhrAdapter(resolve, reject, config) { xDomain = true; } + // HTTP basic authentication + if (config.auth) { + var username = config.auth.username || ''; + var password = config.auth.password || ''; + requestHeaders['Authorization'] = 'Basic: ' + btoa(username + ':' + password); + } + // Create the request var request = new adapter('Microsoft.XMLHTTP'); request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true); - + // Set the request timeout in MS request.timeout = config.timeout; diff --git a/lib/helpers/btoa.js b/lib/helpers/btoa.js new file mode 100644 index 0000000000000000000000000000000000000000..86f45b7bc51cc530d43f198f103a824bde6a83b5 --- /dev/null +++ b/lib/helpers/btoa.js @@ -0,0 +1,34 @@ +'use strict'; + +// btoa polyfill for IE<10 courtesy https://github.com/davidchambers/Base64.js + +var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + +function InvalidCharacterError(message) { + this.message = message; +} +InvalidCharacterError.prototype = new Error; +InvalidCharacterError.prototype.name = 'InvalidCharacterError'; + +function btoa (input) { + var str = String(input); + for ( + // initialize result and counter + var block, charCode, idx = 0, map = chars, output = ''; + // if the next str index does not exist: + // change the mapping table to "=" + // check if d has no fractional digits + str.charAt(idx | 0) || (map = '=', idx % 1); + // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = str.charCodeAt(idx += 3/4); + if (charCode > 0xFF) { + throw new InvalidCharacterError('\'btoa\' failed: The string to be encoded contains characters outside of the Latin1 range.'); + } + block = block << 8 | charCode; + } + return output; +}; + +module.exports = btoa diff --git a/test/specs/basicAuth.spec.js b/test/specs/basicAuth.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0aae6b5c5c257070ec183a6970ab5480c096b8a6 --- /dev/null +++ b/test/specs/basicAuth.spec.js @@ -0,0 +1,43 @@ +var axios = require('../../index'); + +describe('basicAuth without btoa polyfill', function () { + beforeEach(function () { + jasmine.Ajax.install(); + }); + + afterEach(function () { + jasmine.Ajax.uninstall(); + }); + + it('should accept HTTP Basic auth with username/password', function (done) { + axios({ + url: '/foo', + auth: { + username: 'Aladdin', + password: 'open sesame' + } + }); + + setTimeout(function () { + var request = jasmine.Ajax.requests.mostRecent(); + + expect(request.requestHeaders['Authorization']).toEqual('Basic: QWxhZGRpbjpvcGVuIHNlc2FtZQ=='); + done(); + }, 0); + }); + + it('should fail to encode HTTP Basic auth credentials with non-Latin1 characters', function (done) { + axios({ + url: '/foo', + auth: { + username: 'Aladßç£☃din', + password: 'open sesame' + } + }).then(function(response) { + done(new Error('Should not succeed to make a HTTP Basic auth request with non-latin1 chars in credentials.')) + }).catch(function(error) { + expect(error.message).toEqual('INVALID_CHARACTER_ERR: DOM Exception 5') + done() + }); + }); +}); diff --git a/test/specs/basicAuthWithPolyfill.spec.js b/test/specs/basicAuthWithPolyfill.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..83aaf9b035c2155cfa4c4e0110ae08cf7fbd976c --- /dev/null +++ b/test/specs/basicAuthWithPolyfill.spec.js @@ -0,0 +1,52 @@ +var axios = require('../../index'); + +describe('basicAuth with btoa polyfill', function () { + beforeAll(function() { + this.original_btoa = window.btoa; + window.btoa = undefined; + }) + + afterAll(function() { + window.btoa = this.original_btoa; + }) + + beforeEach(function () { + jasmine.Ajax.install(); + }); + + afterEach(function () { + jasmine.Ajax.uninstall(); + }); + + it('should accept HTTP Basic auth with username/password', function (done) { + axios({ + url: '/foo', + auth: { + username: 'Aladdin', + password: 'open sesame' + } + }); + + setTimeout(function () { + var request = jasmine.Ajax.requests.mostRecent(); + + expect(request.requestHeaders['Authorization']).toEqual('Basic: QWxhZGRpbjpvcGVuIHNlc2FtZQ=='); + done(); + }, 0); + }); + + it('should fail to encode HTTP Basic auth credentials with non-Latin1 characters', function (done) { + axios({ + url: '/foo', + auth: { + username: 'Aladßç£☃din', + password: 'open sesame' + } + }).then(function(response) { + done(new Error('Should not succeed to make a HTTP Basic auth request with non-latin1 chars in credentials.')) + }).catch(function(error) { + expect(error.message).toEqual('\'btoa\' failed: The string to be encoded contains characters outside of the Latin1 range.') + done() + }); + }); +});