From 5182c6c534c05a61b36a76b9c1335fe77d246a80 Mon Sep 17 00:00:00 2001
From: Shane Tomlinson <stomlinson@mozilla.com>
Date: Wed, 16 Nov 2011 23:42:35 +0000
Subject: [PATCH] Adding the ability to press "cancel" in the verify email
 dialog.

* Cancel causes the user to go back to the previous screen.
* Cancel causes the network polling to stop.

close #587
---
 .../checkregistration_controller.js           | 45 ++++++---
 .../dialog/controllers/dialog_controller.js   | 22 ++++-
 resources/static/shared/user.js               | 26 +++++-
 .../checkregistration_controller_unit_test.js | 27 ++++--
 .../test/qunit/pages/browserid_unit_test.js   |  2 +-
 resources/static/test/qunit/qunit.js          |  2 +
 .../test/qunit/shared/user_unit_test.js       | 91 ++++++++++++++-----
 7 files changed, 161 insertions(+), 54 deletions(-)

diff --git a/resources/static/dialog/controllers/checkregistration_controller.js b/resources/static/dialog/controllers/checkregistration_controller.js
index f5aa94968..c6269895c 100644
--- a/resources/static/dialog/controllers/checkregistration_controller.js
+++ b/resources/static/dialog/controllers/checkregistration_controller.js
@@ -37,36 +37,51 @@
 (function() {
   "use strict";
 
-  var user = BrowserID.User;
+  var bid = BrowserID,
+      user = bid.User,
+      dom = bid.DOM,
+      errors = bid.Errors;
 
   PageController.extend("Checkregistration", {}, {
-    init: function(el, options) {
-      var me=this;
-      me._super(el, {
-        waitTemplate: "confirmemail",
-        waitVars: {
+    start: function(options) {
+      var self=this;
+      self.renderWait("confirmemail", {
           email: options.email
-        }
       });
-      me.email = options.email;
-      me.verifier = options.verifier;
-      me.verificationMessage = options.verificationMessage;
+      self.email = options.email;
+      self.verifier = options.verifier;
+      self.verificationMessage = options.verificationMessage;
+
+      dom.bindEvent("#back", "click", self.cancel.bind(self));
+
+      self._super();
+    },
+
+    stop: function() {
+      dom.unbindEvent("#back", "click");
+
+      this._super();
     },
 
     startCheck: function() {
-      var me=this;
-      user[me.verifier](me.email, function(status) {
+      var self=this;
+      user[self.verifier](self.email, function(status) {
         if (status === "complete") {
           user.syncEmails(function() {
-            me.close(me.verificationMessage);
+            self.close(self.verificationMessage);
           });
         }
         else if (status === "mustAuth") {
-          me.close("auth", { email: me.email });
+          self.close("auth", { email: self.email });
         }
-      }, me.getErrorDialog(BrowserID.Errors.registration));
+      }, self.getErrorDialog(errors.registration));
     },
 
+    cancel: function() {
+      var self=this;
+      self.close("cancel_" + self.verificationMessage);
+    }
+
   });
 
 
diff --git a/resources/static/dialog/controllers/dialog_controller.js b/resources/static/dialog/controllers/dialog_controller.js
index daf337755..7bffc9bb4 100644
--- a/resources/static/dialog/controllers/dialog_controller.js
+++ b/resources/static/dialog/controllers/dialog_controller.js
@@ -145,6 +145,11 @@
           self.doEmailConfirmed();
         });
 
+        subscribe("cancel_user_confirmed", function() {
+          user.cancelUserValidation();
+          self.returnFromStageCancel();
+        });
+
         subscribe("authenticated", function(msg, info) {
           //self.doEmailSelected(info.email);
           // XXX benadida, lloyd - swap these two if you want to experiment with
@@ -173,6 +178,11 @@
           self.doEmailConfirmed();
         });
 
+        subscribe("cancel_email_confirmed", function() {
+          user.cancelEmailValidation();
+          self.returnFromStageCancel();
+        });
+
         subscribe("notme", function() {
           self.doNotMe();
         });
@@ -229,6 +239,7 @@
 
       doPickEmail: function() {
         var self=this;
+        self.returnFromStageCancel = self.doPickEmail.bind(self);
         self.element.pickemail({
           // XXX ideal is to get rid of this and have a User function
           // that takes care of getting email addresses AND the last used email
@@ -239,11 +250,18 @@
       },
 
       doAuthenticate: function(info) {
-        this.element.authenticate(info);
+        var self = this;
+
+        // Save this off in case the user forgets their password or goes to add 
+        // a new password but has to cancel.
+        self.returnFromStageCancel = self.doAuthenticate.bind(self, info);
+        self.element.authenticate(info);
       },
 
       doAuthenticateWithRequiredEmail: function(info) {
-        this.element.requiredemail(info);
+        var self=this;
+        self.returnFromStageCancel = self.doAuthenticateWithRequiredEmail.bind(self, info);
+        self.element.requiredemail(info);
       },
 
       doForgotPassword: function(email) {
diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js
index 937ab1e68..6c9a19077 100644
--- a/resources/static/shared/user.js
+++ b/resources/static/shared/user.js
@@ -41,7 +41,7 @@ BrowserID.User = (function() {
   var jwk, jwt, vep, jwcert, origin,
       network = BrowserID.Network,
       storage = BrowserID.Storage,
-      User;
+      User, pollTimeout;
 
   function prepareDeps() {
     if (!jwk) {
@@ -117,7 +117,7 @@ BrowserID.User = (function() {
           }
         }
         else if (status === 'pending') {
-          setTimeout(poll, 3000);
+          pollTimeout = setTimeout(poll, 3000);
         }
         else if (onFailure) {
             onFailure(status);
@@ -128,6 +128,12 @@ BrowserID.User = (function() {
     poll();
   }
 
+  function cancelRegistrationPoll() {
+    if (pollTimeout) {
+      clearTimeout(pollTimeout);
+      pollTimeout = null;
+    }
+  }
 
   /**
    * Certify an identity with the server, persist it to storage if the server
@@ -250,6 +256,14 @@ BrowserID.User = (function() {
       registrationPoll(network.checkUserRegistration, email, onSuccess, onFailure);
     },
 
+    /**
+     * Cancel the waitForUserValidation poll
+     * @method cancelUserValidation
+     */
+    cancelUserValidation: function() {
+      cancelRegistrationPoll();
+    },
+
     /**
      * Verify a user
      * @method verifyUser
@@ -505,6 +519,14 @@ BrowserID.User = (function() {
       registrationPoll(network.checkEmailRegistration, email, onSuccess, onFailure);
     },
 
+    /**
+     * Cancel the waitForEmailValidation poll
+     * @method cancelEmailValidation
+     */
+    cancelEmailValidation: function() {
+      cancelRegistrationPoll();
+    },
+
     /**
      * Verify a users email address given by the token
      * @method verifyEmail
diff --git a/resources/static/test/qunit/controllers/checkregistration_controller_unit_test.js b/resources/static/test/qunit/controllers/checkregistration_controller_unit_test.js
index b763784e9..ee15db686 100644
--- a/resources/static/test/qunit/controllers/checkregistration_controller_unit_test.js
+++ b/resources/static/test/qunit/controllers/checkregistration_controller_unit_test.js
@@ -56,10 +56,6 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con
     }
   }
 
-  function reset() {
-    unsubscribeAll();
-  }
-
   function initController(verifier, message) {
     el = $("body");
     controller = el.checkregistration({
@@ -71,8 +67,9 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con
 
   module("controllers/checkregistration_controller", {
     setup: function() {
+      xhr.useResult("valid");
       network.setXHR(xhr);
-      reset();
+      $("#error").hide();
     },
 
     teardown: function() {
@@ -83,7 +80,8 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con
           controller.destroy();
         } catch(e) {}
       }
-      reset();
+      unsubscribeAll();
+      $("#error").hide();
     } 
   });
 
@@ -114,21 +112,30 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con
   });
 
   test("user validation with XHR error", function() {
-    $("#error").hide();
-
     xhr.useResult("ajaxError");
 
     initController("waitForUserValidation", "user_verified");
     subscribe("user_verified", function() {
       ok(false, "on XHR error, should not complete");
-      start();
     });
     controller.startCheck();
     
     setTimeout(function() {
       ok($("#error").is(":visible"), "Error message is visible");
       start();
-    }, 100);
+    }, 1000);
+
+    stop();
+  });
+
+  test("cancel raises cancel_user_verified", function() {
+    initController("waitForUserValidation", "user_verified");
+    subscribe("cancel_user_verified", function() {
+      ok(true, "on cancel, cancel_user_verified is triggered");
+      start();
+    });
+    controller.startCheck();
+    controller.cancel();
 
     stop();
   });
diff --git a/resources/static/test/qunit/pages/browserid_unit_test.js b/resources/static/test/qunit/pages/browserid_unit_test.js
index 7a5184845..1ba1e5c1a 100644
--- a/resources/static/test/qunit/pages/browserid_unit_test.js
+++ b/resources/static/test/qunit/pages/browserid_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.then("/pages/page_helpers", "/pages/browserid", function() {
+steal.then(function() {
   "use strict";
 
   module("/pages/browserid");
diff --git a/resources/static/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js
index f414ec307..d129414f8 100644
--- a/resources/static/test/qunit/qunit.js
+++ b/resources/static/test/qunit/qunit.js
@@ -25,6 +25,8 @@ steal.plugins(
       "/dialog/controllers/page_controller",
       "/dialog/controllers/required_email_controller",
 
+      "/pages/page_helpers",
+
       "pages/browserid_unit_test",
       "pages/page_helpers_unit_test",
 
diff --git a/resources/static/test/qunit/shared/user_unit_test.js b/resources/static/test/qunit/shared/user_unit_test.js
index 66d46bd73..8d13450db 100644
--- a/resources/static/test/qunit/shared/user_unit_test.js
+++ b/resources/static/test/qunit/shared/user_unit_test.js
@@ -39,9 +39,11 @@ var jwt = require("./jwt");
 var jwcert = require("./jwcert");
 
 steal.then(function() {
-  var lib = BrowserID.User,
-      storage = BrowserID.Storage,
-      xhr = BrowserID.Mocks.xhr,
+  var bid = BrowserID,
+      lib = bid.User,
+      storage = bid.Storage,
+      network = bid.Network,
+      xhr = bid.Mocks.xhr,
       testOrigin = "testOrigin";
 
   // I generated these locally, they are used nowhere else.
@@ -51,7 +53,7 @@ steal.then(function() {
   var random_cert = "eyJhbGciOiJSUzEyOCJ9.eyJpc3MiOiJpc3N1ZXIuY29tIiwiZXhwIjoxMzE2Njk1MzY3NzA3LCJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IlJTIiwibiI6IjU2MDYzMDI4MDcwNDMyOTgyMzIyMDg3NDE4MTc2ODc2NzQ4MDcyMDM1NDgyODk4MzM0ODExMzY4NDA4NTI1NTk2MTk4MjUyNTE5MjY3MTA4MTMyNjA0MTk4MDA0NzkyODQ5MDc3ODY4OTUxOTA2MTcwODEyNTQwNzEzOTgyOTU0NjUzODEwNTM5OTQ5Mzg0NzEyNzczMzkwMjAwNzkxOTQ5NTY1OTAzNDM5NTIxNDI0OTA5NTc2ODMyNDE4ODkwODE5MjA0MzU0NzI5MjE3MjA3MzYwMTA1OTA2MDM5MDIzMjk5NTYxMzc0MDk4OTQyNzg5OTk2NzgwMTAyMDczMDcxNzYwODUyODQxMDY4OTg5ODYwNDAzNDMxNzM3NDgwMTgyNzI1ODUzODk5NzMzNzA2MDY5IiwiZSI6IjY1NTM3In0sInByaW5jaXBhbCI6eyJlbWFpbCI6InRlc3R1c2VyQHRlc3R1c2VyLmNvbSJ9fQ.aVIO470S_DkcaddQgFUXciGwq2F_MTdYOJtVnEYShni7I6mqBwK3fkdWShPEgLFWUSlVUtcy61FkDnq2G-6ikSx1fUZY7iBeSCOKYlh6Kj9v43JX-uhctRSB2pI17g09EUtvmb845EHUJuoowdBLmLa4DSTdZE-h4xUQ9MsY7Ik";
 
 
-  function testAssertion(assertion) {
+  function testAssertion(assertion, cb) {
     equal(typeof assertion, "string", "An assertion was correctly generated");
 
     // Decode the assertion to a bundle.
@@ -71,30 +73,32 @@ steal.then(function() {
     var expires = tok.expires.getTime();
     ok(typeof expires === "number" && !isNaN(expires), "expiration date is valid");
 
-    var nowPlus2Mins = new Date().getTime() + (2 * 60 * 1000);
-    // expiration date must be within 5 seconds of 2 minutes from now - see
-    // issue 433 (https://github.com/mozilla/browserid/issues/433)
-    ok(((nowPlus2Mins - 5000) < expires) && (expires < (nowPlus2Mins + 5000)), "expiration date must be within 5 seconds of 2 minutes from now");
-
-    equal(typeof tok.cryptoSegment, "string", "cryptoSegment exists");
-    equal(typeof tok.headerSegment, "string", "headerSegment exists");
-    equal(typeof tok.payloadSegment, "string", "payloadSegment exists");
-    /*
-    // What are these supposed to be?
-    ok(tok.issuer, "issuer?");
-    ok(tok.payload, "payload?");
-    */
+    // this should be based on server time, not local time.
+    network.serverTime(function(time) {
+      var nowPlus2Mins = time.getTime() + (2 * 60 * 1000);
+
+      // expiration date must be within 5 seconds of 2 minutes from now - see
+      // issue 433 (https://github.com/mozilla/browserid/issues/433)
+      var diff = Math.abs(expires - nowPlus2Mins);
+      ok(diff < 5000, "expiration date must be within 5 seconds of 2 minutes from now: " + diff);
+
+      equal(typeof tok.cryptoSegment, "string", "cryptoSegment exists");
+      equal(typeof tok.headerSegment, "string", "headerSegment exists");
+      equal(typeof tok.payloadSegment, "string", "payloadSegment exists");
+
+      if(cb) cb();
+    });
   }
 
   module("shared/user", {
     setup: function() {
-      BrowserID.Network.setXHR(xhr);
+      network.setXHR(xhr);
       xhr.useResult("valid");
       lib.clearStoredEmailKeypairs();
       lib.setOrigin(testOrigin);
     },
     teardown: function() {
-      BrowserID.Network.setXHR($);
+      network.setXHR($);
     }
   });
 
@@ -226,7 +230,7 @@ steal.then(function() {
 
       start();
     }, function(status) {
-      ok(storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes");
+      ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared for noRegistration response");
       ok(status, "noRegistration", "noRegistration response causes failure");
       start();
     });
@@ -234,6 +238,7 @@ steal.then(function() {
     stop();
   });
 
+
   test("waitForUserValidation with XHR failure", function() {
     xhr.useResult("ajaxError");
 
@@ -250,6 +255,25 @@ steal.then(function() {
     stop();
   });
 
+  test("cancelUserValidation: ~1 second", function() {
+    xhr.useResult("pending");
+
+    storage.setStagedOnBehalfOf(testOrigin);
+    lib.waitForUserValidation("registered@testuser.com", function(status) {
+      ok(false, "not expecting success")
+    }, function(status) {
+      ok(false, "not expecting failure");
+    });
+
+    setTimeout(function() {
+      lib.cancelUserValidation();
+      ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared when validation cancelled");
+      start();
+    }, 1000);
+
+    stop();
+  });
+
   test("verifyUser with a good token", function() {
     storage.setStagedOnBehalfOf(testOrigin);
 
@@ -625,6 +649,25 @@ steal.then(function() {
   });
 
 
+  test("cancelEmailValidation: ~1 second", function() {
+    xhr.useResult("pending");
+
+    storage.setStagedOnBehalfOf(testOrigin);
+    lib.waitForEmailValidation("registered@testuser.com", function(status) {
+      ok(false, "not expecting success")
+    }, function(status) {
+      ok(false, "not expecting failure");
+    });
+
+    setTimeout(function() {
+      lib.cancelUserValidation();
+      ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared when validation cancelled");
+      start();
+    }, 1000);
+
+    stop();
+  });
+
   test("verifyEmail with a good token", function() {
     storage.setStagedOnBehalfOf(testOrigin);
     lib.verifyEmail("token", function onSuccess(info) {
@@ -850,10 +893,10 @@ steal.then(function() {
 
   test("getAssertion with known email that has key", function() {
     lib.setOrigin(testOrigin);
+    lib.removeEmail("testuser@testuser.com");
     lib.syncEmailKeypair("testuser@testuser.com", function() {
       lib.getAssertion("testuser@testuser.com", function onSuccess(assertion) {
-        testAssertion(assertion);
-        start();
+        testAssertion(assertion, start);
       }, failure("getAssertion failure"));
     }, failure("syncEmailKeypair failure"));
 
@@ -863,10 +906,10 @@ steal.then(function() {
 
   test("getAssertion with known email that does not have a key", function() {
     lib.setOrigin(testOrigin);
+    lib.removeEmail("testuser@testuser.com");
     storage.addEmail("testuser@testuser.com", {});
     lib.getAssertion("testuser@testuser.com", function onSuccess(assertion) {
-      testAssertion(assertion);
-      start();
+      testAssertion(assertion, start);
     }, failure("getAssertion failure"));
 
     stop();
-- 
GitLab