diff --git a/lib/wsapi/update_password.js b/lib/wsapi/update_password.js
new file mode 100644
index 0000000000000000000000000000000000000000..33d585390fae01e6a4c7a62d46e60df2ce069c00
--- /dev/null
+++ b/lib/wsapi/update_password.js
@@ -0,0 +1,44 @@
+const
+db = require('../db.js'),
+wsapi = require('../wsapi.js'),
+httputils = require('../httputils'),
+logger = require('../logging.js').logger,
+bcrypt = require('bcrypt');
+
+exports.method = 'post';
+exports.writes_db = true;
+exports.authed = true;
+exports.args = ['oldpass','newpass'];
+
+exports.process = function(req, res) {
+  db.checkAuth(req.session.authenticatedUser, function(hash) {
+    if (typeof hash !== 'string' || typeof req.body.oldpass !== 'string')
+    {
+      return res.json({ success: false });
+    }
+
+    bcrypt.compare(req.body.oldpass, hash, function (err, success) {
+      if (err) {
+        logger.warn("error comparing passwords with bcrypt: " + err);
+        return res.json({ success: false });
+      }
+
+      logger.info("updating password for email " + req.session.authenticatedUser);
+      wsapi.bcryptPassword(req.body.newpass, function(err, hash) {
+        if (err) {
+          logger.error("error bcrypting  password for password update for " + req.body.email, err);
+          return res.json({ success: false });
+        }
+
+        db.updatePassword(req.session.authenticatedUser, hash, function(err) {
+          var success = true;
+          if (err) {
+            logger.error("error updating bcrypted password for email " + req.body.email, err);
+            success = false;
+          }
+          return res.json({ success: success });
+        });
+      });
+    });
+  });
+};
diff --git a/tests/password-update-test.js b/tests/password-update-test.js
new file mode 100755
index 0000000000000000000000000000000000000000..a8d8ed8f208662d0e12c3cbba534ff80d75c7b62
--- /dev/null
+++ b/tests/password-update-test.js
@@ -0,0 +1,162 @@
+#!/usr/bin/env node
+
+/* ***** 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 ***** */
+
+require('./lib/test_env.js');
+
+const assert =
+require('assert'),
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js'),
+db = require('../lib/db.js'),
+config = require('../lib/configuration.js'),
+bcrypt = require('bcrypt');
+
+var suite = vows.describe('password-length');
+
+// disable vows (often flakey?) async error behavior
+suite.options.error = false;
+
+start_stop.addStartupBatches(suite);
+
+const TEST_EMAIL = 'someuser@somedomain.comt',
+      OLD_PASSWORD = 'thisismyoldpassword',
+      NEW_PASSWORD = 'thisismynewpassword';
+
+// surpress console output of emails with a noop email interceptor
+var token = undefined;
+
+// first stage the account
+suite.addBatch({
+  "account staging": {
+    topic: wsapi.post('/wsapi/stage_user', {
+      email: TEST_EMAIL,
+      site: 'fakesite.com'
+    }),
+    "works":     function(r, err) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+// wait for the token
+suite.addBatch({
+  "a token": {
+    topic: function() {
+      start_stop.waitForToken(this.callback);
+    },
+    "is obtained": function (t) {
+      assert.strictEqual(typeof t, 'string');
+      token = t;
+    }
+  }
+});
+
+// create a new account via the api with (first address)
+suite.addBatch({
+  "setting password": {
+    topic: function() {
+      wsapi.post('/wsapi/complete_user_creation', {
+        token: token,
+        pass: OLD_PASSWORD
+      }).call(this);
+    },
+    "works just fine": function(r, err) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+suite.addBatch({
+  "authenticating with the password": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: OLD_PASSWORD
+    }),
+    "works as expected": function(r, err) {
+      assert.strictEqual(JSON.parse(r.body).success, true);
+    }
+  },
+  "authenticating with the wrong password": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: NEW_PASSWORD
+    }),
+    "fails as expected": function(r, err) {
+      assert.strictEqual(JSON.parse(r.body).success, false);
+    }
+  }
+});
+
+suite.addBatch({
+  "updating the password": {
+    topic: wsapi.post('/wsapi/update_password', {
+      oldpass: OLD_PASSWORD,
+      newpass: NEW_PASSWORD
+    }),
+    "works as expected": function(r, err) {
+      assert.strictEqual(JSON.parse(r.body).success, true);
+    }
+  }
+});
+
+suite.addBatch({
+  "authenticating with the password": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: NEW_PASSWORD
+    }),
+    "works as expected": function(r, err) {
+      assert.strictEqual(JSON.parse(r.body).success, true);
+    }
+  },
+  "authenticating with the wrong password": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: OLD_PASSWORD
+    }),
+    "fails as expected": function(r, err) {
+      assert.strictEqual(JSON.parse(r.body).success, false);
+    }
+  }
+});
+
+start_stop.addShutdownBatches(suite);
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);