Repository: auth0/auth0.js
Pull request #758 (2018-05-09 19:47:09)

Clear local state when checkSession call fails
partial fix for #402 


Patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 693f93e..bb96b7c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,6 +6,7 @@
   "search.exclude": {
     "**/node_modules": true,
     "**/bower_components": true,
-    "**/build": true
+    "**/build": true,
+    "**/docs": true
   }
 }
\ No newline at end of file
diff --git a/src/web-auth/transaction-manager.js b/src/web-auth/transaction-manager.js
index 27042c1..b20bc39 100644
--- a/src/web-auth/transaction-manager.js
+++ b/src/web-auth/transaction-manager.js
@@ -65,8 +65,12 @@ TransactionManager.prototype.getStoredTransaction = function(state) {
   var transactionData;
 
   transactionData = storage.getItem(this.namespace + state);
-  storage.removeItem(this.namespace + state);
+  this.clearTransaction(state);
   return transactionData;
 };
 
+TransactionManager.prototype.clearTransaction = function(state) {
+  storage.removeItem(this.namespace + state);
+};
+
 module.exports = TransactionManager;
diff --git a/src/web-auth/web-message-handler.js b/src/web-auth/web-message-handler.js
index 3ae1e57..e87ab91 100644
--- a/src/web-auth/web-message-handler.js
+++ b/src/web-auth/web-message-handler.js
@@ -22,7 +22,8 @@ function runWebMessageFlow(authorizeUrl, options, callback) {
     timeoutCallback: function() {
       callback({
         error: 'timeout',
-        error_description: 'Timeout during executing web_message communication'
+        error_description: 'Timeout during executing web_message communication',
+        state: options.state
       });
     }
   });
@@ -58,10 +59,13 @@ WebMessageHandler.prototype.run = function(options, cb) {
   ) {
     var error = err;
     if (!err && eventData.event.data.response.error) {
-      error = objectHelper.pick(eventData.event.data.response, ['error', 'error_description']);
+      error = eventData.event.data.response;
+    }
+    if (!error) {
+      var parsedHash = eventData.event.data.response;
+      return _this.webAuth.validateAuthenticationResponse(options, parsedHash, cb);
     }
     if (
-      error &&
       error.error === 'consent_required' &&
       windowHelper.getWindow().location.hostname === 'localhost'
     ) {
@@ -69,11 +73,8 @@ WebMessageHandler.prototype.run = function(options, cb) {
         "Consent Required. Consent can't be skipped on localhost. Read more here: https://auth0.com/docs/api-auth/user-consent#skipping-consent-for-first-party-clients"
       );
     }
-    if (error) {
-      return cb(error);
-    }
-    var parsedHash = eventData.event.data.response;
-    _this.webAuth.validateAuthenticationResponse(options, parsedHash, cb);
+    _this.webAuth.transactionManager.clearTransaction(error.state);
+    return cb(objectHelper.pick(error, ['error', 'error_description']));
   });
 };
 


Test patch:
diff --git a/test/web-auth/transaction-manager.test.js b/test/web-auth/transaction-manager.test.js
index 2078445..83b1b94 100644
--- a/test/web-auth/transaction-manager.test.js
+++ b/test/web-auth/transaction-manager.test.js
@@ -135,22 +135,34 @@ context('TransactionManager', function() {
   });
   context('getStoredTransaction', function() {
     beforeEach(function() {
-      stub(storage, 'removeItem');
       stub(storage, 'getItem', function(state) {
         expect(state).to.be('com.auth0.auth.state');
         return { from: 'storage' };
       });
+      spy(TransactionManager.prototype, 'clearTransaction');
     });
     afterEach(function() {
-      storage.removeItem.restore();
       storage.getItem.restore();
+      TransactionManager.prototype.clearTransaction.restore();
     });
     it('returns transaction data from storage', function() {
       var state = this.tm.getStoredTransaction('state');
       expect(state).to.be.eql({ from: 'storage' });
     });
     it('removes data from storage', function() {
-      var state = this.tm.getStoredTransaction('state');
+      this.tm.getStoredTransaction('state');
+      expect(TransactionManager.prototype.clearTransaction.firstCall.args[0]).to.be('state');
+    });
+  });
+  context('clearTransaction', function() {
+    beforeEach(function() {
+      stub(storage, 'removeItem');
+    });
+    afterEach(function() {
+      storage.removeItem.restore();
+    });
+    it('removes data from storage', function() {
+      this.tm.clearTransaction('state');
       expect(storage.removeItem.calledOnce).to.be(true);
       expect(storage.removeItem.lastCall.args[0]).to.be('com.auth0.auth.state');
     });
diff --git a/test/web-auth/web-auth.test.js b/test/web-auth/web-auth.test.js
index 915855a..d54d6f2 100644
--- a/test/web-auth/web-auth.test.js
+++ b/test/web-auth/web-auth.test.js
@@ -2302,6 +2302,7 @@ describe('auth0.WebAuth', function() {
       stub(TransactionManager.prototype, 'process', function(params) {
         return Object.assign({}, params, { from: 'transaction-manager' });
       });
+      spy(TransactionManager.prototype, 'clearTransaction');
       stub(windowHelper, 'getOrigin', function() {
         return 'https://test-origin.com';
       });
@@ -2311,6 +2312,7 @@ describe('auth0.WebAuth', function() {
     });
     afterEach(function() {
       TransactionManager.prototype.process.restore();
+      TransactionManager.prototype.clearTransaction.restore();
       if (IframeHandler.prototype.init.restore) {
         IframeHandler.prototype.init.restore();
       }
@@ -2402,7 +2404,7 @@ describe('auth0.WebAuth', function() {
       stub(IframeHandler.prototype, 'init', function() {
         this.timeoutCallback();
       });
-      this.auth0.checkSession({}, function(err, data) {
+      this.auth0.checkSession({ state: 'foobar' }, function(err, data) {
         expect(err).to.be.eql({
           error: 'timeout',
           error_description: 'Timeout during executing web_message communication'
@@ -2427,6 +2429,31 @@ describe('auth0.WebAuth', function() {
         done();
       });
     });
+    it('callback clears transaction on error response', function(done) {
+      var errorResponse = {
+        error: 'the-error',
+        error_description: 'error description',
+        state: 'foobar'
+      };
+      stub(IframeHandler.prototype, 'init', function() {
+        this.callback({ event: { data: { response: errorResponse } } });
+      });
+      this.auth0.checkSession({}, function(err, data) {
+        expect(TransactionManager.prototype.clearTransaction.firstCall.args[0]).to.be(
+          errorResponse.state
+        );
+        done();
+      });
+    });
+    it('callback clears transaction on timeout', function(done) {
+      stub(IframeHandler.prototype, 'init', function() {
+        this.timeoutCallback();
+      });
+      this.auth0.checkSession({ state: 'foobar' }, function(err, data) {
+        expect(TransactionManager.prototype.clearTransaction.firstCall.args[0]).to.be('foobar');
+        done();
+      });
+    });
     it('callback writes to console when consent_required + hostname===localhost', function(done) {
       var errorResponse = {
         error: 'consent_required'