diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b2814d0d9..f586cedf17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,5 +24,5 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: npm - - run: npm install + - run: npm ci - run: npm test diff --git a/README.md b/README.md index ff7ad143a5..c33dcd941e 100755 --- a/README.md +++ b/README.md @@ -399,6 +399,9 @@ These are the available config options for making requests. Only the `url` is re // `xsrfHeaderName` is the name of the http header that carries the xsrf token value xsrfHeaderName: 'X-XSRF-TOKEN', // default + // `undefined` (default) - set XSRF header only for the same origin requests + withXSRFToken: boolean | undefined | ((config: AxiosRequestConfig) => boolean | undefined), + // `onUploadProgress` allows handling of progress events for uploads // browser only onUploadProgress: function (progressEvent) { diff --git a/index.d.ts b/index.d.ts index 7196a3e352..9887119bed 100644 --- a/index.d.ts +++ b/index.d.ts @@ -165,6 +165,7 @@ export interface AxiosRequestConfig { FormData?: new (...args: any[]) => object; }; formSerializer?: FormSerializerOptions; + withXSRFToken?: boolean | ((config: AxiosRequestConfig) => boolean | undefined); } export interface HeadersDefaults { diff --git a/lib/adapters/xhr.js b/lib/adapters/xhr.js index fe3b33a02a..fc84d6af3c 100644 --- a/lib/adapters/xhr.js +++ b/lib/adapters/xhr.js @@ -18,6 +18,7 @@ module.exports = function xhrAdapter(config) { var requestData = config.data; var requestHeaders = config.headers; var responseType = config.responseType; + var withXSRFToken = config.withXSRFToken; var onCanceled; function done() { if (config.cancelToken) { @@ -145,12 +146,13 @@ module.exports = function xhrAdapter(config) { // Specifically not if we're in a web worker, or react-native. if (utils.isStandardBrowserEnv()) { // Add xsrf header - var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? - cookies.read(config.xsrfCookieName) : - undefined; - - if (xsrfValue) { - requestHeaders[config.xsrfHeaderName] = xsrfValue; + withXSRFToken && utils.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(config)); + if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(fullPath))) { + // Add xsrf header + var xsrfValue = config.xsrfHeaderName && config.xsrfCookieName && cookies.read(config.xsrfCookieName); + if (xsrfValue) { + requestHeaders[config.xsrfHeaderName] = xsrfValue; + } } } diff --git a/lib/core/mergeConfig.js b/lib/core/mergeConfig.js index 1d7a7673a0..0caf33349a 100644 --- a/lib/core/mergeConfig.js +++ b/lib/core/mergeConfig.js @@ -73,6 +73,7 @@ module.exports = function mergeConfig(config1, config2) { 'timeout': defaultToConfig2, 'timeoutMessage': defaultToConfig2, 'withCredentials': defaultToConfig2, + 'withXSRFToken': defaultToConfig2, 'adapter': defaultToConfig2, 'responseType': defaultToConfig2, 'xsrfCookieName': defaultToConfig2, diff --git a/package.json b/package.json index 7f08ba5892..eb59ca60b2 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "types": "index.d.ts", "scripts": { - "test": "node bin/ssl_hotfix.js grunt test && node bin/ssl_hotfix.js dtslint", + "test": "node bin/ssl_hotfix.js grunt test && node bin/ssl_hotfix.js dtslint --localTs node_modules/typescript/lib", "start": "node ./sandbox/server.js", "preversion": "grunt version && npm test", "build": "cross-env NODE_ENV=production grunt build", diff --git a/test/specs/xsrf.spec.js b/test/specs/xsrf.spec.js index 56cc0d28c4..3b81683788 100644 --- a/test/specs/xsrf.spec.js +++ b/test/specs/xsrf.spec.js @@ -1,82 +1,146 @@ +'use strict'; + var cookies = require('../../lib/helpers/cookies'); -describe('xsrf', function () { - beforeEach(function () { +describe('xsrf', function() { + beforeEach(function() { jasmine.Ajax.install(); }); - afterEach(function () { + afterEach(function() { document.cookie = axios.defaults.xsrfCookieName + '=;expires=' + new Date(Date.now() - 86400000).toGMTString(); jasmine.Ajax.uninstall(); }); - it('should not set xsrf header if cookie is null', function (done) { + it('should not set xsrf header if cookie is null', function(done) { axios('/foo'); - getAjaxRequest().then(function (request) { + getAjaxRequest().then(function(request) { expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined); done(); }); }); - it('should set xsrf header if cookie is set', function (done) { + it('should set xsrf header if cookie is set', function(done) { document.cookie = axios.defaults.xsrfCookieName + '=12345'; axios('/foo'); - getAjaxRequest().then(function (request) { + getAjaxRequest().then(function(request) { expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual('12345'); done(); }); }); - it('should not set xsrf header if xsrfCookieName is null', function (done) { + it('should not set xsrf header if xsrfCookieName is null', function(done) { document.cookie = axios.defaults.xsrfCookieName + '=12345'; axios('/foo', { xsrfCookieName: null }); - getAjaxRequest().then(function (request) { + getAjaxRequest().then(function(request) { expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined); done(); }); }); - it('should not read cookies at all if xsrfCookieName is null', function (done) { - spyOn(cookies, "read"); + it('should not read cookies at all if xsrfCookieName is null', function(done) { + spyOn(cookies, 'read'); axios('/foo', { xsrfCookieName: null }); - getAjaxRequest().then(function (request) { + getAjaxRequest().then(function() { expect(cookies.read).not.toHaveBeenCalled(); done(); }); }); - it('should not set xsrf header for cross origin', function (done) { + it('should not set xsrf header for cross origin', function(done) { document.cookie = axios.defaults.xsrfCookieName + '=12345'; axios('http://example.com/'); - getAjaxRequest().then(function (request) { + getAjaxRequest().then(function(request) { expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined); done(); }); }); - it('should set xsrf header for cross origin when using withCredentials', function (done) { + it('should set xsrf header for cross origin when using withCredentials and withXSRFToken', function(done) { document.cookie = axios.defaults.xsrfCookieName + '=12345'; axios('http://example.com/', { - withCredentials: true + withCredentials: true, + withXSRFToken: true }); - getAjaxRequest().then(function (request) { + getAjaxRequest().then(function(request) { expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual('12345'); done(); }); }); + describe('withXSRFToken option', function() { + it('should set xsrf header for cross origin when withXSRFToken = true', function(done) { + var token = '12345'; + + document.cookie = axios.defaults.xsrfCookieName + '=' + token; + + axios('http://example.com/', { + withXSRFToken: true + }); + + getAjaxRequest().then(function(request) { + expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(token); + done(); + }); + }); + + it('should not set xsrf header for the same origin when withXSRFToken = false', function(done) { + var token = '12345'; + + document.cookie = axios.defaults.xsrfCookieName + '=' + token; + + axios('/foo', { + withXSRFToken: false + }); + + getAjaxRequest().then(function(request) { + expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined); + done(); + }); + }); + + it('should not set xsrf header for the same origin when withXSRFToken = false', function(done) { + var token = '12345'; + + document.cookie = axios.defaults.xsrfCookieName + '=' + token; + + axios('/foo', { + withXSRFToken: false + }); + + getAjaxRequest().then(function(request) { + expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(undefined); + done(); + }); + }); + + it('should support function resolver', function(done) { + var token = '12345'; + + document.cookie = axios.defaults.xsrfCookieName + '=' + token; + axios('/foo', { + withXSRFToken: function(config) { return config.userFlag === 'yes';}, + userFlag: 'yes' + }); + + getAjaxRequest().then(function(request) { + expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toEqual(token); + done(); + }); + }); + }); });