diff --git a/resources/static/common/js/network.js b/resources/static/common/js/network.js
index 2a38f461eb319abe90817655a95a737b1d839ae9..5864671e9f86c1ed5109be63dd0461c7c88ec1ce 100644
--- a/resources/static/common/js/network.js
+++ b/resources/static/common/js/network.js
@@ -83,14 +83,10 @@ BrowserID.Network = (function() {
     }
   }
 
-  function stageEmailForVerification(email, password, origin, wsapiName, onComplete, onFailure) {
+  function stageAddressForVerification(data, wsapiName, onComplete, onFailure) {
     post({
       url: wsapiName,
-      data: {
-        email: email,
-        pass: password,
-        site : origin
-      },
+      data: data,
       success: function(status) {
         complete(onComplete, status.success);
       },
@@ -235,7 +231,12 @@ BrowserID.Network = (function() {
      * @param {function} [onFailure] - Called on XHR failure.
      */
     createUser: function(email, password, origin, onComplete, onFailure) {
-      stageEmailForVerification(email, password, origin, "/wsapi/stage_user", onComplete, onFailure);
+      var postData = {
+        email: email,
+        pass: password,
+        site : origin
+      };
+      stageAddressForVerification(postData, "/wsapi/stage_user", onComplete, onFailure);
     },
 
     /**
@@ -308,16 +309,17 @@ BrowserID.Network = (function() {
     },
 
     /**
-     * Complete user reset password
-     * @method completeUserResetPassword
-     * @param {string} token - token to register for.
+     * Call with a token to prove an email address ownership.
+     * @method completeEmailRegistration
+     * @param {string} token - token proving email ownership.
      * @param {string} password
-     * @param {function} [onComplete] - Called when complete.
+     * @param {function} [onComplete] - Callback to call when complete.  Called
+     * with one boolean parameter that specifies the validity of the token.
      * @param {function} [onFailure] - Called on XHR failure.
      */
-    completeUserResetPassword: function(token, password, onComplete, onFailure) {
+    completeEmailRegistration: function(token, password, onComplete, onFailure) {
       post({
-        url: "/wsapi/complete_reset",
+        url: "/wsapi/complete_email_addition",
         data: {
           token: token,
           pass: password
@@ -330,17 +332,34 @@ BrowserID.Network = (function() {
     },
 
     /**
-     * Call with a token to prove an email address ownership.
-     * @method completeEmailRegistration
-     * @param {string} token - token proving email ownership.
+     * Request a password reset for the given email address.
+     * @method requestPasswordReset
+     * @param {string} email
      * @param {string} password
-     * @param {function} [onComplete] - Callback to call when complete.  Called
-     * with one boolean parameter that specifies the validity of the token.
+     * @param {string} origin
+     * @param {function} [onComplete] - Callback to call when complete.
      * @param {function} [onFailure] - Called on XHR failure.
      */
-    completeEmailRegistration: function(token, password, onComplete, onFailure) {
+    requestPasswordReset: function(email, password, origin, onComplete, onFailure) {
+      var postData = {
+        email: email,
+        pass: password,
+        site : origin
+      };
+      stageAddressForVerification(postData, "/wsapi/stage_reset", onComplete, onFailure);
+    },
+
+    /**
+     * Complete email reset password
+     * @method completePasswordReset
+     * @param {string} token - token to register for.
+     * @param {string} password
+     * @param {function} [onComplete] - Called when complete.
+     * @param {function} [onFailure] - Called on XHR failure.
+     */
+    completePasswordReset: function(token, password, onComplete, onFailure) {
       post({
-        url: "/wsapi/complete_email_addition",
+        url: "/wsapi/complete_reset",
         data: {
           token: token,
           pass: password
@@ -353,19 +372,49 @@ BrowserID.Network = (function() {
     },
 
     /**
-     * Complete email reset password
-     * @method completeEmailResetPassword
+     * Check the registration status of a password reset
+     * @method checkPasswordReset
+     * @param {function} [onsuccess] - called when complete.
+     * @param {function} [onfailure] - called on xhr failure.
+     */
+    checkPasswordReset: function(email, onComplete, onFailure) {
+      get({
+        // XXX the URL is going to have to change
+        url: "/wsapi/email_addition_status?email=" + encodeURIComponent(email),
+        success: function(status, textStatus, jqXHR) {
+          complete(onComplete, status.status);
+        },
+        error: onFailure
+      });
+    },
+
+    /**
+     * Stage an email reverification.
+     * @method requestEmailReverify
+     * @param {string} email
+     * @param {string} origin - site user is trying to sign in to.
+     * @param {function} [onComplete] - Callback to call when complete.
+     * @param {function} [onFailure] - Called on XHR failure.
+     */
+    requestEmailReverify: function(email, origin, onComplete, onFailure) {
+      var postData = {
+        email: email,
+        site : origin
+      };
+      stageAddressForVerification(postData, "/wsapi/stage_reverify", onComplete, onFailure);
+    },
+
+    /**
+     * Complete email reverification
+     * @method completeEmailReverify
      * @param {string} token - token to register for.
      * @param {string} password
      * @param {function} [onComplete] - Called when complete.
      * @param {function} [onFailure] - Called on XHR failure.
      */
-    /**
-     * BEGIN may not be needed
-     */
-    completeEmailResetPassword: function(token, password, onComplete, onFailure) {
+    completeEmailReverify: function(token, password, onComplete, onFailure) {
       post({
-        url: "/wsapi/complete_reset",
+        url: "/wsapi/complete_reverify",
         data: {
           token: token,
           pass: password
@@ -376,24 +425,25 @@ BrowserID.Network = (function() {
         error: onFailure
       });
     },
-    /**
-     * END may not be needed
-     */
-
 
     /**
-     * Request a password reset for the given email address.
-     * @method requestPasswordReset
-     * @param {string} email
-     * @param {string} password
-     * @param {string} origin
-     * @param {function} [onComplete] - Callback to call when complete.
-     * @param {function} [onFailure] - Called on XHR failure.
+     * Check the registration status of an email reverification
+     * @method checkEmailReverify
+     * @param {function} [onsuccess] - called when complete.
+     * @param {function} [onfailure] - called on xhr failure.
      */
-    requestPasswordReset: function(email, password, origin, onComplete, onFailure) {
-      stageEmailForVerification(email, password, origin, "/wsapi/stage_reset", onComplete, onFailure);
+    checkEmailReverify: function(email, onComplete, onFailure) {
+      get({
+        // XXX the URL is going to have to change
+        url: "/wsapi/email_addition_status?email=" + encodeURIComponent(email),
+        success: function(status, textStatus, jqXHR) {
+          complete(onComplete, status.status);
+        },
+        error: onFailure
+      });
     },
 
+
     /**
      * Set the password of the current user.
      * @method setPassword
@@ -504,7 +554,12 @@ BrowserID.Network = (function() {
      * @param {function} [onFailure] - called on xhr failure.
      */
     addSecondaryEmail: function(email, password, origin, onComplete, onFailure) {
-      stageEmailForVerification(email, password, origin, "/wsapi/stage_email", onComplete, onFailure);
+      var postData = {
+        email: email,
+        pass: password,
+        site : origin
+      };
+      stageAddressForVerification(postData, "/wsapi/stage_email", onComplete, onFailure);
     },
 
     /**
diff --git a/resources/static/common/js/user.js b/resources/static/common/js/user.js
index 9d0cb8150c6d56961eb2bb58b621d5c777b7d8a8..d36d94ea66236862842204edebde5b1e6eb4c490 100644
--- a/resources/static/common/js/user.js
+++ b/resources/static/common/js/user.js
@@ -220,7 +220,20 @@ BrowserID.User = (function() {
     });
   }
 
-  function verifyAddress(token, password, networkFuncName, onComplete, onFailure) {
+  function stageAddressVerificationResponse(onComplete, staged) {
+    var status = { success: staged };
+
+    if (!staged) status.reason = "throttle";
+    // Used on the main site when the user verifies - once
+    // verification is complete, the user is redirected back to the
+    // RP and logged in.
+    var site = User.getReturnTo();
+    if (staged && site) storage.setReturnTo(site);
+
+    complete(onComplete, status);
+  }
+
+  function completeAddressVerification(token, password, networkFuncName, onComplete, onFailure) {
     User.tokenInfo(token, function(info) {
       var invalidInfo = { valid: false };
       if (info) {
@@ -334,6 +347,7 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - Called on error.
      */
     createSecondaryUser: function(email, password, onComplete, onFailure) {
+      // set - XXX Use stageAddressVerificationResponse to handle the response.
       network.createUser(email, password, origin, function(created) {
         // Used on the main site when the user verifies - once verification
         // is complete, the user is redirected back to the RP and logged in.
@@ -509,9 +523,7 @@ BrowserID.User = (function() {
      * @param {function} [onSuccess] - Called to give status updates.
      * @param {function} [onFailure] - Called on error.
      */
-    waitForUserValidation: function(email, onSuccess, onFailure) {
-      registrationPoll(network.checkUserRegistration, email, onSuccess, onFailure);
-    },
+    waitForUserValidation: registrationPoll.curry(network.checkUserRegistration),
 
     /**
      * Cancel the waitForUserValidation poll
@@ -550,7 +562,7 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - Called on error.
      */
     verifyUser: function(token, password, onComplete, onFailure) {
-      verifyAddress(token, password, "completeUserRegistration", onComplete, onFailure);
+      completeAddressVerification(token, password, "completeUserRegistration", onComplete, onFailure);
     },
 
     /**
@@ -604,18 +616,8 @@ BrowserID.User = (function() {
     requestPasswordReset: function(email, password, onComplete, onFailure) {
       User.isEmailRegistered(email, function(registered) {
         if (registered) {
-          network.requestPasswordReset(email, password, origin, function(reset) {
-            var status = { success: reset };
-
-            if (!reset) status.reason = "throttle";
-            // Used on the main site when the user verifies - once
-            // verification is complete, the user is redirected back to the
-            // RP and logged in.
-            var site = User.getReturnTo();
-            if (reset && site) storage.setReturnTo(site);
-
-            complete(onComplete, status);
-          }, onFailure);
+          network.requestPasswordReset(email, password, origin,
+            stageAddressVerificationResponse.curry(onComplete), onFailure);
         }
         else if (onComplete) {
           onComplete({ success: false, reason: "invalid_user" });
@@ -634,9 +636,66 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - Called on error.
      */
     verifyPasswordReset: function(token, password, onComplete, onFailure) {
-      verifyAddress(token, password, "completeUserResetPassword", onComplete, onFailure);
+      completeAddressVerification(token, password, "completePasswordReset", onComplete, onFailure);
     },
 
+    /**
+     * Wait for the password reset to complete
+     * @method waitForPasswordResetComplete
+     * @param {string} email - email address to check.
+     * @param {function} [onSuccess] - Called to give status updates.
+     * @param {function} [onFailure] - Called on error.
+     */
+    waitForPasswordResetComplete: registrationPoll.curry(network.checkPasswordReset),
+
+    /**
+     * Cancel the waitForPasswordResetComplete poll
+     * @method cancelWaitForPasswordResetComplete
+     */
+    cancelWaitForPasswordResetComplete: cancelRegistrationPoll,
+
+    /**
+     * Request the reverification of an unverified email address
+     * @method requestEmailReverify
+     * @param {string} email
+     * @param {function} [onComplete]
+     * @param {function} [onFailure]
+     */
+    requestEmailReverify: function(email, onComplete, onFailure) {
+      var idInfo = storage.getEmail(email);
+      if (idInfo && idInfo.verified) {
+        // this email is already verified, cannot be reverified.
+        complete(onComplete, { success: false, reason: "verified_email" });
+      }
+      else if (idInfo && !idInfo.verified) {
+        // this address is unverified, try to reverify it.
+        network.requestEmailReverify(email, origin,
+          stageAddressVerificationResponse.curry(onComplete), onFailure);
+      }
+      else {
+        // user does not own this address.
+        complete(onComplete, { success: false, reason: "invalid_email" });
+      }
+    },
+
+    completeEmailReverify: function(token, password, onComplete, onFailure) {
+      completeAddressVerification(token, password, "completeEmailReverify", onComplete, onFailure);
+    },
+
+    /**
+     * Wait for the email reverification to complete
+     * @method waitForEmailReverifyComplete
+     * @param {string} email - email address to check.
+     * @param {function} [onSuccess] - Called to give status updates.
+     * @param {function} [onFailure] - Called on error.
+     */
+    waitForEmailReverifyComplete: registrationPoll.curry(network.checkEmailReverify),
+
+    /**
+     * Cancel the waitForEmailReverifyComplete poll
+     * @method cancelWaitForEmailReverifyComplete
+     */
+    cancelWaitForEmailReverifyComplete: cancelRegistrationPoll,
 
     /**
      * Cancel the current user's account.  Remove last traces of their
@@ -932,9 +991,7 @@ BrowserID.User = (function() {
      * @param {function} [onSuccess] - Called to give status updates.
      * @param {function} [onFailure] - Called on error.
      */
-    waitForEmailValidation: function(email, onSuccess, onFailure) {
-      registrationPoll(network.checkEmailRegistration, email, onSuccess, onFailure);
-    },
+    waitForEmailValidation: registrationPoll.curry(network.checkEmailRegistration),
 
     /**
      * Cancel the waitForEmailValidation poll
@@ -955,7 +1012,7 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - Called on error.
      */
     verifyEmail: function(token, password, onComplete, onFailure) {
-      verifyAddress(token, password, "completeEmailRegistration", onComplete, onFailure);
+      completeAddressVerification(token, password, "completeEmailRegistration", onComplete, onFailure);
     },
 
     /**
diff --git a/resources/static/dialog/js/misc/helpers.js b/resources/static/dialog/js/misc/helpers.js
index ce0f58a472e1a65dfb689a64d3eaec5bf1a0f390..d35b68a0ee9d97237278f4bcdcf3db6bab8d7f25 100644
--- a/resources/static/dialog/js/misc/helpers.js
+++ b/resources/static/dialog/js/misc/helpers.js
@@ -89,7 +89,20 @@
     var self=this;
     user.requestPasswordReset(email, password, function(status) {
       if (status.success) {
-        self.publish("password_reset", { email: email });
+        self.publish("password_reset_staged", { email: email });
+      }
+      else {
+        tooltip.showTooltip("#could_not_add");
+      }
+      complete(callback, status.success);
+    }, self.getErrorDialog(errors.requestPasswordReset, callback));
+  }
+
+  function reverifyEmail(email, callback) {
+    var self=this;
+    user.requestEmailReverify(email, function(status) {
+      if (status.success) {
+        self.publish("reverify_email_staged", { email: email });
       }
       else {
         tooltip.showTooltip("#could_not_add");
@@ -149,6 +162,7 @@
     addEmail: addEmail,
     addSecondaryEmail: addSecondaryEmail,
     resetPassword: resetPassword,
+    reverifyEmail: reverifyEmail,
     cancelEvent: helpers.cancelEvent,
     animateClose: animateClose,
     showRPTosPP: showRPTosPP
diff --git a/resources/static/dialog/js/misc/state.js b/resources/static/dialog/js/misc/state.js
index e4f342531aa735ca82a9336648f2fc7bcea81900..cc52fa840d0195b964d7c0d4a752949b403f7edf 100644
--- a/resources/static/dialog/js/misc/state.js
+++ b/resources/static/dialog/js/misc/state.js
@@ -152,7 +152,7 @@ BrowserID.State = (function() {
       }
       else if(self.resetPasswordEmail) {
         self.resetPasswordEmail = null;
-        startAction(false, "doResetPassword", info);
+        startAction(false, "doStageResetPassword", info);
       }
     });
 
@@ -172,6 +172,11 @@ BrowserID.State = (function() {
       redirectToState("email_chosen", { email: self.stagedEmail} );
     });
 
+    handleState("staged_address_confirmed", function() {
+      self.email = self.stagedEmail;
+      redirectToState("email_chosen", { email: self.stagedEmail} );
+    });
+
     handleState("primary_user", function(msg, info) {
       addPrimaryUser = !!info.add;
       email = info.email;
@@ -300,7 +305,7 @@ BrowserID.State = (function() {
       else if (!idInfo.verified) {
         // user selected an unverified secondary email, kick them over to the
         // verify screen.
-        redirectToState("verify_unverified_email", info);
+        redirectToState("stage_reverify_email", info);
       }
       else {
         // Address is verified, check the authentication, if the user is not
@@ -329,8 +334,21 @@ BrowserID.State = (function() {
       }
     });
 
-    handleState("verify_unverified_email", function(msg, info) {
+    handleState("stage_reverify_email", function(msg, info) {
+      // A user has selected an email that has not been verified after
+      // a password reset.  Stage the email again to be re-verified.
+      var actionInfo = {
+        email: info.email,
+        siteName: self.siteName
+      };
+      startAction("doStageReverifyEmail", actionInfo);
+    });
 
+    handleState("reverify_email_staged", function(msg, info) {
+      // The unverified email has been staged, now the user has to confirm
+      // ownership of the address.  Send them off to the "verify your address"
+      // screen.
+      startAction("doConfirmReverifyEmail");
     });
 
     handleState("email_valid_and_ready", function(msg, info) {
@@ -384,15 +402,34 @@ BrowserID.State = (function() {
       startAction(false, "doForgotPassword", info);
     });
 
-    handleState("password_reset", function(msg, info) {
-      // password_reset says the user has confirmed that they want to
-      // reset their password.  doResetPassword will attempt to invoke
+    handleState("stage_reset_password", function(msg, info) {
+      // reset_password says the user has confirmed that they want to
+      // reset their password.  doStageResetPassword will attempt to invoke
       // the reset_password wsapi.  If the wsapi call is successful,
-      // the user will be shown the "go verify your account" message.
-      info.password_reset = true;
-      redirectToState("user_staged", info);
+      // the password_reset_staged message will be triggered and the user will
+      // be shown the "go verify your account" message.
+
+      // We have to save the staged email address here for when the user
+      // verifies their account and user_confirmed is called.
+      self.stagedEmail = info.email;
+
+      var actionInfo = {
+        email: info.email,
+        password: info.password
+      };
+      startAction(false, "doStageResetPassword", actionInfo);
     });
 
+    handleState("password_reset_staged", function(msg, info) {
+      var actionInfo = {
+        email: info.email,
+        siteName: self.siteName
+      };
+
+      startAction("doConfirmResetPassword", actionInfo);
+    });
+
+
     handleState("assertion_generated", function(msg, info) {
       self.success = true;
       if (info.assertion !== null) {
@@ -417,19 +454,6 @@ BrowserID.State = (function() {
       redirectToState("email_chosen", info);
     });
 
-    handleState("reset_password", function(msg, info) {
-      info = info || {};
-      // reset_password says the user has confirmed that they want to
-      // reset their password.  doResetPassword will attempt to invoke
-      // the create_user wsapi.  If the wsapi call is successful,
-      // the user will be shown the "go verify your account" message.
-
-      // We have to save the staged email address here for when the user
-      // verifies their account and user_confirmed is called.
-      self.stagedEmail = info.email;
-      startAction(false, "doResetPassword", info);
-    });
-
     handleState("add_email", function(msg, info) {
       // add_email indicates the user wishes to add an email to the account,
       // the add_email screen must be displayed.  After the user enters the
diff --git a/resources/static/dialog/js/modules/actions.js b/resources/static/dialog/js/modules/actions.js
index aec8e96a38ca1abfa54e69e683aa2bd7f2134c99..711f21bc9951364f21b160f11686f105c8b2c7e7 100644
--- a/resources/static/dialog/js/modules/actions.js
+++ b/resources/static/dialog/js/modules/actions.js
@@ -85,6 +85,10 @@ BrowserID.Modules.Actions = (function() {
       dialogHelpers.addSecondaryEmail.call(this, info.email, info.password, info.ready);
     },
 
+    doConfirmEmail: function(info) {
+      startRegCheckService.call(this, info, "waitForEmailValidation", "email_confirmed");
+    },
+
     doAuthenticate: function(info) {
       startService("authenticate", info);
     },
@@ -97,12 +101,21 @@ BrowserID.Modules.Actions = (function() {
       startService("set_password", _.extend(info, { password_reset: true }));
     },
 
-    doResetPassword: function(info) {
+    doStageResetPassword: function(info) {
       dialogHelpers.resetPassword.call(this, info.email, info.password, info.ready);
     },
 
-    doConfirmEmail: function(info) {
-      startRegCheckService.call(this, info, "waitForEmailValidation", "email_confirmed");
+    doConfirmResetPassword: function(info) {
+      startRegCheckService.call(this, info, "waitForPasswordResetComplete", "staged_address_confirmed", info.password || undefined);
+
+    },
+
+    doStageReverifyEmail: function(info) {
+      dialogHelpers.reverifyEmail.call(this, info.email, info.ready);
+    },
+
+    doConfirmReverifyEmail: function(info) {
+      startRegCheckService.call(this, info, "waitForEmailReverifyComplete", "staged_address_confirmed", info.password || undefined);
     },
 
     doAssertionGenerated: function(info) {
diff --git a/resources/static/test/cases/common/js/network.js b/resources/static/test/cases/common/js/network.js
index 68fc546a8a2b5587902b48f34ee91e5db5aa011c..2b2db59e6d2c57e4489c11d4fa543fd29cf3f7fb 100644
--- a/resources/static/test/cases/common/js/network.js
+++ b/resources/static/test/cases/common/js/network.js
@@ -17,7 +17,7 @@
 
   var network = BrowserID.Network;
 
-  module("shared/network", {
+  module("common/js/network", {
     setup: function() {
       testHelpers.setup();
     },
@@ -26,6 +26,15 @@
     }
   });
 
+  function testVerificationPending(funcName) {
+    transport.useResult("pending");
+
+    network[funcName]("registered@testuser.com", function(status) {
+      equal(status, "pending");
+      start();
+    }, testHelpers.unexpectedFailure);
+  }
+
 
   asyncTest("authenticate with valid user", function() {
     network.authenticate(TEST_EMAIL, "testuser", function onSuccess(authenticated) {
@@ -192,32 +201,6 @@
     failureCheck(network.completeEmailRegistration, "goodtoken", "password");
   });
 
-  asyncTest("completeEmailResetPassword valid", function() {
-    network.completeEmailResetPassword("goodtoken", "password", function onSuccess(proven) {
-      equal(proven, true, "good token proved");
-      start();
-    }, testHelpers.unexpectedXHRFailure);
-  });
-
-  asyncTest("completeEmailResetPassword with valid token, bad password", function() {
-    transport.useResult("badPassword");
-    network.completeEmailResetPassword("token", "password",
-      testHelpers.unexpectedSuccess,
-      testHelpers.expectedXHRFailure);
-  });
-
-  asyncTest("completeEmailResetPassword with invalid token", function() {
-    transport.useResult("invalid");
-    network.completeEmailResetPassword("badtoken", "password", function onSuccess(proven) {
-      equal(proven, false, "bad token could not be proved");
-      start();
-    }, testHelpers.unexpectedXHRFailure);
-  });
-
-  asyncTest("completeEmailResetPassword with XHR failure", function() {
-    failureCheck(network.completeEmailResetPassword, "goodtoken", "password");
-  });
-
   asyncTest("createUser with valid user", function() {
     network.createUser("validuser", "password", "origin", function onSuccess(created) {
       ok(created);
@@ -332,40 +315,6 @@
     failureCheck(network.completeUserRegistration, "token", "password");
   });
 
-  asyncTest("completeUserResetPassword with valid token, no password required", function() {
-    network.completeUserResetPassword("token", undefined, function(registered) {
-      ok(registered);
-      start();
-    }, testHelpers.unexpectedFailure);
-  });
-
-  asyncTest("completeUserResetPassword with valid token, bad password", function() {
-    transport.useResult("badPassword");
-    network.completeUserResetPassword("token", "password",
-      testHelpers.unexpectedSuccess,
-      testHelpers.expectedXHRFailure);
-  });
-
-  asyncTest("completeUserResetPassword with valid token, password required", function() {
-    network.completeUserResetPassword("token", "password", function(registered) {
-      ok(registered);
-      start();
-    }, testHelpers.unexpectedFailure);
-  });
-
-  asyncTest("completeUserResetPassword with invalid token", function() {
-    transport.useResult("invalid");
-
-    network.completeUserResetPassword("token", "password", function(registered) {
-      equal(registered, false);
-      start();
-    }, testHelpers.unexpectedFailure);
-  });
-
-  asyncTest("completeUserResetPassword with XHR failure", function() {
-    failureCheck(network.completeUserResetPassword, "token", "password");
-  });
-
   asyncTest("cancelUser valid", function() {
 
     network.cancelUser(function() {
@@ -437,12 +386,7 @@
   });
 
   asyncTest("checkEmailRegistration pending", function() {
-    transport.useResult("pending");
-
-    network.checkEmailRegistration("registered@testuser.com", function(status) {
-      equal(status, "pending");
-      start();
-    }, testHelpers.unexpectedFailure);
+    testVerificationPending("checkEmailRegistration");
   });
 
   asyncTest("checkEmailRegistration complete", function() {
@@ -548,6 +492,100 @@
     failureCheck(network.requestPasswordReset, TEST_EMAIL, "password", "origin");
   });
 
+  asyncTest("completePasswordReset with valid token, no password required", function() {
+    network.completePasswordReset("token", undefined, function(registered) {
+      ok(registered);
+      start();
+    }, testHelpers.unexpectedFailure);
+  });
+
+  asyncTest("completePasswordReset with valid token, bad password", function() {
+    transport.useResult("badPassword");
+    network.completePasswordReset("token", "password",
+      testHelpers.unexpectedSuccess,
+      testHelpers.expectedXHRFailure);
+  });
+
+  asyncTest("completePasswordReset with valid token, password required", function() {
+    network.completePasswordReset("token", "password", function(registered) {
+      ok(registered);
+      start();
+    }, testHelpers.unexpectedFailure);
+  });
+
+  asyncTest("completePasswordReset with invalid token", function() {
+    transport.useResult("invalid");
+
+    network.completePasswordReset("token", "password", function(registered) {
+      equal(registered, false);
+      start();
+    }, testHelpers.unexpectedFailure);
+  });
+
+  asyncTest("completePasswordReset with XHR failure", function() {
+    failureCheck(network.completePasswordReset, "token", "password");
+  });
+
+
+  asyncTest("checkPasswordReset pending", function() {
+    testVerificationPending("checkPasswordReset");
+  });
+
+
+
+
+  asyncTest("requestEmailReverify - true status", function() {
+    network.requestEmailReverify(TEST_EMAIL, "origin", function onSuccess(status) {
+      equal(status, true, "password reset request success");
+      start();
+    }, testHelpers.unexpectedFailure);
+  });
+
+  asyncTest("requestEmailReverify with XHR failure", function() {
+    failureCheck(network.requestEmailReverify, TEST_EMAIL, "origin");
+  });
+
+  asyncTest("completeEmailReverify with valid token, no password required", function() {
+    network.completeEmailReverify("token", undefined, function(registered) {
+      ok(registered);
+      start();
+    }, testHelpers.unexpectedFailure);
+  });
+
+  asyncTest("completeEmailReverify with valid token, bad password", function() {
+    transport.useResult("badPassword");
+    network.completeEmailReverify("token", "password",
+      testHelpers.unexpectedSuccess,
+      testHelpers.expectedXHRFailure);
+  });
+
+  asyncTest("completeEmailReverify with valid token, password required", function() {
+    network.completeEmailReverify("token", "password", function(registered) {
+      ok(registered);
+      start();
+    }, testHelpers.unexpectedFailure);
+  });
+
+  asyncTest("completeEmailReverify with invalid token", function() {
+    transport.useResult("invalid");
+
+    network.completeEmailReverify("token", "password", function(registered) {
+      equal(registered, false);
+      start();
+    }, testHelpers.unexpectedFailure);
+  });
+
+  asyncTest("completeEmailReverify with XHR failure", function() {
+    failureCheck(network.completeEmailReverify, "token", "password");
+  });
+
+  asyncTest("checkEmailReverify pending", function() {
+    testVerificationPending("checkEmailReverify");
+  });
+
+
+
+
   asyncTest("setPassword happy case expects true status", function() {
     network.setPassword("password", function onComplete(status) {
       equal(status, true, "correct status");
diff --git a/resources/static/test/cases/common/js/user.js b/resources/static/test/cases/common/js/user.js
index 0a4b1d6b80cb5e81c55cc41641b9aa61f2c071b3..ce212f120bc5d56c6f0360347c295f5022ea554c 100644
--- a/resources/static/test/cases/common/js/user.js
+++ b/resources/static/test/cases/common/js/user.js
@@ -65,7 +65,7 @@ var jwcrypto = require("./lib/jwcrypto");
     });
   }
 
-  module("shared/user", {
+  module("common/js/user", {
     setup: function() {
       testHelpers.setup();
     },
@@ -558,6 +558,100 @@ var jwcrypto = require("./lib/jwcrypto");
     );
   });
 
+  asyncTest("requestEmailReverify with owned verified email - false status", function() {
+    storage.addSecondaryEmail(TEST_EMAIL, { verified: true });
+
+    var returnTo = "http://samplerp.org";
+    lib.setReturnTo(returnTo);
+    lib.requestEmailReverify(TEST_EMAIL, function(status) {
+      testObjectValuesEqual(status, {
+        success: false,
+        reason: "verified_email"
+      });
+
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
+
+  asyncTest("requestEmailReverify with owned unverified email - false status", function() {
+    storage.addSecondaryEmail(TEST_EMAIL, { verified: false });
+
+    var returnTo = "http://samplerp.org";
+    lib.setReturnTo(returnTo);
+    lib.requestEmailReverify(TEST_EMAIL, function(status) {
+      equal(status.success, true, "password reset for known user");
+      equal(storage.getReturnTo(), returnTo, "RP URL is stored for verification");
+
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
+
+  asyncTest("requestEmailReverify with unowned email - false status, invalid_user", function() {
+    lib.requestEmailReverify(TEST_EMAIL, function(status) {
+      testObjectValuesEqual(status, {
+        success: false,
+        reason: "invalid_email"
+      });
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
+
+  asyncTest("requestEmailReverify owned email with throttle - false status, throttle", function() {
+    xhr.useResult("throttle");
+    storage.addSecondaryEmail(TEST_EMAIL, { verified: false });
+
+    lib.requestEmailReverify(TEST_EMAIL, function(status) {
+      testObjectValuesEqual(status, {
+        success: false,
+        reason: "throttle"
+      });
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
+
+  asyncTest("requestEmailReverify with XHR failure", function() {
+    storage.addSecondaryEmail(TEST_EMAIL, { verified: false });
+    failureCheck(lib.requestEmailReverify, TEST_EMAIL);
+  });
+
+  asyncTest("completeEmailReverify with a good token", function() {
+    storage.addSecondaryEmail(TEST_EMAIL, { verified: false });
+    storage.setReturnTo(testOrigin);
+
+    lib.completeEmailReverify("token", "password", function onSuccess(info) {
+      testObjectValuesEqual(info, {
+        valid: true,
+        email: TEST_EMAIL,
+        returnTo: testOrigin,
+      });
+
+      equal(storage.getReturnTo(), "", "initiating origin was removed");
+      equal(storage.getEmail(TEST_EMAIL).verified, true, "email now marked as verified");
+
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
+
+  asyncTest("completeEmailReverify with a bad token", function() {
+    xhr.useResult("invalid");
+
+    lib.completeEmailReverify("token", "password", function onSuccess(info) {
+      equal(info.valid, false, "bad token calls onSuccess with a false validity");
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
+
+  asyncTest("completeEmailReverify with an XHR failure", function() {
+    xhr.useResult("ajaxError");
+
+    lib.completeEmailReverify(
+      "token",
+      "password",
+      testHelpers.unexpectedSuccess,
+      testHelpers.expectedXHRFailure
+    );
+  });
+
 
   asyncTest("authenticate with valid credentials, also syncs email with server", function() {
     lib.authenticate(TEST_EMAIL, "testuser", function(authenticated) {
diff --git a/resources/static/test/cases/dialog/js/misc/state.js b/resources/static/test/cases/dialog/js/misc/state.js
index 95ac670d42a642db6b2d0c5ae627c71947f9d6e4..86f919450f623b4f3b68f6d2abbba48d4a28528b 100644
--- a/resources/static/test/cases/dialog/js/misc/state.js
+++ b/resources/static/test/cases/dialog/js/misc/state.js
@@ -42,6 +42,31 @@
     }
   }
 
+  function testAddressStaged(startMessage, expectedAction) {
+    // password_reset_staged indicates the user has verified that they want to reset
+    // their password.
+    mediator.publish(startMessage, {
+      email: TEST_EMAIL
+    });
+    equal(actions.info[expectedAction].email, TEST_EMAIL, expectedAction + " with the correct email");
+
+    // At this point the user should be displayed the "go confirm your address"
+    // screen.
+
+    // user_confirmed means the user has confirmed their email and the dialog
+    // has received the "complete" message from /wsapi/user_creation_status.
+    try {
+      mediator.publish("staged_address_confirmed");
+    } catch(e) {
+      // Exception is expected because as part of the user confirmation
+      // process, before user_confirmed is called, email addresses are synced.
+      // Addresses are not synced in this test.
+      equal(e.toString(), "invalid email", "expected failure");
+    }
+
+  }
+
+
   function createMachine() {
     machine = bid.State.create();
     actions = new ActionsMock();
@@ -61,7 +86,7 @@
     });
   }
 
-  module("resources/state", {
+  module("dialog/js/misc/state", {
     setup: function() {
       testHelpers.setup();
       createMachine();
@@ -128,11 +153,11 @@
     equal(actions.info.doStageEmail.email, TEST_EMAIL, "correct email sent to doStageEmail");
   });
 
-  test("password_set for reset password - call doResetPassword with correct email", function() {
+  test("password_set for reset password - call doStageResetPassword with correct email", function() {
     mediator.publish("forgot_password", { email: TEST_EMAIL });
     mediator.publish("password_set");
 
-    equal(actions.info.doResetPassword.email, TEST_EMAIL, "correct email sent to doResetPassword");
+    equal(actions.info.doStageResetPassword.email, TEST_EMAIL, "correct email sent to doStageResetPassword");
   });
 
   test("start - RPInfo always started", function() {
@@ -146,9 +171,7 @@
   });
 
   test("user_staged - call doConfirmUser", function() {
-    mediator.publish("user_staged", { email: TEST_EMAIL });
-
-    equal(actions.info.doConfirmUser.email, TEST_EMAIL, "waiting for email confirmation for testuser@testuser.com");
+    testAddressStaged("user_staged", "doConfirmUser");
   });
 
   test("user_staged with required email - call doConfirmUser with required = true", function() {
@@ -176,9 +199,7 @@
   });
 
   test("email_staged - call doConfirmEmail", function() {
-    mediator.publish("email_staged", { email: TEST_EMAIL });
-
-    equal(actions.info.doConfirmEmail.required, false, "doConfirmEmail called without required flag");
+    testAddressStaged("email_staged", "doConfirmEmail");
   });
 
   test("email_staged with required email - call doConfirmEmail with required = true", function() {
@@ -281,42 +302,11 @@
     testActionStarted("doForgotPassword", { email: TEST_EMAIL, requiredEmail: true });
   });
 
-  test("password_reset to user_confirmed - call doUserStaged then doEmailConfirmed", function() {
-    // password_reset indicates the user has verified that they want to reset
-    // their password.
-    mediator.publish("password_reset", {
-      email: TEST_EMAIL
-    });
-    equal(actions.info.doConfirmUser.email, TEST_EMAIL, "doConfirmUser with the correct email");
-
-    // At this point the user should be displayed the "go confirm your address"
-    // screen.
-
-    // user_confirmed means the user has confirmed their email and the dialog
-    // has received the "complete" message from /wsapi/user_creation_status.
-    try {
-      mediator.publish("user_confirmed");
-    } catch(e) {
-      // Exception is expected because as part of the user confirmation
-      // process, before user_confirmed is called, email addresses are synced.
-      // Addresses are not synced in this test.
-      equal(e.toString(), "invalid email", "expected failure");
-    }
+  test("password_reset_staged to staged_address_confirmed - call doConfirmResetPassword then doEmailConfirmed", function() {
+    testAddressStaged("password_reset_staged", "doConfirmResetPassword");
   });
 
 
-  test("cancel password_reset flow - go two steps back", function() {
-    // we want to skip the "verify" screen of reset password and instead go two
-    // screens back.  Do do this, we are simulating the steps necessary to get
-    // to the password_reset flow.
-    mediator.publish("authenticate");
-    mediator.publish("forgot_password", undefined, { email: TEST_EMAIL });
-    mediator.publish("password_reset");
-    actions.info.doAuthenticate = {};
-    mediator.publish("cancel_state");
-    equal(actions.info.doAuthenticate.email, TEST_EMAIL, "authenticate called with the correct email");
-  });
-
   asyncTest("assertion_generated with null assertion - redirect to pick_email", function() {
     mediator.subscribe("pick_email", function() {
       ok(true, "redirect to pick_email");
@@ -462,12 +452,12 @@
     });
   });
 
-  function testUnverifiedEmailChosen(auth_level) {
+  function testReverifyEmailChosen(auth_level) {
     storage.addSecondaryEmail(TEST_EMAIL, { verified: false });
     xhr.setContextInfo("auth_level", auth_level);
 
-    mediator.subscribe("verify_unverified_email", function(msg, info) {
-      equal(info.email, TEST_EMAIL, "correctly redirected to verify_unverified_email with correct email");
+    mediator.subscribe("stage_reverify_email", function(msg, info) {
+      equal(info.email, TEST_EMAIL, "correctly redirected to stage_reverify_email with correct email");
       start();
     });
 
@@ -476,12 +466,12 @@
     });
   }
 
-  asyncTest("email_chosen with unverified secondary email, user authenticated to secondary - redirect to verify_unverified_email", function() {
-    testUnverifiedEmailChosen("password");
+  asyncTest("email_chosen with unverified secondary email, user authenticated to secondary - redirect to stage_reverify_email", function() {
+    testReverifyEmailChosen("password");
   });
 
-  asyncTest("email_chosen with unverified secondary email, user authenticated to primary - redirect to verify_unverified_email", function() {
-    testUnverifiedEmailChosen("assertion");
+  asyncTest("email_chosen with unverified secondary email, user authenticated to primary - redirect to stage_reverify_email", function() {
+    testReverifyEmailChosen("assertion");
   });
 
   test("email_chosen with primary email - call doProvisionPrimaryUser", function() {
@@ -565,8 +555,14 @@
     });
   });
 
-  asyncTest("verify_unverified_email", function() {
-    start();
+  test("stage_reverify_email - call doStageReverifyEmail", function() {
+    mediator.publish("start", { siteName: "Unit Test Site" });
+    mediator.publish("stage_reverify_email", { email: TEST_EMAIL });
+    testActionStarted("doStageReverifyEmail", { email: TEST_EMAIL, siteName: "Unit Test Site" });
+  });
+
+  test("unverified_email_staged - call doConfirmReverifyEmail", function() {
+
   });
 
   asyncTest("window_unload - set the final KPIs", function() {
diff --git a/resources/static/test/cases/dialog/js/modules/actions.js b/resources/static/test/cases/dialog/js/modules/actions.js
index 3f98fc81b8d968879c1c34738a00b4eab5500111..15f76f52ceebe272cb6d7dc7c5a144da05579ee5 100644
--- a/resources/static/test/cases/dialog/js/modules/actions.js
+++ b/resources/static/test/cases/dialog/js/modules/actions.js
@@ -8,6 +8,7 @@
 
   var bid = BrowserID,
       user = bid.User,
+      storage = bid.Storage,
       controller,
       el,
       testHelpers = bid.TestHelpers,
@@ -34,7 +35,29 @@
     });
   }
 
-  module("controllers/actions", {
+  function testStageAddress(actionName, expectedMessage) {
+    createController({
+      ready: function() {
+        var message,
+            email;
+
+        testHelpers.register(expectedMessage, function(msg, info) {
+          message = msg;
+          email = info.email;
+        });
+
+        controller[actionName]({ email: TEST_EMAIL, password: "password", ready: function(status) {
+          equal(status, true, "correct status");
+          equal(message, expectedMessage, "correct message triggered");
+          equal(email, TEST_EMAIL, "address successfully staged");
+          start();
+        }});
+      }
+    });
+  }
+
+
+  module("dialog/js/modules/actions", {
     setup: function() {
       testHelpers.setup();
     },
@@ -74,39 +97,50 @@
       "primary_user_provisioned");
   });
 
+  asyncTest("doStageUser with successful creation - trigger user_staged", function() {
+    testStageAddress("doStageUser", "user_staged");
+  });
+
   asyncTest("doConfirmUser - start the check_registration service", function() {
     testActionStartsModule("doConfirmUser", {email: TEST_EMAIL, siteName: "Unit Test Site"},
       "check_registration");
   });
 
+  asyncTest("doStageEmail with successful staging - trigger email_staged", function() {
+    testStageAddress("doStageEmail", "email_staged");
+  });
+
   asyncTest("doConfirmEmail - start the check_registration service", function() {
     testActionStartsModule("doConfirmEmail", {email: TEST_EMAIL, siteName: "Unit Test Site"},
       "check_registration");
   });
 
-  asyncTest("doGenerateAssertion - start the generate_assertion service", function() {
-    testActionStartsModule('doGenerateAssertion', { email: TEST_EMAIL }, "generate_assertion");
+  asyncTest("doForgotPassword - call the set_password controller with reset_password true", function() {
+    testActionStartsModule('doForgotPassword', { email: TEST_EMAIL }, "set_password");
   });
 
-  asyncTest("doStageUser with successful creation - trigger user_staged", function() {
-    createController({
-      ready: function() {
-        var email;
-        testHelpers.register("user_staged", function(msg, info) {
-          email = info.email;
-        });
+  asyncTest("doStageResetPassword - trigger password_reset_staged", function() {
+    testStageAddress("doStageResetPassword", "password_reset_staged");
+  });
 
-        controller.doStageUser({ email: TEST_EMAIL, password: "password", ready: function(status) {
-          equal(status, true, "correct status");
-          equal(email, TEST_EMAIL, "user successfully staged");
-          start();
-        }});
-      }
-    });
+  asyncTest("doConfirmResetPassword - start the check_registration service", function() {
+    testActionStartsModule("doConfirmResetPassword", {email: TEST_EMAIL, siteName: "Unit Test Site"},
+      "check_registration");
   });
 
-  asyncTest("doForgotPassword - call the set_password controller with reset_password true", function() {
-    testActionStartsModule('doForgotPassword', { email: TEST_EMAIL }, "set_password");
+  asyncTest("doStageReverifyEmail - trigger reverify_email_staged", function() {
+
+    storage.addSecondaryEmail(TEST_EMAIL, { verified: false });
+    testStageAddress("doStageReverifyEmail", "reverify_email_staged");
+  });
+
+  asyncTest("doConfirmReverifyEmail - start the check_registration service", function() {
+    testActionStartsModule("doConfirmReverifyEmail", {email: TEST_EMAIL, siteName: "Unit Test Site"},
+      "check_registration");
+  });
+
+  asyncTest("doGenerateAssertion - start the generate_assertion service", function() {
+    testActionStartsModule('doGenerateAssertion', { email: TEST_EMAIL }, "generate_assertion");
   });
 
   asyncTest("doRPInfo - start the rp_info service", function() {
diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js
index b01a6ac766c77407be9a085e90d61f8e53a226a2..5b458fbebb9cb7468a4d616d7adb789db9c0ede9 100644
--- a/resources/static/test/mocks/xhr.js
+++ b/resources/static/test/mocks/xhr.js
@@ -74,6 +74,17 @@ BrowserID.Mocks.xhr = (function() {
       "post /wsapi/complete_reset invalid": { success: false },
       "post /wsapi/complete_reset ajaxError": undefined,
 
+      "post /wsapi/stage_reverify unknown_secondary": { success: true },
+      "post /wsapi/stage_reverify valid": { success: true },
+      "post /wsapi/stage_reverify invalid": { success: false },
+      "post /wsapi/stage_reverify throttle": 429,
+      "post /wsapi/stage_reverify ajaxError": undefined,
+
+      "post /wsapi/complete_reverify valid": { success: true },
+      "post /wsapi/complete_reverify badPassword": 401,
+      "post /wsapi/complete_reverify invalid": { success: false },
+      "post /wsapi/complete_reverify ajaxError": undefined,
+
       "get /wsapi/user_creation_status?email=registered%40testuser.com pending": { status: "pending" },
       "get /wsapi/user_creation_status?email=registered%40testuser.com complete": { status: "complete", userid: 4 },
       "get /wsapi/user_creation_status?email=registered%40testuser.com mustAuth": { status: "mustAuth" },
@@ -89,6 +100,9 @@ BrowserID.Mocks.xhr = (function() {
       "get /wsapi/have_email?email=registered%40testuser.com valid": { email_known: true },
       "get /wsapi/have_email?email=registered%40testuser.com throttle": { email_known: true },
       "get /wsapi/have_email?email=registered%40testuser.com ajaxError": undefined,
+      "get /wsapi/have_email?email=testuser%40testuser.com valid": { email_known: true },
+      "get /wsapi/have_email?email=testuser%40testuser.com throttle": { email_known: true },
+      "get /wsapi/have_email?email=testuser%40testuser.com ajaxError": undefined,
       "get /wsapi/have_email?email=unregistered%40testuser.com valid": { email_known: false },
       "get /wsapi/have_email?email=unregistered%40testuser.com primary": { email_known: false },
       "post /wsapi/remove_email valid": { success: true },