diff --git a/resources/static/dialog/controllers/authenticate_controller.js b/resources/static/dialog/controllers/authenticate_controller.js
index f6b4157abf13b4ff7ecc28e060dcdc2aaa96fe04..323fc4e1e3c4ca1132ca91a172723d296113665a 100644
--- a/resources/static/dialog/controllers/authenticate_controller.js
+++ b/resources/static/dialog/controllers/authenticate_controller.js
@@ -44,6 +44,7 @@
       validation = bid.Validation,
       tooltip = bid.Tooltip,
       helpers = bid.Helpers,
+      dialogHelpers = helpers.Dialog,
       dom = bid.DOM,
       lastEmail = "";
 
@@ -76,7 +77,7 @@
     cancelEvent(event);
 
     if(email) {
-      helpers.createUser.call(self, email);
+      dialogHelpers.createUser.call(self, email);
     }
   }
 
@@ -88,10 +89,12 @@
     cancelEvent(event);
 
     if(email && pass) {
-      helpers.authenticateUser.call(self, email, pass, function() {
-        self.close("authenticated", {
-          email: email
-        });
+      dialogHelpers.authenticateUser.call(self, email, pass, function(authenticated) {
+        if (authenticated) {
+          self.close("authenticated", {
+            email: email
+          });
+        }
       });
     }
   }
@@ -102,7 +105,7 @@
     cancelEvent(event);
 
     if(email) {
-      helpers.resetPassword.call(this, email);
+      dialogHelpers.resetPassword.call(this, email);
     }
   }
 
diff --git a/resources/static/dialog/controllers/pickemail_controller.js b/resources/static/dialog/controllers/pickemail_controller.js
index 57bcb677c286b282dab402dc4fd14f9e63116f4a..e5e02d13d51276c1255bdb8530fc476c9156cba7 100644
--- a/resources/static/dialog/controllers/pickemail_controller.js
+++ b/resources/static/dialog/controllers/pickemail_controller.js
@@ -43,6 +43,7 @@
       errors = bid.Errors,
       storage = bid.Storage,
       helpers = bid.Helpers,
+      dialogHelpers = helpers.Dialog,
       dom = bid.DOM,
       assertion;
 
@@ -109,7 +110,7 @@
         storage.site.set(origin, "remember", $("#remember").is(":checked"));
       }
 
-      helpers.getAssertion.call(self, email);
+      dialogHelpers.getAssertion.call(self, email);
     }
   }
 
@@ -128,7 +129,7 @@
         bid.Tooltip.showTooltip("#already_taken");
       }
       else {
-        helpers.addEmail.call(self, email);
+        dialogHelpers.addEmail.call(self, email);
       }
     }, self.getErrorDialog(errors.isEmailRegistered));
   }
diff --git a/resources/static/dialog/controllers/required_email_controller.js b/resources/static/dialog/controllers/required_email_controller.js
index 812c48dacf826bb3c9919630f98a6e90758c91fa..c9acd8d5415aca9d13956a6fff52cdfa1ce547b7 100644
--- a/resources/static/dialog/controllers/required_email_controller.js
+++ b/resources/static/dialog/controllers/required_email_controller.js
@@ -42,6 +42,7 @@
       user = bid.User,
       errors = bid.Errors,
       helpers = bid.Helpers,
+      dialogHelpers = helpers.Dialog,
       dom = bid.DOM,
       assertion;
 
@@ -54,7 +55,7 @@
     // If the user is already authenticated and they own this address, sign 
     // them right in.
     if(self.authenticated) {
-      helpers.getAssertion.call(self, email);
+      dialogHelpers.getAssertion.call(self, email);
     }
     else {
       // If the user is not already authenticated, but they potentially own 
@@ -62,12 +63,14 @@
       // get the password right.
       var password = helpers.getAndValidatePassword("#password");
       if (password) {
-        helpers.authenticateUser.call(self, email, password, function() {
-          // Now that the user has authenticated, sync their emails and get an 
-          // assertion for the email we care about.
-          user.syncEmailKeypair(email, function() {
-            helpers.getAssertion.call(self, email);
-          }, self.getErrorDialog(errors.syncEmailKeypair));
+        dialogHelpers.authenticateUser.call(self, email, password, function(authenticated) {
+          if (authenticated) {
+            // Now that the user has authenticated, sync their emails and get an 
+            // assertion for the email we care about.
+            user.syncEmailKeypair(email, function() {
+              dialogHelpers.getAssertion.call(self, email);
+            }, self.getErrorDialog(errors.syncEmailKeypair));
+          }
         });
       }
     }
@@ -88,10 +91,10 @@
       // If the address is registered, it means another account has control of 
       // the address and we are consolidating.  If the email is not registered 
       // then it means add the address to the current user's account.
-      helpers.addEmail.call(self, self.email);
+      dialogHelpers.addEmail.call(self, self.email);
     }
     else {
-      helpers.createUser.call(self, self.email);
+      dialogHelpers.createUser.call(self, self.email);
     }
   }
 
@@ -99,7 +102,7 @@
     event && event.preventDefault();
 
     var self=this;
-    helpers.resetPassword.call(self, self.email);
+    dialogHelpers.resetPassword.call(self, self.email);
   }
 
 
diff --git a/resources/static/dialog/dialog.js b/resources/static/dialog/dialog.js
index 46beb215dc08ac90235dbb0472657e0411158200..7ccca0ba918d6b06d2c5e95cb931a3ae19117a0d 100644
--- a/resources/static/dialog/dialog.js
+++ b/resources/static/dialog/dialog.js
@@ -44,8 +44,7 @@ steal
               'jquery/controller',			// a widget factory
               'jquery/controller/subscribe')	// subscribe to OpenAjax.hub
 
-	.resources(
-               'channel')
+	.resources(  'channel')
   .then(
                '../lib/jschannel',
                '../lib/base64',
@@ -67,7 +66,8 @@ steal
                '../shared/browser-support',
                '../shared/browserid-extensions',
                '../shared/wait-messages',
-               '../shared/helpers'
+               '../shared/helpers',
+               'resources/helpers'
                )
 
 	.controllers('page',
diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1f1a61cb3c435e2716e4e2a005d08b6dca2c7e6
--- /dev/null
+++ b/resources/static/dialog/resources/helpers.js
@@ -0,0 +1,158 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global BrowserID: true*/
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// The way this works, when the dialog is opened from a web page, it opens
+// the window with a #host=<requesting_host_name> parameter in its URL.
+// window.setupChannel is called automatically when the dialog is opened. We
+// assume that navigator.id.getVerifiedEmail was the function called, we will
+// keep this assumption until we start experimenting.  Since IE has some
+// serious problems iwth postMessage from a window to a child window, we are now
+// communicating not directly with the calling window, but with an iframe
+// on the same domain as us that we place into the calling window.  We use a
+// function within this iframe to relay messages back to the calling window.
+// We do so by searching for the frame within the calling window, and then
+// getting a reference to the proxy function.  When getVerifiedEmail is
+// complete, it calls the proxy function in the iframe, which then sends a
+// message back to the calling window.
+
+(function() {
+  "use strict";
+
+  var bid = BrowserID,
+      helpers = bid.Helpers,
+      user = bid.User,
+      tooltip = bid.Tooltip,
+      errors = bid.Errors;
+
+  function animateClose(callback) {
+    var body = $("body"),
+        doAnimation = $("#signIn").length && body.innerWidth() > 640;
+
+    if (doAnimation) {
+      $("#signIn").animate({"width" : "685px"}, "slow", function () {
+        // post animation
+         body.delay(500).animate({ "opacity" : "0.5"}, "fast", function () {
+           callback();
+         });
+      });
+    }
+    else {
+      callback();
+    }
+  }
+
+  function getAssertion(email, callback) {
+    var self=this;
+    user.getAssertion(email, function(assert) {
+      assert = assert || null;
+      animateClose(function() {
+        self.close("assertion_generated", {
+          assertion: assert
+        });
+
+        if (callback) callback(assert);
+      });
+    }, self.getErrorDialog(errors.getAssertion));
+  }
+
+  function authenticateUser(email, pass, callback) {
+    var self=this;
+    user.authenticate(email, pass,
+      function onComplete(authenticated) {
+        if (!authenticated) {
+          tooltip.showTooltip("#cannot_authenticate");
+        }
+        if (callback) callback(authenticated);
+      }, self.getErrorDialog(errors.authenticate));
+  }
+
+  function createUser(email, callback) {
+    var self=this;
+    user.createUser(email, function(staged) {
+      if (staged) {
+        self.close("user_staged", {
+          email: email
+        });
+      }
+      else {
+        tooltip.showTooltip("#could_not_add");
+      }
+      if (callback) callback(staged);
+    }, self.getErrorDialog(errors.createUser));
+  }
+
+  function resetPassword(email, callback) {
+    var self=this;
+    user.requestPasswordReset(email, function(status) {
+      if(status.success) {
+        self.close("reset_password", {
+          email: email
+        });
+      }
+      else {
+        tooltip.showTooltip("#could_not_add");
+      }
+      if (callback) callback(status.success);
+    }, self.getErrorDialog(errors.requestPasswordReset));
+  }
+
+  function addEmail(email, callback) {
+    var self=this;
+    user.addEmail(email, function(added) {
+      if (added) {
+        self.close("email_staged", {
+          email: email
+        });
+      }
+      else {
+        tooltip.showTooltip("#could_not_add");
+      }
+      if (callback) callback(added);
+    }, self.getErrorDialog(errors.addEmail));
+  }
+
+  helpers.Dialog = helpers.Dialog || {};
+
+  helpers.extend(helpers.Dialog, {
+    getAssertion: getAssertion,
+    authenticateUser: authenticateUser,
+    createUser: createUser,
+    addEmail: addEmail,
+    resetPassword: resetPassword
+  });
+
+}());
diff --git a/resources/static/shared/helpers.js b/resources/static/shared/helpers.js
index 339211313ae841c31b5f31793291f63d94175bc9..041c8b82d5f48fe4a984e48304d475272f269070 100644
--- a/resources/static/shared/helpers.js
+++ b/resources/static/shared/helpers.js
@@ -66,97 +66,6 @@
     return password;
   }
 
-  /**
-   * XXX add a test for the next two and move them into a dialog specific 
-   * helper module!
-   */
-  function animateClose(callback) {
-    var body = $("body"),
-        doAnimation = $("#signIn").length && body.innerWidth() > 640;
-
-    if (doAnimation) {
-      $("#signIn").animate({"width" : "685px"}, "slow", function () {
-        // post animation
-         body.delay(500).animate({ "opacity" : "0.5"}, "fast", function () {
-           callback();
-         });
-      });
-    }
-    else {
-      callback();
-    }
-  }
-
-  // XXX Move this to a dialog specific module
-  function getAssertion(email) {
-    var self=this;
-    user.getAssertion(email, function(assert) {
-      assert = assert || null;
-      animateClose(function() {
-        self.close("assertion_generated", {
-          assertion: assert
-        });
-      });
-    }, self.getErrorDialog(errors.getAssertion));
-  }
-
-  // XXX Move this to a dialog specific module
-  function authenticateUser(email, pass, callback) {
-    var self=this;
-    user.authenticate(email, pass,
-      function onComplete(authenticated) {
-        if (authenticated) {
-          callback();
-        } else {
-          tooltip.showTooltip("#cannot_authenticate");
-        }
-      }, self.getErrorDialog(errors.authenticate));
-  }
-
-  function createUser(email) {
-    var self=this;
-    user.createUser(email, function(staged) {
-      if (staged) {
-        self.close("user_staged", {
-          email: email
-        });
-      }
-      else {
-        tooltip.showTooltip("#could_not_add");
-      }
-    }, self.getErrorDialog(errors.createUser));
-  }
-
-  function resetPassword(email) {
-    var self=this;
-    user.requestPasswordReset(email, function(status) {
-      if(status.success) {
-        self.close("reset_password", {
-          email: email
-        });
-      }
-      else {
-        tooltip.showTooltip("#could_not_add");
-      }
-    }, self.getErrorDialog(errors.requestPasswordReset));
-  }
-
-  function addEmail(email) {
-    var self=this;
-    user.addEmail(email, function(added) {
-      if (added) {
-        self.close("email_staged", {
-          email: email
-        });
-      }
-      else {
-        tooltip.showTooltip("#could_not_add");
-      }
-    }, function onFailure() {
-        tooltip.showTooltip("#could_not_add");
-    });
-  }
-
   extend(helpers, {
     /**
      * Extend an object with the properties of another object.  Overwrites 
@@ -181,17 +90,8 @@
      * @param {string} target - target containing the password
      * @return {string} password if password is valid, null otw.
      */
-    getAndValidatePassword: getAndValidatePassword,
+    getAndValidatePassword: getAndValidatePassword
 
-    /**
-     * XXX Get from here down out of here and into a specific dialog helpers 
-     * module.
-     */
-    getAssertion: getAssertion,
-    authenticateUser: authenticateUser,
-    createUser: createUser,
-    addEmail: addEmail,
-    resetPassword: resetPassword
   });
 
 
diff --git a/resources/static/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js
index d129414f8a9110270966219c336d14c3e6b551ef..4fc861768aea63c06198d76fb461fd614a0173fa 100644
--- a/resources/static/test/qunit/qunit.js
+++ b/resources/static/test/qunit/qunit.js
@@ -22,6 +22,8 @@ steal.plugins(
       "/shared/validation",
       "/shared/helpers",
 
+      "/dialog/resources/helpers",
+
       "/dialog/controllers/page_controller",
       "/dialog/controllers/required_email_controller",
 
@@ -49,6 +51,7 @@ steal.plugins(
       "shared/network_unit_test",
       "shared/user_unit_test",
       "resources/channel_unit_test",
+      "resources/helpers_unit_test",
 
       "controllers/page_controller_unit_test",
       "controllers/pickemail_controller_unit_test",
diff --git a/resources/static/test/qunit/resources/channel_unit_test.js b/resources/static/test/qunit/resources/channel_unit_test.js
index 94c86024750ee65457228e4595406fda387b37af..edc8d821772b693bf5121fbb0a8e7869bb089140 100644
--- a/resources/static/test/qunit/resources/channel_unit_test.js
+++ b/resources/static/test/qunit/resources/channel_unit_test.js
@@ -35,6 +35,8 @@
  *
  * ***** END LICENSE BLOCK ***** */
 steal.then("/dialog/resources/channel", function() {
+  "use strict";
+
   var channel = BrowserID.Channel;
 
   var navMock = {
diff --git a/resources/static/test/qunit/resources/helpers_unit_test.js b/resources/static/test/qunit/resources/helpers_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..75c8fcaa3dddddc201740b63123ffffe43ea8043
--- /dev/null
+++ b/resources/static/test/qunit/resources/helpers_unit_test.js
@@ -0,0 +1,262 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+steal.then(function() {
+  "use strict";
+
+  var bid = BrowserID,
+      helpers = bid.Helpers,
+      dialogHelpers = helpers.Dialog,
+      network = bid.Network,
+      xhr = bid.Mocks.xhr,
+      user = bid.User,
+      storage = bid.Storage,
+      tooltip = bid.Tooltip,
+      closeCB,
+      errorCB;
+
+  var controllerMock = {
+    close: function(message, info) {
+      closeCB && closeCB(message, info);
+    },
+
+    getErrorDialog: function(info) {
+      return function() {
+        errorCB && errorCB(info);
+      }
+    }
+  };
+
+  function expectedClose(message, field, value) {
+    return function(m, info) {
+      ok(m, message, "correct message: " + message);
+
+      if(value) {
+        equal(info[field], value, field + " has correct value of " + value);
+      }
+      else {
+        ok(info[field], field + " has a value");
+      }
+    }
+  }
+
+  function badError() {
+    ok(false, "error should have never been called");
+  }
+
+  function expectedError() {
+    ok(true, "error condition expected");
+    start();
+  }
+
+  function badClose() {
+    ok(false, "close should have never been called");
+  }
+
+  module("resources/helpers", {
+    setup: function() {
+      network.setXHR(xhr);
+      xhr.useResult("valid");
+      storage.clear();
+      closeCB = errorCB = null;
+      errorCB = badError;
+    },
+
+    teardown: function() {
+      network.setXHR($);
+    }
+  });
+
+  test("getAssertion happy case", function() {
+    closeCB = expectedClose("assertion_generated", "assertion");
+
+    storage.addEmail("registered@testuser.com", {});
+    dialogHelpers.getAssertion.call(controllerMock, "registered@testuser.com", function(assertion) {
+      ok(assertion, "assertion given to close");
+      start();
+    });
+
+    stop();
+  });
+
+  test("getAssertion with XHR error", function() {
+    closeCB = badClose;
+    errorCB = expectedError;
+
+    xhr.useResult("ajaxError");
+    storage.addEmail("registered@testuser.com", {});
+    dialogHelpers.getAssertion.call(controllerMock, "registered@testuser.com", function() {
+      ok(false, "unexpected finish");
+      start();  
+    });
+    stop();
+  });
+
+  test("authenticateUser happy case", function() {
+    dialogHelpers.authenticateUser.call(controllerMock, "testuser@testuser.com", "password", function(authenticated) {
+      equal(authenticated, true, "user is authenticated");
+      start();
+    });
+
+    stop();
+  });
+
+  test("authenticateUser invalid credentials", function() {
+    xhr.useResult("invalid");
+    dialogHelpers.authenticateUser.call(controllerMock, "testuser@testuser.com", "password", function(authenticated) {
+      equal(authenticated, false, "user is not authenticated");
+      start();
+    });
+
+    stop();
+  });
+
+  test("authenticateUser XHR error", function() {
+    errorCB = expectedError;
+
+    xhr.useResult("ajaxError");
+    dialogHelpers.authenticateUser.call(controllerMock, "testuser@testuser.com", "password", function() {
+      ok(false, "unexpected success callback");
+      start();
+    });
+
+    stop();
+  });
+
+  test("createUser happy case", function() {
+    closeCB = expectedClose("user_staged", "email", "unregistered@testuser.com");
+
+    dialogHelpers.createUser.call(controllerMock, "unregistered@testuser.com", function(staged) {
+      equal(staged, true, "user was staged");
+      start(); 
+    });
+
+    stop();
+  });
+
+  test("createUser could not create case", function() {
+    closeCB = badClose;
+
+    xhr.useResult("invalid");
+    dialogHelpers.createUser.call(controllerMock, "registered@testuser.com", function(staged) {
+      equal(staged, false, "user was not staged");
+      start();
+    });
+
+    stop();
+  });
+
+
+  test("createUser with XHR error", function() {
+    errorCB = expectedError;
+
+    xhr.useResult("ajaxError");
+    dialogHelpers.createUser.call(controllerMock, "registered@testuser.com", function(staged) {
+      ok(false, "complete should not have been called");
+      start();
+    });
+    stop();
+  });
+
+  test("addEmail happy case", function() {
+    closeCB = expectedClose("email_staged", "email", "unregistered@testuser.com");
+    dialogHelpers.addEmail.call(controllerMock, "unregistered@testuser.com", function(added) {
+      ok(added, "email added");
+      start();
+    });
+
+    stop();
+  });
+
+
+  test("addEmail throttled", function() {
+    xhr.useResult("throttle");
+    dialogHelpers.addEmail.call(controllerMock, "unregistered@testuser.com", function(added) {
+      equal(added, false, "email not added");
+      start();
+    });
+
+    stop();
+  });
+
+  test("addEmail with XHR error", function() {
+    errorCB = expectedError;
+
+    xhr.useResult("ajaxError");
+    dialogHelpers.addEmail.call(controllerMock, "unregistered@testuser.com", function(added) {
+      ok(false, "unexpected close");
+      start();
+    });
+
+    stop();
+  });
+
+  test("resetPassword happy case", function() {
+    closeCB = expectedClose("reset_password", "email", "registered@testuser.com");
+    dialogHelpers.resetPassword.call(controllerMock, "registered@testuser.com", function(reset) {
+      ok(reset, "password reset");
+      start();
+    });
+
+    stop();
+  });
+
+
+  test("resetPassword throttled", function() {
+    xhr.useResult("throttle");
+    dialogHelpers.resetPassword.call(controllerMock, "registered@testuser.com", function(reset) {
+      equal(reset, false, "password not reset");
+      start();
+    });
+
+    stop();
+  });
+
+  test("resetPassword with XHR error", function() {
+    errorCB = expectedError;
+
+    xhr.useResult("ajaxError");
+    dialogHelpers.resetPassword.call(controllerMock, "registered@testuser.com", function(reset) {
+      ok(false, "unexpected close");
+      start();
+    });
+
+    stop();
+  });
+});
+
+
+