diff --git a/resources/static/dialog/controllers/add_email.js b/resources/static/dialog/controllers/add_email.js
index da7755705a1607975ddf09c9ce8bce341e1659b2..f4b1b3c1bf6c2a473995e5724bc6d8c5672d655d 100644
--- a/resources/static/dialog/controllers/add_email.js
+++ b/resources/static/dialog/controllers/add_email.js
@@ -1,4 +1,4 @@
-/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
 /*global _: true, BrowserID: true, PageController: true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
diff --git a/resources/static/dialog/controllers/email_chosen.js b/resources/static/dialog/controllers/email_chosen.js
index 1042d1eefc0addadabdbe7e40d5479711826650c..0eed93025db81931613d7fded14d54740f171827 100644
--- a/resources/static/dialog/controllers/email_chosen.js
+++ b/resources/static/dialog/controllers/email_chosen.js
@@ -1,4 +1,4 @@
-/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
 /*global _: true, BrowserID: true, PageController: true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
diff --git a/resources/static/dialog/controllers/page.js b/resources/static/dialog/controllers/page.js
index e0dceb8218bbb4de6f374b70ec8c86f904055a0c..8f29623cdfbb3b22ba7ea26fe16f0956bd3d49a2 100644
--- a/resources/static/dialog/controllers/page.js
+++ b/resources/static/dialog/controllers/page.js
@@ -129,7 +129,9 @@ BrowserID.Modules.PageModule = (function() {
 
       bid.ErrorDisplay.start();
 
-      $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME, oncomplete);
+      $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME, function() {
+        if(oncomplete) oncomplete(false);
+      });
     },
 
     validate: function() {
diff --git a/resources/static/dialog/controllers/pick_email.js b/resources/static/dialog/controllers/pick_email.js
index 55b6789182fe034949249c49df208c3a5ca29cdf..2d88fefeedf4b0adb62018ce7330b7ce434d08ee 100644
--- a/resources/static/dialog/controllers/pick_email.js
+++ b/resources/static/dialog/controllers/pick_email.js
@@ -1,4 +1,4 @@
-/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
 /*global _: true, BrowserID: true, PageController: true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
diff --git a/resources/static/dialog/controllers/required_email.js b/resources/static/dialog/controllers/required_email.js
index a027061cb9447691587498ca19e34931c0cb94e5..b7754ca1824778f9ac4b3d03df327fc7d07234bb 100644
--- a/resources/static/dialog/controllers/required_email.js
+++ b/resources/static/dialog/controllers/required_email.js
@@ -1,4 +1,4 @@
-/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
 /*global _: true, BrowserID: true, PageController: true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
diff --git a/resources/static/dialog/controllers/set_password.js b/resources/static/dialog/controllers/set_password.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd84ddd442852c8ee8e7166760cc0129c0310c2f
--- /dev/null
+++ b/resources/static/dialog/controllers/set_password.js
@@ -0,0 +1,95 @@
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
+/*global _: true, BrowserID: true, PageController: 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 ***** */
+BrowserID.Modules.SetPassword = (function() {
+  "use strict";
+
+  var bid = BrowserID,
+      user = bid.User,
+      errors = bid.Errors,
+      helpers = bid.Helpers,
+      dom = bid.DOM,
+      sc;
+
+
+  function setPassword(oncomplete) {
+    function complete(status) {
+      if(oncomplete) oncomplete(status);
+    }
+
+    var self = this,
+        pass = dom.getInner("#password"),
+        vpass = dom.getInner("#vpassword"),
+        valid = bid.Validation.passwordAndValidationPassword(pass, vpass);
+
+    if(valid) {
+      user.setPassword(
+        pass,
+        function(status) {
+          self.publish("password_set");
+          complete(true);
+        },
+        self.getErrorDialog(errors.setPassword, complete)
+      );
+    }
+    else {
+      complete(false);
+    }
+  }
+
+  var Module = bid.Modules.PageModule.extend({
+    start: function(options) {
+      var self=this;
+      sc.start.call(self, options);
+
+      self.renderDialog("set_password", options);
+    },
+
+    submit: setPassword
+
+    // BEGIN TESTING API
+    ,
+
+    setPassword: setPassword
+    // END TESTING API
+  });
+
+  sc = Module.sc;
+
+  return Module;
+
+}());
+
diff --git a/resources/static/dialog/views/set_password.ejs b/resources/static/dialog/views/set_password.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..75572ed6cd4e7018d0f2f3ba44798d7aed14d8bc
--- /dev/null
+++ b/resources/static/dialog/views/set_password.ejs
@@ -0,0 +1,40 @@
+  <strong>
+    Sign in using
+  </strong>
+
+  <div class="form_section" id="set_password">
+      <ul class="inputs">
+          <li>
+              <label for="password" class="serif">Password</label>
+              <input class="sans" id="password" placeholder="Enter a Password" type="password" autofocus maxlength=80>
+
+              <div class="tooltip" id="password_required" for="password">
+                Password is required.
+              </div>
+
+              <div class="tooltip" id="password_too_short" for="password">
+                Password must be at least 8 characters long.
+              </div>
+          <li>
+
+          <li>
+              <label for="vpassword" class="serif">Verify Password</label>
+              <input class="sans" id="vpassword" placeholder="Repeat Password" type="password" maxlength=80>
+
+              <div class="tooltip" id="vpassword_required" for="vpassword">
+                Verification password is required.
+              </div>
+
+              <div class="tooltip" id="passwords_no_match" for="vpassword">
+                Passwords do not match.
+              </div>
+          <li>
+
+
+      </ul>
+
+      <div class="submit cf">
+          <button tabindex="1">Reset Password</button>
+          <!--button id="cancel_set_password" tabindex="2">Cancel</button-->
+      </div>
+  </div>
diff --git a/resources/static/shared/error-messages.js b/resources/static/shared/error-messages.js
index 894f84ec54ec0c51c47d2c7ce3171c26adbdafbc..9f96220519ce985403b2f6fd745c5684d984f9b5 100644
--- a/resources/static/shared/error-messages.js
+++ b/resources/static/shared/error-messages.js
@@ -126,6 +126,10 @@ BrowserID.Errors = (function(){
       title: "Remove Email Address from Account"
     },
 
+    setPassword: {
+      title: "Setting Password"
+    },
+
     signIn: {
       title: "Signin Failed"
     },
diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js
index 847f487a923d39e0b54c225ec752c79384feb836..23b70798690ceb0eef0187ce231f3b9d0a308902 100644
--- a/resources/static/shared/network.js
+++ b/resources/static/shared/network.js
@@ -387,15 +387,23 @@ BrowserID.Network = (function() {
     },
 
     /**
-     * Update the password of the current user. This is for a password reseT
-     * @method resetPassword
+     * Set the password of the current user.
+     * @method setPassword
      * @param {string} password - new password.
      * @param {function} [onComplete] - Callback to call when complete.
      * @param {function} [onFailure] - Called on XHR failure.
      */
-    resetPassword: function(password, onComplete, onFailure) {
-      // XXX fill this in.
-      if (onComplete) onComplete();
+    setPassword: function(password, onComplete, onFailure) {
+      post({
+        url: "/wsapi/set_password",
+        data: {
+          password: password
+        },
+        success: function(status) {
+          if (onComplete) onComplete(status.success);
+        },
+        error: onFailure
+      });
     },
 
     /**
diff --git a/resources/static/test/index.html b/resources/static/test/index.html
index d5c1e0a225590a8c0e499018a1f1cae2704b9c50..bc796b39b27c913d26704272c7fc05124e099fda 100644
--- a/resources/static/test/index.html
+++ b/resources/static/test/index.html
@@ -132,6 +132,7 @@
     <script type="text/javascript" src="/dialog/controllers/email_chosen.js"></script>
     <script type="text/javascript" src="/dialog/controllers/provision_primary_user.js"></script>
     <script type="text/javascript" src="/dialog/controllers/primary_user_provisioned.js"></script>
+    <script type="text/javascript" src="/dialog/controllers/set_password.js"></script>
 
     <script type="text/javascript" src="/pages/page_helpers.js"></script>
     <script type="text/javascript" src="/pages/add_email_address.js"></script>
@@ -182,6 +183,7 @@
     <script type="text/javascript" src="qunit/controllers/email_chosen_unit_test.js"></script>
     <script type="text/javascript" src="qunit/controllers/provision_primary_user_unit_test.js"></script>
     <script type="text/javascript" src="qunit/controllers/primary_user_provisioned_unit_test.js"></script>
+    <script type="text/javascript" src="qunit/controllers/set_password_unit_test.js"></script>
 
     <!-- must go last or all other tests will fail. -->
     <script type="text/javascript" src="qunit/controllers/dialog_unit_test.js"></script>
diff --git a/resources/static/test/qunit/controllers/set_password_unit_test.js b/resources/static/test/qunit/controllers/set_password_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..e58f84beeacaad5fd5b077a64ce35be87c6a4a00
--- /dev/null
+++ b/resources/static/test/qunit/controllers/set_password_unit_test.js
@@ -0,0 +1,137 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global 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 ***** */
+(function() {
+  "use strict";
+
+  var controller,
+      bid = BrowserID,
+      storage = bid.Storage,
+      testHelpers = bid.TestHelpers,
+      xhr = bid.Mocks.xhr,
+      register = bid.TestHelpers.register;
+
+  module("controllers/set_password", {
+    setup: function() {
+      testHelpers.setup();
+      createController();
+      $("#password").val("");
+      $("#vpassword").val("");
+    },
+
+    teardown: function() {
+      if (controller) {
+        try {
+          controller.destroy();
+          controller = null;
+        } catch(e) {
+          // could already be destroyed from the close
+        }
+      }
+      testHelpers.setup();
+    }
+  });
+
+
+  function createController(options) {
+    controller = bid.Modules.SetPassword.create();
+    controller.start(options);
+  }
+
+  function testInvalidInput() {
+    controller.setPassword(function(status) {
+      equal(false, status, "status is false");
+      testHelpers.testTooltipVisible();
+      start();
+    });
+  }
+
+  test("create displays the correct template", function() {
+    equal($("#set_password").length, 1, "the correct template is displayed");
+  });
+
+  asyncTest("setPassword with no password", function() {
+    $("#password").val("");
+    $("#vpassword").val("password");
+    testInvalidInput();
+  });
+
+  asyncTest("setPassword with no verification password", function() {
+    $("#password").val("password");
+    $("#vpassword").val("");
+    testInvalidInput();
+  });
+
+  asyncTest("setPassword with too short of a password", function() {
+    $("#password").val("pass");
+    $("#vpassword").val("pass");
+    testInvalidInput();
+  });
+
+  asyncTest("setPassword with mismatched passwords", function() {
+    $("#password").val("passwords");
+    $("#vpassword").val("password");
+    testInvalidInput();
+  });
+
+  asyncTest("setPassword with XHR error", function() {
+    $("#password").val("password");
+    $("#vpassword").val("password");
+    xhr.useResult("ajaxError");
+
+    controller.setPassword(function(status) {
+      equal(status, false, "correct status");
+      testHelpers.testErrorVisible();
+      start();
+    });
+  });
+
+  asyncTest("setPassword happy case", function() {
+    $("#password").val("password");
+    $("#vpassword").val("password");
+
+
+    register("password_set", function(msg, info) {
+      ok(true, msg + " message received");
+      start();
+    });
+
+    controller.setPassword(function(status) {
+      equal(status, true, "correct status");
+    });
+  });
+}());
+
diff --git a/resources/static/test/qunit/mocks/xhr.js b/resources/static/test/qunit/mocks/xhr.js
index c1f50c5dbbe717b9af189a9757acf72461c32292..b6c2cbde8c5910c1697d0ce76fa339f204e35a1b 100644
--- a/resources/static/test/qunit/mocks/xhr.js
+++ b/resources/static/test/qunit/mocks/xhr.js
@@ -117,6 +117,9 @@ BrowserID.Mocks.xhr = (function() {
       "get /wsapi/list_emails ajaxError": undefined,
       // Used in conjunction with registration to do a complete userflow
       "get /wsapi/list_emails complete": {"registered@testuser.com":{}},
+      "post /wsapi/set_password valid": { success: true },
+      "post /wsapi/set_password invalid": { success: false },
+      "post /wsapi/set_password ajaxError": undefined,
       "post /wsapi/update_password valid": { success: true },
       "post /wsapi/update_password incorrectPassword": { success: false },
       "post /wsapi/update_password invalid": undefined,
diff --git a/resources/static/test/qunit/shared/network_unit_test.js b/resources/static/test/qunit/shared/network_unit_test.js
index c09235b23545ffd97cfd496f73686fe434624198..ce9edf9b17199add6207fed396ae0ce2a89a58c5 100644
--- a/resources/static/test/qunit/shared/network_unit_test.js
+++ b/resources/static/test/qunit/shared/network_unit_test.js
@@ -587,32 +587,20 @@
     failureCheck(network.requestPasswordReset, "address", "origin");
   });
 
-  asyncTest("resetPassword", function() {
-    network.resetPassword("password", function onSuccess() {
-      // XXX need a test here;
-      ok(true);
-      start();
-    }, function onFailure() {
-      ok(false);
+  asyncTest("setPassword happy case expects true status", function() {
+    network.setPassword("password", function onComplete(status) {
+      equal(status, true, "correct status");
       start();
-    });
-
+    }, testHelpers.unexpectedXHRFailure);
   });
 
-  asyncTest("resetPassword with XHR failure", function() {
+  asyncTest("setPassword with XHR failure", function() {
     xhr.useResult("ajaxError");
-/*
-    the body of this function is not yet written
-
-    network.resetPassword("password", function onSuccess() {
-      ok(false, "XHR failure should never call success");
-      start();
-    }, function onFailure() {
-      ok(true, "XHR failure should always call failure");
-      start();
-    });
-*/
-    start();
+    network.setPassword(
+      "password",
+      testHelpers.unexpectedSuccess,
+      testHelpers.expectedXHRFailure
+    );
   });
 
   asyncTest("serverTime", function() {
diff --git a/resources/static/test/qunit/testHelpers/helpers.js b/resources/static/test/qunit/testHelpers/helpers.js
index 3775abd0ed6b03b8e986f06b5db0e3bde8302ad6..ac78a203cc868faa12e89dd70106288f45e57ef3 100644
--- a/resources/static/test/qunit/testHelpers/helpers.js
+++ b/resources/static/test/qunit/testHelpers/helpers.js
@@ -102,6 +102,11 @@
     unexpectedXHRFailure: function() {
       ok(false, "unexpected XHR failure");
       start();
+    },
+
+    testTooltipVisible: function() {
+      equal(tooltip.shown, true, "tooltip is visible");
     }
+
   };
 }());
diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs
index fbfafcfbde66aad4c81951824eead00d9950613c..212eb3b0e0ecd72c18785d910b8275b6bfd42348 100644
--- a/resources/views/dialog_layout.ejs
+++ b/resources/views/dialog_layout.ejs
@@ -94,6 +94,7 @@
           <script type="text/javascript" src="/dialog/controllers/verify_primary_user.js"></script>
           <script type="text/javascript" src="/dialog/controllers/provision_primary_user.js"></script>
           <script type="text/javascript" src="/dialog/controllers/primary_user_provisioned.js"></script>
+          <script type="text/javascript" src="/dialog/controllers/set_password.js"></script>
           <script type="text/javascript" src="/dialog/controllers/email_chosen.js"></script>
           <script type="text/javascript" src="/dialog/start.js"></script>
         <% } %>
diff --git a/resources/views/verifyuser.ejs b/resources/views/verifyuser.ejs
index 357644f2d43f64de920aed0ca3f9115e80f1b8e5..7a54af78988fab7a6299b99736c428cddf8cc268 100644
--- a/resources/views/verifyuser.ejs
+++ b/resources/views/verifyuser.ejs
@@ -37,7 +37,7 @@
             </div>
 
             <div class="tooltip" id="passwords_no_match" for="vpassword">
-              Passwords do not match
+              Passwords do not match.
             </div>
         </li>
       </ul>
diff --git a/scripts/compress.sh b/scripts/compress.sh
index d7d5ce845b09dfdb1a70f995728c6af2fcaf8cd7..6c1260f74422590639072f561d67f74285188ebc 100755
--- a/scripts/compress.sh
+++ b/scripts/compress.sh
@@ -54,7 +54,7 @@ cp templates.js $BUILD_PATH/templates.js
 cd ../..
 
 # produce the dialog js
-cat lib/jquery-1.6.2.min.js lib/winchan.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/provisioning.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgot_password.js dialog/controllers/check_registration.js dialog/controllers/pick_email.js dialog/controllers/add_email.js dialog/controllers/required_email.js dialog/controllers/verify_primary_user.js dialog/controllers/provision_primary_user.js dialog/controllers/primary_user_provisioned.js dialog/controllers/email_chosen.js dialog/start.js > $BUILD_PATH/dialog.uncompressed.js
+cat lib/jquery-1.6.2.min.js lib/winchan.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/provisioning.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgot_password.js dialog/controllers/check_registration.js dialog/controllers/pick_email.js dialog/controllers/add_email.js dialog/controllers/required_email.js dialog/controllers/verify_primary_user.js dialog/controllers/provision_primary_user.js dialog/controllers/primary_user_provisioned.js dialog/controllers/set_password.js dialog/controllers/email_chosen.js dialog/start.js > $BUILD_PATH/dialog.uncompressed.js
 
 # produce the dialog css
 cat css/common.css dialog/css/popup.css dialog/css/m.css > $BUILD_PATH/dialog.uncompressed.css