Repository: auth0/node-auth0
Pull request #507 (2020-07-20 18:48:20)

Sanitize Headers on Errors
### Changes

Sanitize the authorization header on errors, as we do with passwords and secrets.

### Testing

- [X] This change adds unit test coverage
- [ ] This change adds integration test coverage

### Checklist

- [X] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [X] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
- [X] All existing and new tests complete without errors



Patch:
diff --git a/src/Auth0RestClient.js b/src/Auth0RestClient.js
index 48ee840a1..019fabe19 100644
--- a/src/Auth0RestClient.js
+++ b/src/Auth0RestClient.js
@@ -3,6 +3,7 @@ var Promise = require('bluebird');
 var ArgumentError = require('rest-facade').ArgumentError;
 
 var utils = require('./utils');
+var SanitizedError = require('./errors').SanitizedError;
 
 var Auth0RestClient = function(resourceUrl, options, provider) {
   if (resourceUrl === null || resourceUrl === undefined) {
@@ -17,6 +18,9 @@ var Auth0RestClient = function(resourceUrl, options, provider) {
     throw new ArgumentError('Must provide options');
   }
 
+  options.errorCustomizer = options.errorCustomizer || SanitizedError;
+  options.errorFormatter = options.errorFormatter || { message: 'message', name: 'error' };
+
   this.options = options;
   this.provider = provider;
   this.restClient = new RestClient(resourceUrl, options);
diff --git a/src/errors.js b/src/errors.js
index 02837d6d0..861e2fde8 100644
--- a/src/errors.js
+++ b/src/errors.js
@@ -12,19 +12,32 @@ var errors = (module.exports = {});
  * @memberOf  module:errors
  */
 errors.sanitizeErrorRequestData = function(error) {
-  if (!error.response || !error.response.request || !error.response.request._data) {
+  if (
+    !error.response ||
+    !error.response.request ||
+    (!error.response.request._data && !error.response.request._header)
+  ) {
     return error;
   }
 
-  Object.keys(error.response.request._data).forEach(function(key) {
-    if (key.toLowerCase().match('password|secret')) {
-      error.response.request._data[key] = '[SANITIZED]';
-    }
-  });
+  sanitizeErrors(error.response.request._header);
+  sanitizeErrors(error.response.request._data);
 
   return error;
 };
 
+var sanitizeErrors = function(collection) {
+  if (!collection) {
+    return;
+  }
+
+  Object.keys(collection).forEach(function(key) {
+    if (key.toLowerCase().match('password|secret|authorization')) {
+      collection[key] = '[SANITIZED]';
+    }
+  });
+};
+
 /**
  * Given an Api Error, modify the original error and sanitize
  * sensitive information using sanitizeErrorRequestData


Test patch:
diff --git a/test/auth0-rest-client.tests.js b/test/auth0-rest-client.tests.js
index d9adc0cc7..27cea9d00 100644
--- a/test/auth0-rest-client.tests.js
+++ b/test/auth0-rest-client.tests.js
@@ -151,6 +151,24 @@ describe('Auth0RestClient', function() {
     });
   });
 
+  it('should sanitize error when access token is in authorization header', function(done) {
+    nock(API_URL)
+      .get('/some-resource')
+      .reply(401);
+
+    var options = {
+      headers: {}
+    };
+
+    var client = new Auth0RestClient(API_URL + '/some-resource', options, this.providerMock);
+    client.getAll().catch(function(err) {
+      const originalRequestHeader = err.originalError.response.request._header;
+      expect(originalRequestHeader.authorization).to.equal('[SANITIZED]');
+      done();
+      nock.cleanAll();
+    });
+  });
+
   it('should catch error when provider.getAccessToken throws an error', function(done) {
     var providerMock = {
       getAccessToken: function() {
diff --git a/test/errors.tests.js b/test/errors.tests.js
index 604dba670..bd13a48ac 100644
--- a/test/errors.tests.js
+++ b/test/errors.tests.js
@@ -4,7 +4,7 @@ var errors = require('../src/errors');
 
 describe('Errors', function() {
   describe('sanitizeErrorRequestData', function() {
-    describe('when passed in error is missing request data', function() {
+    describe('when passed in error is missing request data and headers', function() {
       var error = { response: { request: {} } };
       var sanitizedError = errors.sanitizeErrorRequestData(error);
 
@@ -38,6 +38,24 @@ describe('Errors', function() {
         expect(sanitizedData.USER_NAME).to.equal(sanitizedData.USER_NAME);
       });
     });
+
+    describe('when passed in error has header data', function() {
+      const error = {
+        response: {
+          request: {
+            _header: {
+              authorization: 'Bearer xyz'
+            }
+          }
+        }
+      };
+      const sanitizedError = errors.sanitizeErrorRequestData(error);
+      const sanitizedData = sanitizedError.response.request._header;
+
+      it('should return [SANITIZED] for authorization', function() {
+        expect(sanitizedData.authorization).to.equal('[SANITIZED]');
+      });
+    });
   });
 
   describe('SanitizedError', function() {