diff --git a/.gitignore b/.gitignore
index a03c5e3933569d05747d2a3e1219fbce7da36a60..1823f2e5796b4df5d7c5694ff922d2e906b415a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 *~
 \#*\#
 .\#*
-node_modules
+/node_modules
+/var
diff --git a/ChangeLog b/ChangeLog
index a2e7f3ae24920567d2acba932cb5f5ffa8888c8a..87040e9dfcabf257e202b9adcdf208d4c74836dd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,2 +1,12 @@
+train-2011.08.04:
+  * when user closes dialog without clicking "cancel", properly return 'null' to the webpage (via getVerifiedEmail callback) - issue #107
+  * improve checks to warn developer that prerequisite software is missing. issue #110
+  * parameterize software to support multiple deployment environments (dev/beta/prod) issues #102 & #52
+  * documentation updates.
+  * improved logging (using the winston logging framework for node.js)
+  * [website] fixed inclusion of youtube video (now over https to keep browsers from getting scared about mixed mode resource inclusion)
+
 train-1:
-  * beginning of time, everything is new.  
+  * beginning of time, everything is new.
+  * (2011.08.03) include youtube video embedding over https (issue #112)
+  * (2011.08.04) fix mozillalabs.com link in dialog (issue #116)
diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md
index 54a350162cdea99ed069928217df0c21cf9814b7..36639c0b2461db12f0ddd7f25bb51f63254e929d 100644
--- a/DEPLOYMENT.md
+++ b/DEPLOYMENT.md
@@ -85,9 +85,17 @@ Subsequent steps use different software which you might need to install.
 
   * **curl** - used to iniate http requests from the cmd line (to kick the browserid server)
   * **java** - used to minify css
-  * **libsqlite3-dev** - database libraries 
+  * **mysql 5.1+** - the preferred persistence backend
 
-### 4. Set up post-update hook
+### 4. Set up mysql
+
+  0. ensure you can connect via TCP - localhost:3306 (like, make sure skip-networking is off in my.cnf)
+  1. connect to the database as user root
+  2. `CREATE USER 'browserid'@'localhost' IDENTIFIED BY 'browserid';`
+  3. `CREATE DATABASE browserid;`
+  4. `GRANT CREATE, DELETE, INDEX, INSERT, LOCK TABLES, SELECT, UPDATE ON browserid.* TO 'browserid'@'localhost';`
+
+### 5. Set up post-update hook
 
 *This step is optional* - if you want to manually update code you
  probably skipped step #1, you can skip this one as well.  All you need
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..173bdc47afb6bff9936d8c92f1742d4f21244261
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,5 @@
+This software is available under your choice of the following licenses:
+
+  * MPL 1.1 or later: http://www.mozilla.org/MPL/
+  * GPL 2.0 or later: http://www.gnu.org/licenses/gpl.html
+  * LGPL 2.1 or later: http://www.gnu.org/licenses/lgpl.html
diff --git a/README.md b/README.md
index e84edefb0d776fd66d4553e07bd87f77352498a3..518808162dd1ff1aad9c10ecd7bd72330778e21b 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,6 @@ Here's the software you'll need installed:
 
 * node.js (>= 0.4.5): http://nodejs.org/
 * npm: http://npmjs.org/
-* sqlite (3) development libraries: http://www.sqlite.org/
 * Several node.js 3rd party libraries - see `package.json` for details
 
 ## Getting started:
diff --git a/browserid/app.js b/browserid/app.js
index c62b6a2d8ec9dc419cb9c7a3e7d0805825691083..2f020e2bcec9d7a07493cce35f927bd31a420c1e 100644
--- a/browserid/app.js
+++ b/browserid/app.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 const
 fs = require('fs'),
 path = require('path');
@@ -12,12 +47,17 @@ crypto = require('crypto'),
 wsapi = require('./lib/wsapi.js'),
 httputils = require('./lib/httputils.js'),
 webfinger = require('./lib/webfinger.js'),
-sessions = require('cookie-sessions'),
+//sessions = require('cookie-sessions'),
+sessions = require('connect-cookie-session'),
 express = require('express'),
 secrets = require('./lib/secrets.js'),
 db = require('./lib/db.js'),
 configuration = require('../libs/configuration.js'),
 substitution = require('../libs/substitute.js');
+logging = require("../libs/logging.js");
+
+// open the databse
+db.open(configuration.get('database'));
 
 // looks unused, see run.js
 // const STATIC_DIR = path.join(path.dirname(__dirname), "static");
@@ -32,7 +72,7 @@ function internal_redirector(new_url) {
 }
 
 function router(app) {
-  app.set("views", __dirname + '/views'); 
+  app.set("views", __dirname + '/views');
 
   app.set('view options', {
     production: configuration.get('use_minified_resources')
@@ -40,7 +80,8 @@ function router(app) {
 
   // this should probably be an internal redirect
   // as soon as relative paths are figured out.
-  app.get('/sign_in', function(req, res, next ){
+  app.get('/sign_in', function(req, res, next ) {
+    logging.userEntry('browserid', req);
     res.render('dialog.ejs', {
       title: 'A Better Way to Sign In',
       layout: false,
@@ -114,15 +155,31 @@ function router(app) {
 exports.varDir = VAR_DIR;
 
 exports.setup = function(server) {
+  // over SSL?
+  var overSSL = (configuration.get('scheme') == 'https');
+  
   server.use(express.cookieParser());
 
   var cookieSessionMiddleware = sessions({
     secret: COOKIE_SECRET,
-    session_key: COOKIE_KEY,
-    path: '/'
+    //    session_key: COOKIE_KEY,
+    key: COOKIE_KEY,
+    cookie: {
+        path: '/',
+        httpOnly: true,
+        maxAge: 14400000,
+        secure: overSSL
+      }
   });
 
+  // cookie sessions
   server.use(function(req, resp, next) {
+    // we set this parameter so the connect-cookie-session
+    // sends the cookie even though the local connection is HTTP
+    // (the load balancer does SSL)
+    if (overSSL)
+      req.connection.proxySecure = true;
+
     try {
       cookieSessionMiddleware(req, resp, next);
     } catch(e) {
@@ -156,6 +213,15 @@ exports.setup = function(server) {
     next();
   });
 
+  // Strict Transport Security
+  server.use(function(req, resp, next) {
+      if (overSSL) {
+        // expires in 30 days, include subdomains like www
+        resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains");
+      }
+      next();
+    });
+
   // prevent framing
   server.use(function(req, resp, next) {
     resp.setHeader('x-frame-options', 'DENY');
diff --git a/browserid/lib/db.js b/browserid/lib/db.js
index 482fad4c1d92afa2b94ae982959d540a6f880571..7baab9a6e27577257ee7b21dfa05e69c05098698 100644
--- a/browserid/lib/db.js
+++ b/browserid/lib/db.js
@@ -1,391 +1,111 @@
-const
-sqlite = require('sqlite'),
-path = require('path');
-
-var VAR_DIR = path.join(path.dirname(__dirname), "var");
-
-var db = new sqlite.Database();
-
-// a configurable parameter if set immediately after require() of db.js
-exports.dbPath = path.join(VAR_DIR, "authdb.sqlite");
+/* ***** 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 ***** */
+
+var driver;
 
 var ready = false;
 var waiting = [];
 
+function checkReady() {
+  if (!ready) throw "database not ready.  did you call open()?";
+}
+
 // async break allow database path to be configured by calling code
 // a touch tricky cause client must set dbPath before releasing
 // control of the runloop
-setTimeout(function() {
-  db.open(exports.dbPath, function (error) {
-    if (error) {
-      console.log("Couldn't open database: " + error);
-      throw error;
-    }
-    db.executeScript(
-      "CREATE TABLE IF NOT EXISTS users  ( id INTEGER PRIMARY KEY, password TEXT );" +
-        "CREATE TABLE IF NOT EXISTS emails ( id INTEGER PRIMARY KEY, user INTEGER, address TEXT UNIQUE );" +
-        "CREATE TABLE IF NOT EXISTS keys   ( id INTEGER PRIMARY KEY, email INTEGER, key TEXT, expires INTEGER )",
-      function (error) {
-        if (error) {
-          throw error;
-        }
-        ready = true;
-        waiting.forEach(function(f) { f() });
-        waiting = [];
-      });
-  });
-}, 0);
-
-// accepts a function that will be invoked once the database is ready for transactions.
-// this hook is important to pause the rest of application startup until async database
-// connection establishment is complete.
-exports.onReady = function(f) {
-  setTimeout(function() {
-    if (ready) f();
-    else waiting.push(f);
-  }, 0);
-};
-
-// XXX: g_staged and g_stagedEmails should be moved into persistent/fast storage.
-
-// half created user accounts (pending email verification)
-// OR
-// half added emails (pending verification)
-var g_staged = {
-};
-
-// an email to secret map for efficient fulfillment of isStaged queries
-var g_stagedEmails = {
-};
-
-function executeTransaction(statements, cb) {
-  function executeTransaction2(statements, cb) {
-    if (statements.length == 0) cb();
-    else {
-      var s = statements.shift();
-      db.execute(s[0], s[1], function(err, rows) {
-        if (err) cb(err);
-        else executeTransaction2(statements, cb);
-      });
-    }
+exports.open = function(cfg, cb) {
+  var driverName = "json";
+  if (cfg && cfg.driver) driverName = cfg.driver;
+  try {
+    driver = require('./db_' + driverName + '.js');
+  } catch(e) {
+    var msg = "FATAL: couldn't find database driver: " + driverName;
+    console.log(msg);
+    throw msg + ": " + e.toString();
   }
 
-  db.execute('BEGIN', function(err, rows) {
-    executeTransaction2(statements, function(err) {
-      if (err) cb(err);
-      else db.execute('COMMIT', function(err, rows) {
-        cb(err);
-      });
-    });
-  });
-}
-
-function emailToUserID(email, cb) {
-  db.execute(
-    'SELECT users.id FROM emails, users WHERE emails.address = ? AND users.id == emails.user',
-    [ email ],
-    function (err, rows) {
-      if (rows && rows.length == 1) {
-        cb(rows[0].id);
-      } else {
-        if (err) console.log("database error: " + err);
-        cb(undefined);
+  driver.open(cfg, function(error) {
+    if (error) {
+      if (cb) cb(error);
+      else {
+        console.log("ERROR:" + error);
+        process.exit(1);
       }
-    });
-}
-
-exports.emailKnown = function(email, cb) {
-  db.execute(
-    "SELECT id FROM emails WHERE address = ?",
-    [ email ],
-    function(error, rows) {
-      cb(rows.length > 0);
-    });
-};
-
-// XXX: should be moved to async.
-exports.isStaged = function(email) {
-  return g_stagedEmails.hasOwnProperty(email);
-};
-
-function generateSecret() {
-  var str = "";
-  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-  for (var i=0; i < 48; i++) {
-    str += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
-  }
-  return str;
-}
-
-function addEmailToAccount(existing_email, email, pubkey, cb) {
-  emailToUserID(existing_email, function(userID) {
-    if (userID == undefined) {
-      cb("no such email: " + existing_email, undefined);
     } else {
-      executeTransaction([
-        [ "INSERT INTO emails (user, address) VALUES(?,?)", [ userID, email ] ],
-        [ "INSERT INTO keys (email, key, expires) VALUES(last_insert_rowid(),?,?)",
-          [ pubkey, ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000)) ]
-        ]
-      ], function (error) {
-        if (error) cb(error);
-        else cb();
-      });
+      ready = true;
+      waiting.forEach(function(f) { f() });
+      waiting = [];
+      if (cb) cb();
     }
   });
-}
-
-exports.emailsBelongToSameAccount = function(lhs, rhs, cb) {
-  emailToUserID(lhs, function(lhs_uid) {
-    emailToUserID(rhs, function(rhs_uid) {
-      cb(lhs_uid === rhs_uid);
-    }, function (error) {
-      cb(false);
-    });
-  }, function (error) {
-    cb(false);
-  });
 };
 
-exports.addKeyToEmail = function(existing_email, email, pubkey, cb) {
-  emailToUserID(existing_email, function(userID) {
-    if (userID == undefined) {
-      cb("no such email: " + existing_email, undefined);
-      return;
-    }
 
-    db.execute("SELECT emails.id FROM emails,users WHERE users.id = ? AND emails.address = ? AND emails.user = users.id",
-               [ userID, email ],
-               function(err, rows) {
-                 if (err || rows.length != 1) {
-                   cb(err);
-                   return;
-                 }
-                 executeTransaction([
-                   [ "INSERT INTO keys (email, key, expires) VALUES(?,?,?)",
-                     [ rows[0].id, pubkey, ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000)) ]
-                   ]
-                 ], function (error) {
-                   if (error) cb(error);
-                   else cb();
-                 });
-               });
+exports.close = function(cb) {
+  driver.close(function(err) {
+    ready = false;
+    cb(err);
   });
-}
-
-/* takes an argument object including email, password hash, and pubkey. */
-exports.stageUser = function(obj) {
-  var secret = generateSecret();
-
-  // overwrite previously staged users
-  g_staged[secret] = {
-    type: "add_account",
-    email: obj.email,
-    pubkey: obj.pubkey,
-    pass: obj.hash
-  };
-
-  g_stagedEmails[obj.email] = secret;
-  return secret;
 };
 
-/* takes an argument object including email, pass, and pubkey. */
-// XXX: change to async
-exports.stageEmail = function(existing_email, new_email, pubkey) {
-  var secret = generateSecret();
-  // overwrite previously staged users
-  g_staged[secret] = {
-    type: "add_email",
-    existing_email: existing_email,
-    email: new_email,
-    pubkey: pubkey
-  };
-  g_stagedEmails[new_email] = secret;
-  return secret;
-};
-
-/* invoked when a user clicks on a verification URL in their email */
-exports.gotVerificationSecret = function(secret, cb) {
-  if (!g_staged.hasOwnProperty(secret)) return cb("unknown secret");
-
-  // simply move from staged over to the emails "database"
-  var o = g_staged[secret];
-  delete g_staged[secret];
-  delete g_stagedEmails[o.email];
-  if (o.type === 'add_account') {
-    exports.emailKnown(o.email, function(known) {
-      function createAccount() {
-        executeTransaction([
-          [ "INSERT INTO users (password) VALUES(?)", [ o.pass ] ] ,
-          [ "INSERT INTO emails (user, address) VALUES(last_insert_rowid(),?)", [ o.email ] ],
-          [ "INSERT INTO keys (email, key, expires) VALUES(last_insert_rowid(),?,?)",
-            [ o.pubkey, ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000)) ]
-          ]
-        ], function (error) {
-          if (error) cb(error);
-          else cb();
-        });
-      }
-
-      // if this email address is known and a user has completed a re-verification of this email
-      // address, remove the email from the old account that it was associated with, and then
-      // create a brand new account with only this email.
-      // NOTE: this might be sub-optimal, but it's a dead simple approach that mitigates many attacks
-      // and gives us reasonable behavior (without explicitly supporting) in the face of shared email
-      // addresses.
-      if (known) {
-        exports.removeEmail(o.email, o.email, function (err) {
-          if (err) cb(err);
-          else createAccount();
-        });
-      } else {
-        createAccount();
-      }
-    });
-  } else if (o.type === 'add_email') {
-    exports.emailKnown(o.email, function(known) {
-      function addIt() {
-        addEmailToAccount(o.existing_email, o.email, o.pubkey, cb);
-      }
-      if (known) {
-        exports.removeEmail(o.email, o.email, function (err) {
-          if (err) cb(err);
-          else addIt();
-        });
-      } else {
-        addIt();
-      }
-    });
-  } else {
-    cb("internal error");
-  }
-};
-
-// check authentication credentials for a given email address.  This will invoke the
-// users callback with the authentication (password/hash/whatever - the database layer
-// doesn't care).  callback will be passed undefined if email cannot be found
-exports.checkAuth = function(email, cb) {
-  db.execute("SELECT users.password FROM emails, users WHERE users.id = emails.user AND emails.address = ?",
-             [ email ],
-             function (error, rows) {
-               cb(rows.length !== 1 ? undefined : rows[0].password);
-             });
+// accepts a function that will be invoked once the database is ready for transactions.
+// this hook is important to pause the rest of application startup until async database
+// connection establishment is complete.
+exports.onReady = function(f) {
+  setTimeout(function() {
+    if (ready) f();
+    else waiting.push(f);
+  }, 0);
 };
 
-function emailHasPubkey(email, pubkey, cb) {
-  db.execute(
-    'SELECT keys.key FROM keys, emails WHERE emails.address = ? AND keys.email = emails.id AND keys.key = ?',
-    [ email, pubkey ],
-    function(err, rows) {
-      cb(rows.length === 1);
-    });
-}
-
-/* a high level operation that attempts to sync a client's view with that of the
- * server.  email is the identity of the authenticated channel with the user,
- * identities is a map of email -> pubkey.
- * We'll return an object that expresses three different types of information:
- * there are several things we need to express:
- * 1. emails that the client knows about but we do not
- * 2. emails that we know about and the client does not
- * 3. emails that we both know about but who need to be re-keyed
- * NOTE: it's not neccesary to differentiate between #2 and #3, as the client action
- *       is the same (regen keypair and tell us about it).
- */
-exports.getSyncResponse = function(email, identities, cb) {
-  var respBody = {
-    unknown_emails: [ ],
-    key_refresh: [ ]
+[
+  'emailKnown',
+  'isStaged',
+  'emailsBelongToSameAccount',
+  'addKeyToEmail',
+  'stageUser',
+  'stageEmail',
+  'gotVerificationSecret',
+  'checkAuth',
+  'getSyncResponse',
+  'pubkeysForEmail',
+  'removeEmail',
+  'cancelAccount'
+].forEach(function(fn) {
+  exports[fn] = function() {
+    checkReady();
+    driver[fn].apply(undefined, arguments);
   };
-
-  // get the user id associated with this account
-  emailToUserID(email, function(userID) {
-    if (userID === undefined) {
-      cb("no such email: " + email);
-      return;
-    }
-    db.execute(
-      'SELECT address FROM emails WHERE ? = user',
-      [ userID ],
-      function (err, rows) {
-        if (err) cb(err);
-        else {
-          var emails = [ ];
-          var keysToCheck = [ ];
-          for (var i = 0; i < rows.length; i++) emails.push(rows[i].address);
-
-          // #1
-          for (var e in identities) {
-            if (emails.indexOf(e) == -1) respBody.unknown_emails.push(e);
-            else keysToCheck.push(e);
-          }
-
-          // #2
-          for (var e in emails) {
-            e = emails[e];
-            if (!identities.hasOwnProperty(e)) respBody.key_refresh.push(e);
-            
-          }
-
-          // #3 -- yes, this is sub-optimal in terms of performance.  when we
-          // move away from public keys this will be unnec.
-          if (keysToCheck.length) {
-            var checked = 0;
-            keysToCheck.forEach(function(e) {
-              emailHasPubkey(e, identities[e], function(v) {
-                checked++;
-                if (!v) respBody.key_refresh.push(e);
-                if (checked === keysToCheck.length) {
-                  cb(undefined, respBody);
-                }
-              });
-            });
-          } else {
-            cb(undefined, respBody);
-          }
-        }
-      });
-  });
-};
-
-// get all public keys associated with an email address
-exports.pubkeysForEmail = function(identity, cb) {
-  db.execute(
-    'SELECT keys.key FROM keys, emails WHERE emails.address = ? AND keys.email = emails.id',
-    [ identity ],
-    function(err, rows) {
-      var keys = undefined;
-      if (!err && rows && rows.length) {
-        keys = [ ];
-        for (var i = 0; i < rows.length; i++) keys.push(rows[i].key);
-      }
-      cb(keys);
-    });
-};
-
-exports.removeEmail = function(authenticated_email, email, cb) {
-  // figure out the user, and remove Email only from addressed
-  // linked to the authenticated email address
-  emailToUserID(authenticated_email, function(user_id) {
-    executeTransaction([
-      [ "delete from emails where emails.address = ? and user = ?", [ email,user_id ] ] ,
-      [ "delete from keys where email in (select address from emails where emails.address = ? and user = ?)", [ email,user_id ] ],
-    ], function (error) {
-      if (error) cb(error);
-      else cb();
-    });
-  });
-};
-
-exports.cancelAccount = function(authenticated_email, cb) {
-  emailToUserID(authenticated_email, function(user_id) {
-    executeTransaction([
-      [ "delete from emails where user = ?", [ user_id ] ] ,
-      [ "delete from keys where email in (select address from emails where user = ?)", [ user_id ] ],
-      [ "delete from users where id = ?", [ user_id ] ],
-    ], function (error) {
-      if (error) cb(error);
-      else cb();
-    });
-  });
-};
+});
diff --git a/browserid/lib/db_json.js b/browserid/lib/db_json.js
new file mode 100644
index 0000000000000000000000000000000000000000..608476895e7c57c7bc68d7b73633854551461e60
--- /dev/null
+++ b/browserid/lib/db_json.js
@@ -0,0 +1,357 @@
+/* ***** 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 ***** */
+
+/* db_json is a json database driver.  It is designed for use in
+ * local development, is intended to be extremely easy to maintain,
+ * have minimal dependencies on 3rd party libraries, and we could
+ * care less if it performs well with more than 10 or so users.
+ */
+const
+path = require('path'),
+fs = require('fs'),
+secrets = require('./secrets'),
+jsel = require('JSONSelect');
+
+// a little alias for stringify
+const ESC = JSON.stringify;
+
+var VAR_DIR = path.join(path.dirname(__dirname), "var");
+
+var dbPath = path.join(VAR_DIR, "authdb.json");
+
+/* The JSON database. The structure is thus:
+ *  [
+ *    {
+ *      password: "somepass",
+ *      emails: [
+ *        {
+ *          address: "lloyd@hilaiel.com",
+ *          keys: [
+ *            {
+ *              key: "SOMESTRINGOFTEXT",
+ *              expires: 1231541615125
+ *            }
+ *          ]
+ *        }
+ *      ]
+ *    }
+ *  ]
+ */
+
+var db = [];
+var stagedEmails = { };
+var staged = { };
+
+function flush() {
+  fs.writeFileSync(dbPath, JSON.stringify(db));
+}
+
+// when should a key created right now expire?
+function getExpiryTime() {
+  return ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000));
+}
+
+exports.open = function(cfg, cb) {
+  if (cfg && cfg.path) dbPath = cfg.path;
+  try {
+    db = JSON.parse(fs.readFileSync(dbPath));
+  } catch(e) {
+  }
+
+  setTimeout(cb, 0);
+};
+
+exports.close = function(cb) {
+  flush();
+  setTimeout(cb, 0);
+};
+
+exports.emailKnown = function(email, cb) {
+  var m = jsel.match(".address:val(" + ESC(email) + ")", db);
+  setTimeout(function() { cb(m.length > 0) }, 0);
+};
+
+exports.isStaged = function(email, cb) {
+  if (cb) {
+    setTimeout(function() {
+      cb(stagedEmails.hasOwnProperty(email));
+    }, 0);
+  }
+};
+
+exports.emailsBelongToSameAccount = function(lhs, rhs, cb) {
+  emailToUserID(lhs, function(lhs_uid) {
+    emailToUserID(rhs, function(rhs_uid) {
+      cb(lhs_uid === rhs_uid);
+    }, function (error) {
+      cb(false);
+    });
+  }, function (error) {
+    cb(false);
+  });
+};
+
+function addEmailToAccount(existing_email, email, pubkey, cb) {
+  emailToUserID(existing_email, function(userID) {
+    if (userID == undefined) {
+      cb("no such email: " + existing_email, undefined);
+    } else {
+      db[userID].emails.push({
+        address: email,
+        keys: [
+          {
+            key: pubkey,
+            expires: getExpiryTime()
+          }
+        ]
+      });
+      flush();
+      cb();
+    }
+  });
+}
+
+exports.addKeyToEmail = function(existing_email, email, pubkey, cb) {
+  emailToUserID(existing_email, function(userID) {
+    if (userID == undefined) {
+      cb("no such email: " + existing_email, undefined);
+      return;
+    }
+
+    if (!(db[userID].emails)) {
+      db[userID].emails = [ ];
+    }
+
+    var m = jsel.match("object:has(.address:val(" + ESC(email) + ")) > .keys", db[userID].emails);
+
+    var kobj = {
+      key: pubkey,
+      expires: getExpiryTime()
+    };
+
+    if (m.length) {
+      m[0].push(kobj);
+    } else {
+      db[userID].emails.push({
+        address: email,
+        keys: [ kobj ]
+      });
+    }
+
+    flush();
+    if (cb) setTimeout(function() { cb(); }, 0);
+  });
+}
+
+exports.stageUser = function(obj, cb) {
+  var secret = secrets.generate(48);
+
+  // overwrite previously staged users
+  staged[secret] = {
+    type: "add_account",
+    email: obj.email,
+    pubkey: obj.pubkey,
+    pass: obj.hash
+  };
+
+  stagedEmails[obj.email] = secret;
+  setTimeout(function() { cb(secret); }, 0);
+};
+
+exports.stageEmail = function(existing_email, new_email, pubkey, cb) {
+  var secret = secrets.generate(48);
+  // overwrite previously staged users
+  staged[secret] = {
+    type: "add_email",
+    existing_email: existing_email,
+    email: new_email,
+    pubkey: pubkey
+  };
+  stagedEmails[new_email] = secret;
+  setTimeout(function() { cb(secret); }, 0);
+};
+
+exports.gotVerificationSecret = function(secret, cb) {
+  if (!staged.hasOwnProperty(secret)) return cb("unknown secret");
+
+  // simply move from staged over to the emails "database"
+  var o = staged[secret];
+  delete staged[secret];
+  delete stagedEmails[o.email];
+  if (o.type === 'add_account') {
+    exports.emailKnown(o.email, function(known) {
+      function createAccount() {
+        db.push({
+          password: o.pass,
+          emails: [
+            {
+              address: o.email,
+              keys: [ {
+                key: o.pubkey,
+                expires: getExpiryTime(),
+              } ]
+            }
+          ]
+        });
+        flush();
+        cb();
+      }
+
+      // if this email address is known and a user has completed a re-verification of this email
+      // address, remove the email from the old account that it was associated with, and then
+      // create a brand new account with only this email.
+      // NOTE: this might be sub-optimal, but it's a dead simple approach that mitigates many attacks
+      // and gives us reasonable behavior (without explicitly supporting) in the face of shared email
+      // addresses.
+
+      if (known) {
+        exports.removeEmail(o.email, o.email, function (err) {
+          if (err) cb(err);
+          else createAccount();
+        });
+      } else {
+        createAccount();
+      }
+    });
+  } else if (o.type === 'add_email') {
+    exports.emailKnown(o.email, function(known) {
+      function addIt() {
+        addEmailToAccount(o.existing_email, o.email, o.pubkey, cb);
+      }
+      if (known) {
+        exports.removeEmail(o.email, o.email, function (err) {
+          if (err) cb(err);
+          else addIt();
+        });
+      } else {
+        addIt();
+      }
+    });
+  } else {
+    cb("internal error");
+  }
+};
+
+exports.checkAuth = function(email, cb) {
+  var m = jsel.match(":root > object:has(.address:val(" + ESC(email) + ")) > .password", db);
+  if (m.length === 0) m = undefined;
+  else m = m[0];
+  setTimeout(function() { cb(m) }, 0);
+};
+
+function emailToUserID(email, cb) {
+  var id = undefined;
+
+  for (var i = 0; i < db.length; i++) {
+    if (jsel.match(".address:val(" + JSON.stringify(email) + ")", db[i]).length) {
+      id = i;
+      break;
+    }
+    if (id !== undefined) break;
+  }
+
+  setTimeout(function() { cb(id); }, 0);
+}
+
+exports.getSyncResponse = function(email, identities, cb) {
+  var respBody = {
+    unknown_emails: [ ],
+    key_refresh: [ ]
+  };
+
+  // get the user id associated with this account
+  emailToUserID(email, function(userID) {
+    if (userID === undefined) {
+      cb("no such email: " + email);
+      return;
+    }
+    var emails = jsel.match(".address", db[userID]);
+    var keysToCheck = [ ];
+
+    // #1 emails that the client knows about but we do not
+    for (var e in identities) {
+      if (emails.indexOf(e) == -1) respBody.unknown_emails.push(e);
+      else keysToCheck.push(e);
+    }
+
+    // #2 emails that we know about and the client does not
+    for (var e in emails) {
+      e = emails[e];
+      if (!identities.hasOwnProperty(e)) respBody.key_refresh.push(e);
+    }
+
+    // #3 emails that we both know about but who need to be re-keyed
+    if (keysToCheck.length) {
+      var checked = 0;
+      keysToCheck.forEach(function(e) {
+        if (!jsel.match(".key:val(" + ESC(identities[e]) + ")", db[userID]).length)
+          respBody.key_refresh.push(e);
+        checked++;
+        if (checked === keysToCheck.length) cb(undefined, respBody);
+      });
+    } else {
+      cb(undefined, respBody);
+    }
+  });
+};
+
+exports.pubkeysForEmail = function(identity, cb) {
+  var m = jsel.match(".emails object:has(.address:val(" + ESC(identity)+ ")) .key", db);
+  setTimeout(function() { cb(m); }, 0);
+};
+
+exports.removeEmail = function(authenticated_email, email, cb) {
+  var m = jsel.match(":root > object:has(.address:val("+ESC(authenticated_email)+")):has(.address:val("+ESC(email)+")) .emails", db);
+
+  if (m.length) {
+    var emails = m[0];
+    for (var i = 0; i < emails.length; i++) {
+      if (emails[i].address === email) {
+        emails.splice(i, 1);
+        break;
+      }
+    }
+  }
+
+  setTimeout(function() { cb(); }, 0);
+};
+
+exports.cancelAccount = function(authenticated_email, cb) {
+  emailToUserID(authenticated_email, function(user_id) {
+    db.splice(user_id, 1);
+    flush();
+    cb();
+  });
+};
diff --git a/browserid/lib/db_mysql.js b/browserid/lib/db_mysql.js
new file mode 100644
index 0000000000000000000000000000000000000000..1fb2fd8ce2e2f72a778d08b669ecc99bd09b73bd
--- /dev/null
+++ b/browserid/lib/db_mysql.js
@@ -0,0 +1,457 @@
+/* ***** 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 ***** */
+
+/* This is a mysql driver for the browserid server.  It maps the data
+ * storage requirements of browserid onto a relational schema.  This
+ * driver is intended to be fast and scalable.
+ */
+
+/*
+ * The Schema:
+ *
+ *    +--- user ------+       +--- email ----+        +--- pubkey -----+
+ *    |*int id        | <-\   |*int id       | <-\    |*int id         |
+ *    | string passwd |    \- |*int user     |    \-- |*int email      |
+ *    +---------------+       |*string address        | string pubkey  |
+ *                            +--------------+        | int expires    |
+ *                                                    +----------------+
+ *
+ *
+ *    +------ staged ----------+
+ *    |*string secret          |
+ *    | bool new_acct          |
+ *    | string existing        |
+ *    |*string email           |
+ *    | string pubkey          |
+ *    | string passwd          |
+ *    | timestamp ts           |
+ *    +------------------------+
+ */
+
+const
+mysql = require('mysql'),
+secrets = require('./secrets'),
+logger = require('../../libs/logging.js');
+
+var client = undefined;
+
+// may get defined at open() time causing a database to be dropped upon connection closing.
+var drop_on_close = undefined;
+
+const schemas = [
+  "CREATE TABLE IF NOT EXISTS user   ( id INTEGER AUTO_INCREMENT PRIMARY KEY, passwd VARCHAR(64) );",
+  "CREATE TABLE IF NOT EXISTS email  ( id INTEGER AUTO_INCREMENT PRIMARY KEY, user INTEGER, address VARCHAR(255) UNIQUE, INDEX(address) );",
+  "CREATE TABLE IF NOT EXISTS pubkey ( id INTEGER AUTO_INCREMENT PRIMARY KEY, email INTEGER, content TEXT, expiry DATETIME );",
+  "CREATE TABLE IF NOT EXISTS staged ( secret VARCHAR(48) PRIMARY KEY, new_acct BOOL, existing VARCHAR(255), email VARCHAR(255) UNIQUE, INDEX(email), pubkey TEXT, passwd VARCHAR(64), ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP);"
+];
+
+// log an unexpected database error
+function logUnexpectedError(detail) {
+  // first, get line number of callee
+  var where;
+  try { dne; } catch (e) { where = e.stack.split('\n')[2].trim(); };
+  // now log it!
+  logger.log('db', { type: "unexpected", message: "unexpected database failure", detail: detail, where: where });
+}
+
+// open & create the mysql database
+exports.open = function(cfg, cb) {
+  if (client) throw "database is already open!";
+  client = new mysql.Client();
+  // mysql config requires
+  const defParams = {
+    host: '127.0.0.1',
+    port: "3306",
+    user: 'test',
+    password: 'pass',
+    unit_test: false
+  };
+
+  Object.keys(defParams).forEach(function(param) {
+    client[param] = cfg[param] ? cfg[param] : defParams[param];
+  });
+
+  // let's figure out the database name
+  var database = cfg.database;
+  if (!database) database = "browserid";
+  if (cfg.unit_test) {
+    database += "_" + secrets.generate(8);
+    drop_on_close = database;
+  }
+
+  client.connect(function(error) {
+    if (error) {
+      logUnexpectedError(error);
+      cb(error);
+    } else {
+      // now create the databse
+      client.query("CREATE DATABASE IF NOT EXISTS " + database, function(err) {
+        if (err) {
+          logUnexpectedError(err);
+          cb(err);
+          return;
+        }
+        client.useDatabase(database, function(err) {
+          if (err) {
+            logUnexpectedError(err);
+            cb(err);
+            return;
+          }
+
+          // now create tables
+          function createNextTable(i) {
+            if (i < schemas.length) {
+              client.query(schemas[i], function(err) {
+                if (err) {
+                  logUnexpectedError(err);
+                  cb(err);
+                } else {
+                  createNextTable(i+1);
+                }
+              });
+            } else {
+              cb();
+            }
+          }
+          createNextTable(0);
+        });
+      });
+    }
+  });
+};
+
+exports.close = function(cb) {
+  function endConn() {
+    client.end(function(err) {
+      client = undefined;
+      if (err) logUnexpectedError(err);
+      if (cb) cb(err);
+    });
+  }
+  // when unit_test is specified at open time, we use a temporary database,
+  // and clean it up upon close.
+  if (drop_on_close) {
+    client.query("DROP DATABASE " + drop_on_close, function() {
+      endConn();
+    });
+  } else {
+    endConn();
+  }
+};
+
+exports.emailKnown = function(email, cb) {
+  client.query(
+    "SELECT COUNT(*) as N FROM email WHERE address = ?", [ email ],
+    function(err, rows) {
+      if (err) logUnexpectedError(err);
+      cb(rows && rows.length > 0 && rows[0].N > 0);
+    }
+  );
+}
+
+exports.isStaged = function(email, cb) {
+  client.query(
+    "SELECT COUNT(*) as N FROM staged WHERE email = ?", [ email ],
+    function(err, rows) {
+      if (err) logUnexpectedError(err);
+      cb(rows && rows.length > 0 && rows[0].N > 0);
+    }
+  );
+}
+
+exports.stageUser = function(obj, cb) {
+  var secret = secrets.generate(48);
+  // overwrite previously staged users
+  client.query('INSERT INTO staged (secret, new_acct, email, pubkey, passwd) VALUES(?,TRUE,?,?,?) ' +
+               'ON DUPLICATE KEY UPDATE secret=?, existing="", new_acct=TRUE, pubkey=?, passwd=?',
+               [ secret, obj.email, obj.pubkey, obj.hash, secret, obj.pubkey, obj.hash],
+               function(err) {
+                 if (err) {
+                   logUnexpectedError(err);
+                   cb(undefined, err);
+                 } else cb(secret);
+               });
+}
+
+exports.gotVerificationSecret = function(secret, cb) {
+  client.query(
+    "SELECT * FROM staged WHERE secret = ?", [ secret ],
+    function(err, rows) {
+      if (err) {
+        logUnexpectedError(err);
+        cb(err);
+      } else if (rows.length === 0) cb("unknown secret");
+      else {
+        var o = rows[0];
+
+        function addEmailAndPubkey(userID) {
+          client.query(
+            "INSERT INTO email(user, address) VALUES(?, ?)",
+            [ userID, o.email ],
+            function(err, info) {
+              if (err) { logUnexpectedError(err); cb(err); return; }
+              addKeyToEmailRecord(info.insertId, o.pubkey, cb);
+            });
+        }
+
+        // delete the record
+        client.query("DELETE LOW_PRIORITY FROM staged WHERE secret = ?", [ secret ]);
+
+        if (o.new_acct) {
+          // we're creating a new account, add appropriate entries into user, email, and pubkey.
+          client.query(
+            "INSERT INTO user(passwd) VALUES(?)",
+            [ o.passwd ],
+            function(err, info) {
+              if (err) { logUnexpectedError(err); cb(err); return; }
+              addEmailAndPubkey(info.insertId);
+            });
+        } else {
+          // we're adding an email address to an existing user account.  add appropriate entries into email and
+          // pubkey
+          client.query(
+            "SELECT user FROM email WHERE address = ?", [ o.existing ],
+            function(err, rows) {
+              if (err) { logUnexpectedError(err); cb(err); }
+              else if (rows.length === 0) cb("cannot find email address: " + o.existing);
+              else {
+                addEmailAndPubkey(rows[0].user);
+              }
+            });
+        }
+      }
+    }
+  );
+}
+
+exports.emailsBelongToSameAccount = function(lhs, rhs, cb) {
+  client.query(
+    'SELECT COUNT(*) AS n FROM email WHERE address = ? AND user = ( SELECT user FROM email WHERE address = ? );',
+    [ lhs, rhs ],
+    function (err, rows) {
+      if (err) cb(false);
+      else cb(rows.length === 1 && rows[0].n === 1);
+    });
+}
+
+function addKeyToEmailRecord(emailId, pubkey, cb) {
+  client.query(
+    // XXX: 2 weeks is wrong, but then so is keypairs.
+    "INSERT INTO pubkey(email, content, expiry) VALUES(?, ?, DATE_ADD(NOW(), INTERVAL 2 WEEK))",
+    [ emailId, pubkey ],
+    function(err, info) {
+      if (err) logUnexpectedError(err);
+      // smash null into undefined.
+      cb(err ? err : undefined);
+    });
+}
+
+exports.addKeyToEmail = function(existing_email, email, pubkey, cb) {
+  // this function will NOT add a new email address to a user record.  The only
+  // way that happens is when a verification secret is provided to us.  Limiting
+  // the code paths that result in us concluding that a user owns an email address
+  // is a Good Thing.
+  exports.emailsBelongToSameAccount(existing_email, email, function(ok) {
+    if (!ok) {
+      cb("authenticated user doesn't have permission to add a public key to " + email);
+      return;
+    }
+
+    // now we know that the user has permission to add a key.
+    client.query(
+      "SELECT id FROM email WHERE address = ?", [ email ],
+      function(err, rows) {
+        if (err) { logUnexpectedError(err); cb(err); }
+        else if (rows.length === 0) cb("cannot find email address: " + email);
+        else {
+          addKeyToEmailRecord(rows[0].id, pubkey, cb);
+        }
+      });
+  });
+}
+
+exports.stageEmail = function(existing_email, new_email, pubkey, cb) {
+  var secret = secrets.generate(48);
+  // overwrite previously staged users
+  client.query('INSERT INTO staged (secret, new_acct, existing, email, pubkey) VALUES(?,FALSE,?,?,?) ' +
+               'ON DUPLICATE KEY UPDATE secret=?, existing=?, new_acct=FALSE, pubkey=?, passwd=""',
+               [ secret, existing_email, new_email, pubkey, secret, existing_email, pubkey],
+               function(err) {
+                 if (err) {
+                   logUnexpectedError(err);
+                   cb(undefined, err);
+                 }
+                 else cb(secret);
+               });
+}
+
+exports.checkAuth = function(email, cb) {
+  client.query(
+    'SELECT passwd FROM user WHERE id = ( SELECT user FROM email WHERE address = ? )',
+    [ email ],
+    function (err, rows) {
+      if (err) logUnexpectedError(err);
+      cb((rows && rows.length == 1) ? rows[0].passwd : undefined);
+    });
+}
+
+function emailHasPubkey(email, pubkey, cb) {
+  client.query(
+    'SELECT pubkey.content FROM pubkey, email WHERE email.address = ? AND pubkey.email = email.id AND pubkey.content = ?',
+    [ email, pubkey ],
+    function(err, rows) {
+      if (err) logUnexpectedError(err);
+      cb(rows && rows.length === 1);
+    });
+}
+
+/* a high level operation that attempts to sync a client's view with that of the
+ * server.  email is the identity of the authenticated channel with the user,
+ * identities is a map of email -> pubkey.
+ * We'll return an object that expresses three different types of information:
+ * there are several things we need to express:
+ * 1. emails that the client knows about but we do not
+ * 2. emails that we know about and the client does not
+ * 3. emails that we both know about but who need to be re-keyed
+ * NOTE: it's not neccesary to differentiate between #2 and #3, as the client action
+ *       is the same (regen keypair and tell us about it).
+ */
+exports.getSyncResponse = function(email, identities, cb) {
+  var respBody = {
+    unknown_emails: [ ],
+    key_refresh: [ ]
+  };
+
+  client.query(
+    'SELECT address FROM email WHERE user = ( SELECT user FROM email WHERE address = ? ) ',
+      [ email ],
+      function (err, rows) {
+        if (err) cb(err);
+        else {
+          var emails = [ ];
+          var keysToCheck = [ ];
+          for (var i = 0; i < rows.length; i++) emails.push(rows[i].address);
+
+          // #1
+          for (var e in identities) {
+            if (emails.indexOf(e) == -1) respBody.unknown_emails.push(e);
+            else keysToCheck.push(e);
+          }
+
+          // #2
+          for (var e in emails) {
+            e = emails[e];
+            if (!identities.hasOwnProperty(e)) respBody.key_refresh.push(e);
+          }
+
+          // #3 -- yes, this is sub-optimal in terms of performance.  when we
+          // move away from public keys this will be unnec.
+          if (keysToCheck.length) {
+            var checked = 0;
+            keysToCheck.forEach(function(e) {
+              emailHasPubkey(e, identities[e], function(v) {
+                checked++;
+                if (!v) respBody.key_refresh.push(e);
+                if (checked === keysToCheck.length) {
+                  cb(undefined, respBody);
+                }
+              });
+            });
+          } else {
+            cb(undefined, respBody);
+          }
+        }
+      });
+};
+
+
+exports.pubkeysForEmail = function(email, cb) {
+  client.query(
+    'SELECT content FROM pubkey WHERE email = (SELECT id FROM email WHERE address = ?)',
+    [ email ],
+    function (err, rows) {
+      var ar = [ ];
+      if (!err) rows.forEach(function(r) { ar.push(r.content); });
+      else logUnexpectedError(err);
+      cb(ar);
+    });
+}
+
+exports.removeEmail = function(authenticated_email, email, cb) {
+  exports.emailsBelongToSameAccount(authenticated_email, email, function(ok) {
+    if (!ok) {
+      logger.log('security', authenticated_email + ' attempted to delete an email that doesn\'t belong to her: ' + email);
+      cb("authenticated user doesn't have permission to remove specified email " + email);
+      return;
+    }
+
+    client.query(
+      'DELETE FROM pubkey WHERE email = ( SELECT id FROM email WHERE address = ? )',
+      [ email ],
+      function (err, info) {
+        if (err) {
+          logUnexpectedError(err);
+          cb(err);
+        } else {
+          client.query(
+            'DELETE FROM email WHERE address = ?',
+            [ email ],
+            function(err, info) {
+              if (err) logUnexpectedError(err);
+              // smash null into undefined
+              cb(err ? err : undefined);
+            });
+        }
+      });
+  });
+}
+
+exports.cancelAccount = function(email, cb) {
+  function reportErr(err) { if (err) logUnexpectedError(err); }
+  client.query(
+    "SELECT user FROM email WHERE address = ?", [ email ],
+    function (err, rows) {
+      if (err) {
+        logUnexpectedError(err)
+        cb(err);
+        return
+      }
+      var uid = rows[0].user;
+      client.query("DELETE LOW_PRIORITY FROM pubkey WHERE email in ( SELECT id FROM email WHERE user = ? )", [ uid ], reportErr);
+      client.query("DELETE LOW_PRIORITY FROM email WHERE user = ?", [ uid ], reportErr);
+      client.query("DELETE LOW_PRIORITY FROM user WHERE id = ?", [ uid ], reportErr);
+      cb();
+    });
+}
diff --git a/browserid/lib/email.js b/browserid/lib/email.js
index 9a5f56475308def4b2b63f271dc1050aec384d01..47157e14ab16d7445485f76b0e88e4a5fffa26ed 100644
--- a/browserid/lib/email.js
+++ b/browserid/lib/email.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 const
 db = require('./db'),
 emailer = require('nodemailer'),
diff --git a/browserid/lib/httputils.js b/browserid/lib/httputils.js
index 96e8a30c906dd86ae08559daeff6373626d6bddd..f543d403fa1ca703d5f55d48cf92f401f08565da 100644
--- a/browserid/lib/httputils.js
+++ b/browserid/lib/httputils.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 // various little utilities to make crafting boilerplate responses
 // simple
 
diff --git a/browserid/lib/secrets.js b/browserid/lib/secrets.js
index 7aca40b986e7e82366fd531e22421f17f57b7dff..b57bf0ea3a0e57fcba6487ec13e118348bec6336 100644
--- a/browserid/lib/secrets.js
+++ b/browserid/lib/secrets.js
@@ -1,11 +1,46 @@
+/* ***** 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 ***** */
+
 const
 path = require('path'),
 fs = require('fs');
 
-function generateSecret() {
+exports.generate = function(chars) {
   var str = "";
   const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-  for (var i=0; i < 128; i++) {
+  for (var i=0; i < chars; i++) {
     str += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
   }
   return str;
@@ -19,7 +54,7 @@ exports.hydrateSecret = function(name, dir) {
   try{ secret = fs.readFileSync(p).toString(); } catch(e) {};
 
   if (secret === undefined) {
-    secret = generateSecret();
+    secret = exports.generate(128);
     fs.writeFileSync(p, secret);
   }
   return secret;
diff --git a/browserid/lib/webfinger.js b/browserid/lib/webfinger.js
index f79045b48db076db08c603613dffcece05119064..b7eb85b2d36154aa0cb4196c3efae460a15d96bf 100644
--- a/browserid/lib/webfinger.js
+++ b/browserid/lib/webfinger.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 const
 db = require('./db.js'),
 fs = require('fs'),
diff --git a/browserid/lib/wsapi.js b/browserid/lib/wsapi.js
index e7d757956aa8ddae61e51ab6f7ccc4230ce0d0be..da4c3a2ce11362de844102a64e9b2c3e7880feda 100644
--- a/browserid/lib/wsapi.js
+++ b/browserid/lib/wsapi.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 // a module which implements the authorities web server api.
 // it used to be that we stuffed every function in exports.
 // now we're using proper express function registration to deal
@@ -72,25 +107,26 @@ function setup(app) {
     try {
       // upon success, stage_user returns a secret (that'll get baked into a url
       // and given to the user), on failure it throws
-      var secret = db.stageUser(stageParams);
-
-      // store the email being registered in the session data
-      if (!req.session) req.session = {};
+      db.stageUser(stageParams, function(secret) {
+        // store the email being registered in the session data
+        if (!req.session) req.session = {};
 
-      // store inside the session the details of this pending verification
-      req.session.pendingVerification = {
-        email: stageParams.email,
-        hash: stageParams.hash // we must store both email and password to handle the case where
-        // a user re-creates an account - specifically, registration status
-        // must ensure the new credentials work to properly verify that
-        // the user has clicked throught the email link. note, this salted, bcrypted
-        // representation of a user's password will get thrust into an encrypted cookie
-        // served over an encrypted (SSL) session.  guten, yah.
-      };
+        // store inside the session the details of this pending verification
+        req.session.pendingVerification = {
+          email: stageParams.email,
+          hash: stageParams.hash // we must store both email and password to handle the case where
+          // a user re-creates an account - specifically, registration status
+          // must ensure the new credentials work to properly verify that
+          // the user has clicked throught the email link. note, this salted, bcrypted
+          // representation of a user's password will get thrust into an encrypted cookie
+          // served over an encrypted (SSL) session.  guten, yah.
+        };
 
-      resp.json(true);
-      email.sendVerificationEmail(stageParams.email, stageParams.site, secret);
+        resp.json(true);
 
+        // let's now kick out a verification email!
+        email.sendVerificationEmail(stageParams.email, stageParams.site, secret);
+      });
     } catch(e) {
       // we should differentiate tween' 400 and 500 here.
       httputils.badRequest(resp, e.toString());
@@ -128,7 +164,6 @@ function setup(app) {
     } else {
       // this is a pending registration, let's check if the creds stored on the
       // session are good yet.
-
       var v = req.session.pendingVerification;
       db.checkAuth(v.email, function(hash) {
         if (hash === v.hash) {
@@ -161,17 +196,17 @@ function setup(app) {
 
   app.post('/wsapi/add_email', checkAuthed, checkParams(["email", "pubkey", "site"]), function (req, resp) {
     try {
-      // upon success, stage_user returns a secret (that'll get baked into a url
-      // and given to the user), on failure it throws
-      var secret = db.stageEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey);
+      // on failure stageEmail may throw
+      db.stageEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey, function(secret) {
 
-      // store the email being added in session data
-      req.session.pendingAddition = req.body.email;
+        // store the email being added in session data
+        req.session.pendingAddition = req.body.email;
 
-      resp.json(true);
+        resp.json(true);
 
-      // let's now kick out a verification email!
-      email.sendVerificationEmail(req.body.email, req.body.site, secret);
+        // let's now kick out a verification email!
+        email.sendVerificationEmail(req.body.email, req.body.site, secret);
+      });
     } catch(e) {
       // we should differentiate tween' 400 and 500 here.
       httputils.badRequest(resp, e.toString());
diff --git a/browserid/run.js b/browserid/run.js
index ed8b03dbe38bb9cde7f09640deb67fa27ce96a6c..e83192d2fdf67955d34cf8ddd0d03f0f9c01df39 100755
--- a/browserid/run.js
+++ b/browserid/run.js
@@ -1,5 +1,40 @@
 #!/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 ***** */
+
 var  path = require("path"),
 fs = require("fs"),
 express = require("express");
diff --git a/browserid/static/css/style.css b/browserid/static/css/style.css
index 0a94ef5ea99e6ee4ac4171169a60b3bd5828cf98..9944eb2906632e0782d519c3488097c03f9e13f4 100644
--- a/browserid/static/css/style.css
+++ b/browserid/static/css/style.css
@@ -181,6 +181,12 @@ footer .copyright {
     padding: 0;
 }
 
+#steps a {
+    color: #666;
+    text-decoration: none;
+    border-bottom: 1px dotted #666;
+}
+
 .step  {
     margin: 1em 0 2em 0;
     padding: 0 0 0 50px;
@@ -222,7 +228,7 @@ footer .copyright {
     font-size: 1.2em;
 }
 
- pre code {
+pre code {
     padding: 10px 15px 10px 15px;
     margin: .75em;
     -webkit-border-radius: 10px;
diff --git a/browserid/static/dialog/controllers/addemail_controller.js b/browserid/static/dialog/controllers/addemail_controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..035b5faff541dce04931d73caf78ad6006e1f9ad
--- /dev/null
+++ b/browserid/static/dialog/controllers/addemail_controller.js
@@ -0,0 +1,83 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: 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 ***** */
+(function() {
+  "use strict";
+
+  PageController.extend("addemail", {}, {
+    init: function(options) {
+      this._super({
+        bodyTemplate: "addemail.ejs",
+        bodyVars: {
+          sitename: BrowserIDNetwork.origin,
+          identities: getEmails()
+        },
+        footerTemplate: "bottom-addemail.ejs",
+        footerVars: {}
+      });
+      // select the first option
+      this.find('input:first').attr('checked', true);
+    },
+
+    validate: function() {
+      var email = $("#email_input").val();
+      return /^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/.test(email);
+    },
+
+    submit: function() {
+      // add the actual email
+      // now we need to actually try to stage the creation of this account.
+      var email = $("#email_input").val();
+      var keypair = CryptoStubs.genKeyPair();
+
+      // kick the user to waiting/status page while we talk to the server.
+      this.doWait(BrowserIDWait.addEmail);
+
+      var self = this;
+      BrowserIDNetwork.addEmail(email, keypair, function() {
+          // email successfully staged, now wait for email confirmation
+          self.close("addemail:complete", {
+            email: email,
+            keypair: keypair
+          });
+        },
+        function() {
+          self.runErrorDialog(BrowserIDErrors.addEmail);
+        });
+    }
+  });
+
+}());
diff --git a/browserid/static/dialog/controllers/authenticate_controller.js b/browserid/static/dialog/controllers/authenticate_controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..62fdd0b04e303604d5cae7bdbe1364dc223715b2
--- /dev/null
+++ b/browserid/static/dialog/controllers/authenticate_controller.js
@@ -0,0 +1,86 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: 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 ***** */
+(function() {
+  "use strict";
+
+  PageController.extend("Authenticate", {}, {
+    init: function() {
+      this._super({
+        bodyTemplate: "authenticate.ejs",
+        bodyVars: {
+          sitename: BrowserIDNetwork.origin
+        },
+        footerTemplate: "bottom-signin.ejs",
+        footerVars: {}
+      });
+    },
+
+    "#forgotpassword click": function(event) {
+      this.close("authenticate:forgotpassword");
+    },
+            
+    "#create click": function(event) {
+      this.close("authenticate:createuser");
+    },
+
+    validate: function() {
+      var email = $("#email_input").val();
+      var pass = $("#password_input").val();
+
+      return true;
+    },
+
+    submit: function() {
+      var email = $("#email_input").val();
+      var pass = $("#password_input").val();
+
+      var self = this;
+      BrowserIDNetwork.authenticate(email, pass, function(authenticated) {
+        if (authenticated) {
+          self.doWait(BrowserIDWait.authentication);
+          self.close("authenticate:authenticated");
+        } else {
+          self.find("#nosuchaccount").hide().fadeIn(400);
+        }
+      }, function(resp) {
+        self.runErrorDialog(BrowserIDErrors.authentication);
+        self.close("cancel");
+      });
+    }
+  });
+
+}());
diff --git a/browserid/static/dialog/controllers/checkregistration_controller.js b/browserid/static/dialog/controllers/checkregistration_controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..1905523a8bf7da2bf4bcb08d0bf3ce7cb51918f5
--- /dev/null
+++ b/browserid/static/dialog/controllers/checkregistration_controller.js
@@ -0,0 +1,117 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: 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 ***** */
+(function() {
+  "use strict";
+
+  PageController.extend("Checkregistration", {}, {
+    init: function(options) {
+      this._super({
+        bodyTemplate: "confirmemail.ejs",
+        bodyVars: {
+          email: options.email
+        },
+        footerTemplate: "bottom-confirmemail.ejs",
+        footerVars: {}
+      });
+      $('#continue_button').addClass('disabled');
+      this.setupRegCheck();
+    },
+
+    close: function() {
+      if(this.pollTimeout) {
+        clearTimeout(this.pollTimeout);
+        this.pollTimeout = null;
+      }
+
+      this._super.apply(this, arguments);
+    },
+
+    setupRegCheck: function() {
+      // now poll every 3s waiting for the user to complete confirmation
+      var self=this;
+      function setupRegCheck() {
+        self.pollTimeout = setTimeout(function() {
+          BrowserIDNetwork.checkRegistration(function(status) {
+            // registration status checks the status of the last initiated registration,
+            // it's possible return values are:
+            //   'complete' - registration has been completed
+            //   'pending'  - a registration is in progress
+            //   'noRegistration' - no registration is in progress
+            if (status === 'complete') {
+              // and tell the user that everything is really quite awesome.
+              self.find("#waiting_confirmation").hide();
+              self.find("#resendit_action").hide();
+              self.find("#confirmed_notice").show();
+
+              // enable button
+              $('#continue_button').removeClass('disabled');
+
+              self.publish("checkregistration:confirmed");
+            } else if (status === 'pending') {
+              // try again, what else can we do?
+              self.setupRegCheck();
+            } else {
+              self.runErrorDialog(BrowserIDErrors.registration);
+            }
+          },
+          function(jqXHR, textStatus, errorThrown) {
+              self.runErrorDialog(BrowserIDErrors.registration);
+          });
+        }, 3000);
+      }
+      
+      // setup the timeout
+      setupRegCheck();
+
+    },
+
+    validate: function() {
+      var valid = !$("#continue_button").hasClass("disabled");
+      return valid;
+    },
+
+    submit: function() {
+      var self=this;
+      self.publish("checkregistration:complete");
+      self._super();      
+    }
+
+  });
+
+
+
+}());
diff --git a/browserid/static/dialog/controllers/chooseemail_controller.js b/browserid/static/dialog/controllers/chooseemail_controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..873ffcee1ef5c5d967d07b04e23ac8b14d48e726
--- /dev/null
+++ b/browserid/static/dialog/controllers/chooseemail_controller.js
@@ -0,0 +1,71 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: 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 ***** */
+(function() {
+  "use strict";
+
+  PageController.extend("Chooseemail", {}, {
+    init: function(options) {
+      this._super({
+        bodyTemplate: "signin.ejs",
+        bodyVars: {
+          sitename: BrowserIDNetwork.origin,
+          identities: getEmails()
+        },
+        footerTemplate: "bottom-pickemail.ejs",
+        footerVars: {}
+      });
+      // select the first option
+      this.find('input:first').attr('checked', true);
+    },
+
+    submit: function() {
+      var email = $("#identities input:checked").val();
+      this.close("chooseemail:complete", {
+        email: email
+      });
+    },
+
+    "#addemail click": function(event) {
+      this.close("chooseemail:addemail");
+    },
+
+    "#notme click": function(event) {
+      this.close("chooseemail:notme");
+    }
+  });
+
+}());
diff --git a/browserid/static/dialog/controllers/createaccount_controller.js b/browserid/static/dialog/controllers/createaccount_controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..50ba5cdb0fae1d0970bfc8268b273cc0ec714388
--- /dev/null
+++ b/browserid/static/dialog/controllers/createaccount_controller.js
@@ -0,0 +1,169 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: 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 ***** */
+(function() {
+  "use strict";
+
+  PageController.extend("Createaccount", {}, {
+    init: function() {
+      this._super({
+        bodyTemplate: "create.ejs",
+        bodyVars: {},
+        footerTemplate: "bottom-continue.ejs",
+        footerVars: {}
+      });
+
+      $('#create_continue').addClass('disabled');
+
+      // watch input dialogs
+      this.setupWatchers();      
+    },
+
+    validate: function() {
+      if ($('#create_continue').hasClass('disabled'))
+        return false;
+      return true;
+    },
+
+    submit: function() {
+      // now we need to actually try to stage the creation of this account.
+      var email = this.find("#email_input").val();
+      var pass = this.find("#password_input").val();
+      var keypair = CryptoStubs.genKeyPair();
+
+      this.doWait(BrowserIDWait.createAccount);
+
+      var self = this;
+      BrowserIDNetwork.stageUser(email, pass, keypair, function() {
+          self.close("createaccount:created", {
+            email: email,
+            keypair: keypair
+          });
+        },
+        function() {
+          self.runErrorDialog(BrowserIDErrors.createAccount);
+        }
+      );
+
+      
+    },
+
+    setupWatchers: function() {
+      var checkedEmails = {};
+      var emailCheckState = null;
+      var nextEmailToCheck = null;
+      var self = this;
+
+      function checkInput() {
+        // check the email address
+        var email = self.find("#email_input").val();
+
+        if (typeof email === 'string' && email.length) {
+          var valid = checkedEmails[email];
+          if (typeof valid === 'string') {
+            // oh noes.  we tried to check this email, but it failed.  let's just not tell the
+            // user anything, cause this is a non-critical issue
+          } else if (typeof valid === 'boolean') {
+            if (valid) {
+              self.find("#email_input_note").show();
+              self.find("#emailinuse_message").hide();
+            } else {
+              $("#emailinuse_message").fadeIn(300);
+              self.find("#email_input_note").hide();
+              $("#in_use_email").text(email);
+            }
+          } else {
+            // this is an email that needs to be checked!
+            if (emailCheckState !== 'querying') {
+              if (emailCheckState) {
+                window.clearTimeout(emailCheckState);
+              }
+              emailCheckState = setTimeout(function() {
+                emailCheckState = 'querying';
+                var checkingNow = nextEmailToCheck;
+                // bounce off the server and enter the 'querying' state
+                BrowserIDNetwork.haveEmail(checkingNow, function(success) {
+                      checkedEmails[checkingNow] = success;
+                      emailCheckState = undefined;
+                      checkInput();
+                    },function() {
+                      // some kind of error was encountered.  This is non-critical, we'll simply ignore it
+                      // and mark this email check as failed.
+                      checkedEmails[checkingNow] = "server failed";
+                      emailCheckState = undefined;
+                      checkInput();
+                    }
+                  );
+                }, 700);
+            } else {
+              // FIXME: not sure when this comes up, not refactored
+              // $("#create_dialog div.note:eq(0)").html($('<span class="warning"/>').text("Checking address"));
+            }
+          }
+          nextEmailToCheck = email;
+        }
+      
+        // next let's check the password entry
+        var pass = $("#password_input").val();
+        var match = pass === $("#password_verify_input").val();
+        self.find('.passwordnote').hide();
+        $('#create_continue').addClass('disabled');
+        if (!match) {
+          self.find('#passwords_different').show();
+        } else {
+          if (!pass) {
+            self.find('#enter_a_password').show();
+          } else if (pass.length < 5) {
+            self.find('#password_too_short').show();
+          } else {
+            self.find('#password_ok').show();
+            $('#create_continue').removeClass('disabled');
+          }
+        }
+      }
+
+      self.find("input").unbind('keyup').bind('keyup', checkInput);
+      // do a check at load time, in case the user is using the back button (enables the continue button!)
+      checkInput();
+    },
+
+    "#suggest_signin click": function(event) {
+      this.close("createaccount:signin");
+    }
+
+  });
+
+}());
diff --git a/browserid/static/dialog/controllers/dialog_controller.js b/browserid/static/dialog/controllers/dialog_controller.js
index 39d9ffc2b72b4d8f56fc55bc3b4e01d9c1d47235..1f23ac46a4ef9c0a004cdf4e2581087b56299686 100644
--- a/browserid/static/dialog/controllers/dialog_controller.js
+++ b/browserid/static/dialog/controllers/dialog_controller.js
@@ -1,16 +1,49 @@
-/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */                                             
-/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, runErrorDialog: true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true */
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: true, PageController: true, OpenAjax: 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 ***** */
 
 //
 // a JMVC controller for the browserid dialog
 //
 
-$.Controller("Dialog", {}, {
-    init: function(el) {
-      // FIXME: there's a small chance the CSRF token doesn't come back in time.
-      this.csrf = null;
-      this._getCSRF();
+(function() {
+"use strict";
 
+PageController.extend("Dialog", {}, {
+    init: function(el) {
       var html = $.View("//dialog/views/body.ejs", {});
       this.element.html(html);
       this.element.show();
@@ -19,218 +52,79 @@ $.Controller("Dialog", {}, {
       this.onsuccess = null;
       this.onerror = null;
       var chan = setupChannel(this);
+      this.stateMachine();
     },
       
-    _getCSRF: function(cb) {
-      // go get CSRF token
-      var self = this;
-      $.get('/csrf', {}, function(result) {
-          self.csrf = result;
-          if (cb)
-            cb();
-        });
-    },
-
-    setupEnterKey: function() {
-      $("input").keyup(function(e) {
-          if(e.which == 13) {
-            $('.submit').click();
-            e.preventDefault();
-          }
-        });
-    },
-
-    renderTemplates: function(body, body_vars, footer, footer_vars) {
-      if (body) {
-        var bodyHtml = $.View("//dialog/views/" + body, body_vars);
-        $('#dialog').html(bodyHtml).hide().fadeIn(300, function() {
-          $('#dialog input').eq(0).focus(); 
-        });
-      }
-
-      if (footer) {
-        var footerHtml = $.View("//dialog/views/" + footer, footer_vars);
-        $('#bottom-bar').html(footerHtml);
-      }
-      this.setupEnterKey();
-    },
-    
-    "#suggest_signin click": function(event) {
-      this.doAuthenticate();
-    },
-      
-    "#signin click": function(event) {
-      var email = $("#email_input").val();
-      var pass = $("#password_input").val();
-
-      var self = this;
-
-      $.ajax({
-          type: "POST",
-          url: '/wsapi/authenticate_user',
-          data: {
-            email: email,
-            pass: pass,
-            csrf: this.csrf
-          },
-            success: function(status, textStatus, jqXHR) {
-            var authenticated = JSON.parse(status);
-            if (!authenticated) {
-              self.find("#nosuchaccount").hide().fadeIn(400);
-            } else {
-              self.doWait("Finishing Sign In...",
-                          "In just a moment you'll be signed into BrowserID.");
-              
-              self.syncIdentities();
-            }
-          },
-            error: function() {
-            runErrorDialog(
-                           "serverError",
-                           "Error Authenticating!",
-                           "There was a technical problem while trying to log you in.  Yucky!");
-          }
-        });
-    },
+    getVerifiedEmail: function(origin_url, onsuccess, onerror) {
+      this.onsuccess = onsuccess;
+      this.onerror = onerror;
 
-    "#pickemail click": function(event) {
-      var email = $("#identities input:checked").val();
+      BrowserIDNetwork.setOrigin(origin_url);
 
-      // yay!  now we need to produce an assertion.
-      var storedID = getEmails()[email];
+      this.doStart();
 
-      var privkey = storedID.priv;
-      var issuer = storedID.issuer;
-      var audience = this.remoteOrigin.replace(/^(http|https):\/\//, '');
-      var assertion = CryptoStubs.createAssertion(audience, email, privkey, issuer);
-      // Clear onerror before the call to onsuccess - the code to onsuccess 
-      // calls window.close, which would trigger the onerror callback if we 
-      // tried this afterwards.
-      this.onerror = null;
-      this.onsuccess(assertion);
+      var self=this;
+      $(window).bind("unload", function() {
+        self.doCancel();
+      });
     },
 
-    "#addemail click": function(event) {
-      this.doNewEmail();
-    },
 
-    "#addemail_button click": function(event) {
-      // add the actual email
-      // now we need to actually try to stage the creation of this account.
-      var email = $("#email_input").val();
-      var keypair = CryptoStubs.genKeyPair();
+    stateMachine: function() {
+      var self=this, hub = OpenAjax.hub, el = this.element;
 
-      var self = this;
+      hub.subscribe("createaccount:created", function(msg, info) {
+        self.doConfirmEmail(info.email, info.keypair);
+      });
 
-      // kick the user to waiting/status page while we talk to the server.
-      this.doWait(
-        "One Moment Please...",
-        "We're adding this email to your account, this should only take a couple seconds."
-      );
+      hub.subscribe("createaccount:signin", function() {
+        self.doAuthenticate();
+      });
 
-      $.ajax({
-        type: 'POST',
-        url: '/wsapi/add_email',
-        data: {
-            email: email,
-            pubkey: keypair.pub,
-            site: this.remoteOrigin.replace(/^(http|https):\/\//, ''),
-            csrf: this.csrf
-         },
-        success: function() {
-          // email successfully staged, now wait for email confirmation
-          self.doConfirmEmail(email, keypair);
-        },
-        error: function() {
-          runErrorDialog(
-            "serverError",
-            "Error Adding Address!",
-            "There was a technical problem while trying to add this email to your account.  Yucky.");
-        }
+      hub.subscribe("authenticate:authenticated", function() {
+        self.syncIdentities();
       });
-    },
 
-    "#notme click": function(event) {
-      clearEmails();
-      var self = this;
-      $.post("/wsapi/logout", {csrf: this.csrf}, function() {
-          self._getCSRF(function() {
-              self.doAuthenticate();
-            });
+      hub.subscribe("authenticate:createuser", function() {
+        self.doCreate();
       });
-    },
-      
-    "#create click": function(event) {
-      this.doCreate();
-    },
 
-    "#forgotpassword click": function(event) {
-      this.doForgotPassword();
-    },
-            
-    "#cancel click": function(event) {
-      this.onerror("canceled");
-    },
+      hub.subscribe("authenticate:forgotpassword", function() {
+        self.doForgotPassword();
+      });
 
-    "#back click": function(event) {
-      this.doStart();
-    },
+      hub.subscribe("checkregistration:confirmed", function() {
+        self.doRegistrationConfirmed();
+      });
 
-    "#continue_button click": function(event) {
-      if (!$("#continue_button").hasClass('disabled')) {
-        this.doSignIn();
-      }
-    },
+      hub.subscribe("checkregistration:complete", function() {
+        self.doSignIn();
+      });
 
-    "#create_continue click": function(event) {
-      if ($('#create_continue').hasClass('disabled'))
-        return;
+      hub.subscribe("chooseemail:complete", function(msg, info) {
+        self.doEmailSelected(info.email);
+      });
 
-      // now we need to actually try to stage the creation of this account.
-      var email = this.find("#email_input").val();
-      var pass = this.find("#password_input").val();
-      var keypair = CryptoStubs.genKeyPair();
+      hub.subscribe("chooseemail:addemail", function() {
+        self.doAddEmail();
+      });
 
-      this.doWait(
-        "One Moment Please...",
-        "We're creating your account, this should only take a couple seconds");
+      hub.subscribe("chooseemail:notme", function() {
+        self.doNotMe();
+      });
 
-      var self = this;
+      hub.subscribe("addemail:complete", function(msg, info) {
+        self.doConfirmEmail(info.email, info.keypair);
+      });
 
-      $.ajax({
-          type: "post",
-          url: '/wsapi/stage_user',
-          data: {email: email,
-              pass: pass,
-              pubkey : keypair.pub,
-              site : this.remoteOrigin.replace(/^(http|https):\/\//, ''),
-              csrf : self.csrf},
-          success: function() {
-            // account successfully staged, now wait for email confirmation
-            self.doConfirmEmail(email, keypair);
-          },
-            error: function() {
-            runErrorDialog(
-                           "serverError",
-                           "Error Creating Account!",
-                           "There was a technical problem while trying to create your account.  Yucky.");
-          }
-        });
-    },
+      hub.subscribe("start", function() {
+        self.doStart();
+      });
 
-    getVerifiedEmail: function(origin_url, onsuccess, onerror) {
-      this.onsuccess = onsuccess;
-      this.onerror = onerror;
-      this.remoteOrigin = origin_url.replace(/^.*:\/\//, "");
-      this.doStart();
-      var me=this;
-      $(window).bind("unload", function() {
-        // In the success case, me.onerror will have been cleared before unload 
-        // is triggered.
-        if (me.onerror) {
-          me.onerror("canceled");
-        }
+      hub.subscribe("cancel", function() {
+        self.doCancel();
       });
+
     },
 
     doStart: function() {
@@ -243,212 +137,73 @@ $.Controller("Dialog", {}, {
         this.doSignIn();
       } else {
         // do we even need to authenticate?
-        this.checkAuth(function() {
-            self.syncIdentities();
-          }, function() {
-            self.doAuthenticate();
-          });
+        this.doCheckAuth();
       }
     },
       
-    doSignIn: function() {
-      this.renderTemplates("signin.ejs", {sitename: this.remoteOrigin, identities: getEmails()},
-                           "bottom-pickemail.ejs", {});
+    doCancel: function() {
+      var self=this;
+      if(self.onsuccess) {
+        self.onsuccess(null);
+      }
+    },
 
-      // select the first option
-      this.find('input:first').attr('checked', true);
+    doSignIn: function() {
+      this.element.chooseemail();
     },
 
     doAuthenticate: function() {
-      this.renderTemplates("authenticate.ejs", {sitename: this.remoteOrigin},
-                           "bottom-signin.ejs", {});
-
+      this.element.authenticate();
     },
       
     doCreate: function() {
-      this.renderTemplates("create.ejs", {},
-                           "bottom-continue.ejs", {});
-
-      $('#create_continue').addClass('disabled');
-
-      var checkedEmails = {};
-      var emailCheckState = null;
-      var nextEmailToCheck = null;
-      var self = this;
-
-      function checkInput() {
-        // check the email address
-        var email = self.find("#email_input").val();
-        if (typeof email === 'string' && email.length) {
-          var valid = checkedEmails[email];
-          if (typeof valid === 'string') {
-            // oh noes.  we tried to check this email, but it failed.  let's just not tell the
-            // user anything, cause this is a non-critical issue
-          } else if (typeof valid === 'boolean') {
-            if (valid) {
-              self.find("#email_input_note").show();
-              self.find("#emailinuse_message").hide();
-            } else {
-              $("#emailinuse_message").fadeIn(300);
-              self.find("#email_input_note").hide();
-              $("#in_use_email").text(email);
-            }
-          } else {
-            // this is an email that needs to be checked!
-            if (emailCheckState !== 'querying') {
-              if (emailCheckState) window.clearTimeout(emailCheckState);
-              emailCheckState = setTimeout(function() {
-                  emailCheckState = 'querying';
-                  var checkingNow = nextEmailToCheck;
-                  // bounce off the server and enter the 'querying' state
-                  $.ajax({
-                      url: '/wsapi/have_email?email=' + encodeURIComponent(checkingNow),
-                        success: function(data, textStatus, jqXHR) {
-                        checkedEmails[checkingNow] = !JSON.parse(data);
-                        emailCheckState = undefined;
-                        checkInput();
-                      }, error: function(jqXHR, textStatus, errorThrown) {
-                        // some kind of error was encountered.  This is non-critical, we'll simply ignore it
-                        // and mark this email check as failed.
-                        checkedEmails[checkingNow] = "server failed";
-                        emailCheckState = undefined;
-                        checkInput();
-                      }
-                    });
-                }, 700);
-            } else {
-              // FIXME: not sure when this comes up, not refactored
-              // $("#create_dialog div.note:eq(0)").html($('<span class="warning"/>').text("Checking address"));
-            }
-          }
-          nextEmailToCheck = email;
-          //$("#submit").addClass("disabled");
-        }
-      
-        // next let's check the password entry
-        var pass = $("#password_input").val();
-        var match = pass === $("#password_verify_input").val();
-        self.find('.passwordnote').hide();
-        $('#create_continue').addClass('disabled');
-        if (!match) {
-          self.find('#passwords_different').show();
-        } else {
-          if (!pass) {
-            self.find('#enter_a_password').show();
-          } else if (pass.length < 5) {
-            self.find('#password_too_short').show();
-          } else {
-            self.find('#password_ok').show();
-            $('#create_continue').removeClass('disabled');
-          }
-        }
-      }
-      
-      // watch input dialogs
-      self.find("input").unbind('keyup').bind('keyup', checkInput);
-      this.setupEnterKey();
-            
-      // do a check at load time, in case the user is using the back button (enables the continue button!)
-      checkInput();
+      this.element.createaccount();
     },
       
     doForgotPassword: function() {
-      this.renderTemplates("forgotpassword.ejs", {},
-                           "bottom-continue.ejs", {});
-
-      $('#create_continue').addClass('disabled');
-
-      var self=this;
-      function checkInput() {
-        var pass = $("#password_input").val();
-        var match = pass === $("#password_verify_input").val();
-        self.find('.passwordnote').hide();
-        $('#create_continue').addClass('disabled');
-        if (!match) {
-          self.find('#passwords_different').show();
-        } else {
-          if (!pass) {
-            self.find('#enter_a_password').show();
-          } else if (pass.length < 5) {
-            self.find('#password_too_short').show();
-          } else {
-            self.find('#password_ok').show();
-            $('#create_continue').removeClass('disabled');
-          }
-        }
-      }
-      
-      // watch input dialogs
-      self.find("input").unbind('keyup').bind('keyup', checkInput);
-      this.setupEnterKey();
-      
-      // do a check at load time, in case the user is using the back button (enables the continue button!)
-      checkInput();
+      this.element.forgotpassword();
     },
 
-    doWait: function(title, message) {
-      this.renderTemplates("wait.ejs", {title: title, message: message});
+    doAddEmail: function() {
+      this.element.addemail();
     },
 
-    doNewEmail: function() {
-      this.renderTemplates("addemail.ejs", {},
-                           "bottom-addemail.ejs", {});
+    doConfirmEmail: function(email, keypair) {
+      this.confirmEmail = email;
+      this.confirmKeypair = keypair;
 
-      this.setupEnterKey();
+      this.element.checkregistration({email: email});
     },
 
-    doConfirmEmail: function(email, keypair) {
-      this.renderTemplates("confirmemail.ejs", {email:email},
-                           "bottom-confirmemail.ejs", {});
+    doRegistrationConfirmed: function() {
+        var self = this;
+        // this is a secondary registration from browserid.org, persist
+        // email, keypair, and that fact
+        self.persistAddressAndKeyPair(self.confirmEmail, 
+          self.confirmKeypair, "browserid.org:443");
+        self.syncIdentities();
 
-      $('#continue_button').addClass('disabled');
+    },
 
-      var self = this;
+    doEmailSelected: function(email) {
+      var self=this,
+          // yay!  now we need to produce an assertion.
+          storedID = getEmails()[email],
+          privkey = storedID.priv,
+          issuer = storedID.issuer,
+          audience = BrowserIDNetwork.origin,
+          assertion = CryptoStubs.createAssertion(audience, email, privkey, issuer);
 
-      // now poll every 3s waiting for the user to complete confirmation
-      function setupRegCheck() {
-        return setTimeout(function() {
-            $.ajax({
-                url: '/wsapi/registration_status',
-                  success: function(status, textStatus, jqXHR) {
-                  // registration status checks the status of the last initiated registration,
-                  // it's possible return values are:
-                  //   'complete' - registration has been completed
-                  //   'pending'  - a registration is in progress
-                  //   'noRegistration' - no registration is in progress
-                  if (status === 'complete') {
-                    // this is a secondary registration from browserid.org, persist
-                    // email, keypair, and that fact
-                    self.persistAddressAndKeyPair(email, keypair, "browserid.org:443");
-                    
-                    // and tell the user that everything is really quite awesome.
-                    self.find("#waiting_confirmation").hide();
-                    self.find("#resendit_action").hide();
-                    self.find("#confirmed_notice").show();
-
-                    // enable button
-                    $('#continue_button').removeClass('disabled');
-
-                  } else if (status === 'pending') {
-                    // try again, what else can we do?
-                    pollTimeout = setupRegCheck();
-                  } else {
-                    runErrorDialog("serverError",
-                                   "Registration Failed",
-                                   "An error was encountered and the sign up cannot be completed, please try again later.");
-                  }
-                },
-                  error: function(jqXHR, textStatus, errorThrown) {
-                  runErrorDialog("serverError", "Registration Failed", jqXHR.responseText);
-                }
-              });
-          }, 3000);
-      }
-      
-      // setup the timeout
-      this.pollTimeout = setupRegCheck();
+      // Clear onerror before the call to onsuccess - the code to onsuccess 
+      // calls window.close, which would trigger the onerror callback if we 
+      // tried this afterwards.
+      self.onerror = null;
+      self.onsuccess(assertion);
+    },
 
-      // FIXME cancel this timeout appropriately on cancel
+    doNotMe: function() {
+      clearEmails();
+      BrowserIDNetwork.logout(this.doAuthenticate.bind(this));
     },
 
     persistAddressAndKeyPair: function(email, keypair, issuer) {
@@ -479,88 +234,39 @@ $.Controller("Dialog", {}, {
         });
       
       var self = this;
+      BrowserIDNetwork.syncEmails(issued_identities, 
+        function onKeySyncSuccess(email, keypair) {
+          self.persistAddressAndKeyPair(email, keypair, "browserid.org:443");
+        },
+        function onKeySyncFailure() {
+          self.runErrorDialog(BrowserIDErrors.syncAddress);
+        },
+        function onSuccess() {
+          self.doSignIn();
+        },
+        function onFailure(jqXHR, textStatus, errorThrown) {
+          self.runErrorDialog(BrowserIDErrors.signIn);
+        }
+      );
 
-      $.ajax({
-          url: '/wsapi/sync_emails',
-            type: "post",
-            data: {'emails': JSON.stringify(issued_identities),
-              'csrf': this.csrf},
-            success: function(resp, textStatus, jqXHR) {
-            // first remove idenitites that the server doesn't know about
-            if (resp.unknown_emails) {
-              _(resp.unknown_emails).each(function(email_address) {
-                  console.log("removed local identity: " + email_address);
-                  removeEmail(email_address);
-                });
-            }
-
-            // now let's begin iteratively re-keying the emails mentioned in the server provided list
-            var emailsToAdd = resp.key_refresh;
-            
-            function addNextEmail() {
-              if (!emailsToAdd || !emailsToAdd.length) {
-                self.doSignIn();
-                return;
-              }
-
-              // pop the first email from the list
-              var email = emailsToAdd.shift();
-              var keypair = CryptoStubs.genKeyPair();
-
-              $.ajax({
-                  type: 'POST',
-                  url: '/wsapi/set_key',
-                  data: {
-                    email: email,
-                    pubkey: keypair.pub,
-                    csrf: self.csrf},
-                  success: function() {
-                    // update emails list and commit to local storage, then go do the next email
-                    self.persistAddressAndKeyPair(email, keypair, "browserid.org:443");
-                    addNextEmail();
-                  },
-                    error: function() {
-                    runErrorDialog(
-                                   "serverError",
-                                   "Error Adding Address!",
-                                   "There was a technical problem while trying to synchronize your account.  Yucky.");
-                  }
-                });
-            }
-
-            addNextEmail();
-          },
-            error: function(jqXHR, textStatus, errorThrown) {
-            runErrorDialog("serverError", "Signin Failed", jqXHR.responseText);
-          }
-        });
-
-      
     },
 
-    checkAuth: function(authcb, notauthcb) {
-      this.doWait("Communicating with server",
-             "Just a moment while we talk with the server.");
-      
-      $.ajax({
-          url: '/wsapi/am_authed',
-            success: function(status, textStatus, jqXHR) {
-            var authenticated = JSON.parse(status);
-            if (!authenticated) {
-              notauthcb();
-            } else {
-              authcb();
-            }
-          },
-            error: function() {
-            runErrorDialog(
-                           "serverError",
-                           "Error Communicating With Server!",
-                           "There was a technical problem while trying to log you in.  Yucky!");
-          }
-        });
+
+    doCheckAuth: function() {
+      this.doWait(BrowserIDWait.checkAuth);
+      var self=this;
+      BrowserIDNetwork.checkAuth(function(authenticated) {
+        if (authenticated) {
+          self.syncIdentities();
+        } else {
+          self.doAuthenticate();
+        }
+      }, function() {
+        self.runErrorDialog(BrowserIDErrors.checkAuthentication);
+      });
   }
 
   });
 
 
+}());
diff --git a/browserid/static/dialog/controllers/forgotpassword_controller.js b/browserid/static/dialog/controllers/forgotpassword_controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7037030ce96854af915f047ec94bf82168bfc05
--- /dev/null
+++ b/browserid/static/dialog/controllers/forgotpassword_controller.js
@@ -0,0 +1,114 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: 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 ***** */
+(function() {
+  "use strict";
+
+  PageController.extend("Forgotpassword", {}, {
+      init: function() {
+        this._super({
+          bodyTemplate: "forgotpassword.ejs",
+          bodyVars: {},
+          footerTemplate: "bottom-continue.ejs",
+          footerVars: {}
+        });
+
+        $("#create_continue").addClass("disabled");
+        
+        this.setupWatchers();
+      },
+
+      setupWatchers: function() {
+        var self=this;
+        function checkInput() {
+          var pass = $("#password_input").val();
+          var match = pass === $("#password_verify_input").val();
+          self.find(".passwordnote").hide();
+          $("#create_continue").addClass("disabled");
+          if (!match) {
+            self.find("#passwords_different").show();
+          } else {
+            if (!pass) {
+              self.find("#enter_a_password").show();
+            } else if (pass.length < 5) {
+              self.find("#password_too_short").show();
+            } else {
+              self.find("#password_ok").show();
+              $("#create_continue").removeClass("disabled");
+            }
+          }
+        }
+        
+        // watch input dialogs
+        self.find("input").unbind("keyup").bind("keyup", checkInput);
+        
+        // do a check at load time, in case the user is using the back button (enables the continue button!)
+        checkInput();
+
+      },
+
+      validate: function() {
+        if ($("#create_continue").hasClass("disabled"))
+          return false;
+        return true;
+      },
+
+      submit: function() {
+        // now we need to actually try to stage the creation of this account.
+        var email = this.find("#email_input").val();
+        var pass = this.find("#password_input").val();
+        var keypair = CryptoStubs.genKeyPair();
+
+        this.doWait(BrowserIDWait.createAccount);
+
+        var self = this;
+        BrowserIDNetwork.stageUser(email, pass, keypair, function() {
+            self.close("createaccount:created", {
+              email: email,
+              keypair: keypair
+            });
+          },
+          function() {
+            self.runErrorDialog(BrowserIDErrors.createAccount);
+          }
+        );
+      }
+
+  });
+
+}());
+
+
diff --git a/browserid/static/dialog/controllers/page_controller.js b/browserid/static/dialog/controllers/page_controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..a1b6f0937e43b2ece83d8354ce1e8f6361e9b020
--- /dev/null
+++ b/browserid/static/dialog/controllers/page_controller.js
@@ -0,0 +1,135 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: 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 ***** */
+//
+// a JMVC controller for the browserid dialog
+//
+
+(function() {
+"use strict";
+
+  $.Controller.extend("PageController", {
+    }, {
+    init: function(options) {
+      var bodyTemplate = options.bodyTemplate;
+      var bodyVars = options.bodyVars;
+      var footerTemplate = options.footerTemplate;
+      var footerVars = options.footerVars;
+
+      this.renderTemplates(bodyTemplate, bodyVars, footerTemplate, footerVars);
+      $("form").bind("submit", this.onSubmit.bind(this));
+      $("#cancel").click(this.onCancel.bind(this));
+      $("#back").click(this.onBack.bind(this));
+    },
+
+    destroy: function() {
+      $("form").unbind("submit");
+      $("input").unbind("keyup");
+      $("#cancel").unbind("click");
+      $("#back").unbind("click");
+      this._super();
+    },
+
+    renderTemplates: function(body, body_vars, footer, footer_vars) {
+      if (body) {
+        var bodyHtml = $.View("//dialog/views/" + body, body_vars);
+        $("#dialog").html(bodyHtml).hide().fadeIn(300, function() {
+          $("#dialog input").eq(0).focus(); 
+        });
+      }
+
+      if (footer) {
+        var footerHtml = $.View("//dialog/views/" + footer, footer_vars);
+        $("#bottom-bar").html(footerHtml);
+      }
+    },
+
+    onSubmit: function(event) {
+      event.stopPropagation();
+      event.preventDefault();
+      if (this.validate()) {
+        this.submit();
+      }
+      return false;
+    },
+
+    validate: function() {
+      return true;
+    },
+
+    submit: function() {
+      this.close("submit");
+    },
+
+    doWait: function(info) {
+      this.renderTemplates("wait.ejs", {title: info.message, message: info.description});
+    },
+
+    close: function(message, data) {
+      this.destroy();
+      if (message) {
+        this.publish(message, data);
+      }
+    },
+
+    runErrorDialog: function(info) {
+      $(".dialog").hide();
+
+      $("#error_dialog div.title").text(info.message);
+      $("#error_dialog div.content").text(info.description);
+
+      $("#back").hide();
+      $("#cancel").hide();
+      $("#submit").show().unbind("click").click(function() {
+      }).text("Close");
+
+      $("#error_dialog").fadeIn(500);
+    },
+
+    onCancel: function(event) {
+      event.preventDefault();
+      event.stopPropagation();
+      this.close("cancel");
+    },
+
+    onBack: function(event) {
+      event.preventDefault();
+      event.stopPropagation();
+      this.close("start");
+    }
+  });
+
+}());
diff --git a/browserid/static/dialog/dialog.js b/browserid/static/dialog/dialog.js
index 281b52b02609314d138c318fd34cd8d3e02458f9..238dcc7cb1339b88a8b6da58531a93b319c407ac 100644
--- a/browserid/static/dialog/dialog.js
+++ b/browserid/static/dialog/dialog.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 /*globals steal
  */
 steal.plugins(
@@ -13,11 +48,22 @@ steal.plugins(
                'crypto',
                'crypto-api',
                'channel',
-               'storage')					// 3rd party script's (like jQueryUI), in resources folder
+               'storage',
+               'browserid-extensions',
+               'browserid-network',
+               'browserid-errors',
+               'browserid-wait')					// 3rd party script's (like jQueryUI), in resources folder
 
 	.models()						// loads files in models folder 
 
-	.controllers('dialog')					// loads files in controllers folder
+	.controllers('page',
+               'dialog',
+               'authenticate',
+               'createaccount',
+               'checkregistration',
+               'forgotpassword',
+               'chooseemail',
+               'addemail')					// loads files in controllers folder
 
 	.views('authenticate.ejs',
            'addemail.ejs',
diff --git a/browserid/static/dialog/register_iframe.js b/browserid/static/dialog/register_iframe.js
index c2da36defa52957ec807e68f3f6f3b1e9f2c77b8..42494702a9e5792f36c20e8630bfdf67a2f0bddb 100644
--- a/browserid/static/dialog/register_iframe.js
+++ b/browserid/static/dialog/register_iframe.js
@@ -1,6 +1,38 @@
-/*jshint browser:true, jQuery: true, forin: true */
-/*global Channel:true, CryptoStubs:true, alert:true, errorOut: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 ***** */
+
 // this is the picker code!  it runs in the identity provider's domain, and
 // fiddles the dom expressed by picker.html
 (function() {
diff --git a/browserid/static/dialog/resources/browserid-errors.js b/browserid/static/dialog/resources/browserid-errors.js
new file mode 100644
index 0000000000000000000000000000000000000000..e4ed574ee58c214ceff0ccc18ffa25d2febae21b
--- /dev/null
+++ b/browserid/static/dialog/resources/browserid-errors.js
@@ -0,0 +1,87 @@
+/* ***** 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 ***** */
+var BrowserIDErrors = (function(){
+  "use strict";
+
+  var Errors = {
+    authentication: {
+      type: "serverError",
+      message: "Error Authenticating",
+      description: "There was a technical problem while trying to log you in.  Yucky!"
+    },
+
+    addEmail: {
+      type: "serverError",
+      message: "Error Adding Address",
+      description: "There was a technical problem while trying to add this email to your account.  Yucky!"
+    },
+
+    checkAuthentication: {
+      type: "serverError",
+      message: "Error Checking Authentication",
+      description: "There was a tenical problem while trying to log you in.  Yucky!"
+    },
+
+    createAccount: {
+      type: "serverError",
+      message: "Error Creating Account",
+      description: "There was a technical problem while trying to create your account.  Yucky!"
+    },
+
+    registration: {
+      type: "serverError",
+      message: "Registration Failed",
+      description: "An error was encountered and the signup cannot be completed.  Yucky!"
+    },
+
+    signIn: {
+      type: "serverError",
+      message: "Signin Failed",
+      description: "There was an error signing in. Yucky!"
+    },
+
+    syncAddress: {
+      type: "serverError",
+      message: "Error Syncing Address",
+      description: "There was a technical problem while trying to synchronize your account.  Yucky!"
+    }
+
+  };
+
+
+  return Errors;
+}());
+
+
diff --git a/browserid/static/dialog/resources/browserid-extensions.js b/browserid/static/dialog/resources/browserid-extensions.js
new file mode 100644
index 0000000000000000000000000000000000000000..1d45a366caec5dbdd111c8028d12846c5c388e36
--- /dev/null
+++ b/browserid/static/dialog/resources/browserid-extensions.js
@@ -0,0 +1,58 @@
+/* ***** 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 ***** */
+
+if (!Function.prototype.bind) {
+
+  Function.prototype.bind = function (oThis) {
+
+    if (typeof this !== "function") // closest thing possible to the ECMAScript 5 internal IsCallable function
+      throw new TypeError("Function.prototype.bind - what is trying to be fBound is not callable");
+
+    var aArgs = Array.prototype.slice.call(arguments, 1), 
+    fToBind = this, 
+    fNOP = function () {},
+    fBound = function () {
+      return fToBind.apply(this instanceof fNOP ? this : oThis || window, aArgs.concat(Array.prototype.slice.call(arguments)));    
+    };
+
+    fNOP.prototype = this.prototype;
+    fBound.prototype = new fNOP();
+
+    return fBound;
+
+  };
+
+}
+
diff --git a/browserid/static/dialog/resources/browserid-network.js b/browserid/static/dialog/resources/browserid-network.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b1e4eeac5cea8d74404a5d9f3450189ace1e24c
--- /dev/null
+++ b/browserid/static/dialog/resources/browserid-network.js
@@ -0,0 +1,218 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global _: true, console: true, addEmail: true, removeEmail: true, CryptoStubs: 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 ***** */
+"use strict";
+var BrowserIDNetwork = (function() {
+  var Network = {
+    csrf: function(onSuccess) {
+      $.get('/csrf', {}, function(result) {
+        BrowserIDNetwork.csrf_token = result;
+        if(onSuccess) {
+          onSuccess();
+        }
+      });
+    },
+
+    setOrigin: function(origin) {
+      BrowserIDNetwork.origin = filterOrigin(origin);
+    },
+
+    authenticate: function(email, password, onSuccess, onFailure) {
+      $.ajax({
+        type: "POST",
+        url: '/wsapi/authenticate_user',
+        data: {
+          email: email,
+          pass: password,
+          csrf: BrowserIDNetwork.csrf_token
+        },
+        success: function(status, textStatus, jqXHR) {
+          if(onSuccess) {
+            var authenticated = JSON.parse(status);
+            onSuccess(authenticated);
+          }
+        },
+        error: onFailure
+      });
+    },
+
+    checkAuth: function(onSuccess, onFailure) {
+      $.ajax({
+        url: '/wsapi/am_authed',
+        success: function(status, textStatus, jqXHR) {
+          var authenticated = JSON.parse(status);
+          onSuccess(authenticated);
+        },
+        error: onFailure
+      });
+
+    },
+
+    logout: function(onSuccess) {
+      $.post("/wsapi/logout", {
+        csrf: BrowserIDNetwork.csrf_token
+      }, 
+      function() {
+        BrowserIDNetwork.csrf();
+        onSuccess();
+      });
+    },
+
+    stageUser: function(email, password, keypair, onSuccess, onFailure) {
+      $.ajax({
+          type: "post",
+          url: '/wsapi/stage_user',
+          data: {
+            email: email,
+            pass: password,
+            pubkey : keypair.pub,
+            site : BrowserIDNetwork.origin,
+            csrf : BrowserIDNetwork.csrf_token
+          },
+          success: onSuccess,
+          error: onFailure
+        });
+
+    },
+
+    addEmail: function(email, keypair, onSuccess, onFailure) {
+      $.ajax({
+        type: 'POST',
+        url: '/wsapi/add_email',
+        data: {
+          email: email,
+          pubkey: keypair.pub,
+          site: BrowserIDNetwork.origin,
+          csrf: BrowserIDNetwork.csrf_token
+        },
+        success: onSuccess,
+        error: onFailure
+      });
+    },
+
+    haveEmail: function(email, onSuccess, onFailure) {
+      $.ajax({
+        url: '/wsapi/have_email?email=' + encodeURIComponent(email),
+        success: function(data, textStatus, xhr) {
+          if(onSuccess) {
+            var success = !JSON.parse(data);
+            onSuccess(success);
+          }
+        },
+        error: onFailure
+      });
+    },
+
+    checkRegistration: function(onSuccess, onFailure) {
+      $.ajax({
+          url: '/wsapi/registration_status',
+          success: function(status, textStatus, jqXHR) {
+            if(onSuccess) {
+              onSuccess(status);
+            }
+          },
+          error: onFailure
+      });
+    },
+
+    setKey: function(email, keypair, onSuccess, onError) {
+      $.ajax({
+        type: 'POST',
+        url: '/wsapi/set_key',
+        data: {
+          email: email,
+          pubkey: keypair.pub,
+          csrf: BrowserIDNetwork.csrf_token
+        },
+        success: onSuccess,
+        error: onError
+      });
+
+    },
+
+    syncEmails: function(issued_identities, onKeySyncSuccess, onKeySyncFailure, onSuccess, onFailure) {
+      $.ajax({
+        type: "POST",
+        url: '/wsapi/sync_emails',
+        data: {
+          emails: JSON.stringify(issued_identities),
+          csrf: BrowserIDNetwork.csrf_token
+        },
+        success: function(resp, textStatus, jqXHR) {
+          // first remove idenitites that the server doesn't know about
+          if (resp.unknown_emails) {
+            _(resp.unknown_emails).each(function(email_address) {
+                removeEmail(email_address);
+              });
+          }
+
+          // now let's begin iteratively re-keying the emails mentioned in the server provided list
+          var emailsToAdd = resp.key_refresh;
+          
+          function addNextEmail() {
+            if (!emailsToAdd || !emailsToAdd.length) {
+              onSuccess();
+              return;
+            }
+
+            // pop the first email from the list
+            var email = emailsToAdd.shift();
+            var keypair = CryptoStubs.genKeyPair();
+
+            BrowserIDNetwork.setKey(email, keypair, function() {
+              // update emails list and commit to local storage, then go do the next email
+              onKeySyncSuccess(email, keypair);
+              addNextEmail();
+            }, onKeySyncFailure);
+          }
+
+          addNextEmail();
+        },
+        error: onFailure
+      }
+    );
+
+
+    }
+  };
+
+  Network.csrf();
+  return Network;
+
+  function filterOrigin(origin) {
+    return origin.replace(/^.*:\/\//, '');
+  }
+}());
diff --git a/browserid/static/dialog/resources/browserid-wait.js b/browserid/static/dialog/resources/browserid-wait.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e4e6fe0849060074679ef9d218be1155b1eaa05
--- /dev/null
+++ b/browserid/static/dialog/resources/browserid-wait.js
@@ -0,0 +1,64 @@
+/* ***** 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 ***** */
+var BrowserIDWait = (function(){
+  "use strict";
+
+  var Wait = {
+    authentication: {
+      message: "Finishing Sign In...",
+      description: "In just a moment you'll be signed into BrowserID."
+    },
+
+    addEmail: {
+      message: "One Moment Please...",
+      description: "We're adding this email to your account, this should only take a couple of seconds."
+    },
+
+    checkAuth: {
+      message: "Communicating with server",
+      description: "Just a moment while we talk with the server."
+    },
+
+    createAccount: {
+      message: "One Moment Please...",
+      description: "We're creating your account, this should only take a couple of seconds."
+    }
+  };
+
+
+  return Wait;
+}());
+
+
diff --git a/browserid/static/dialog/resources/channel.js b/browserid/static/dialog/resources/channel.js
index 8f08018fc62acdb6c415363f7526d63a18bb00cc..43aa6703fb4c8d72d6a5b8c059c259dd4250d1d1 100644
--- a/browserid/static/dialog/resources/channel.js
+++ b/browserid/static/dialog/resources/channel.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 /*global alert:true, setupNativeChannel:true, setupHTMLChannel:true, Channel:true */
 function errorOut(trans, code) {
   function getVerboseMessage(code) {
diff --git a/browserid/static/dialog/resources/crypto-api.js b/browserid/static/dialog/resources/crypto-api.js
index 3eeec8c946f93835c19af2e97a20e923d703b81b..76b39ff1e86f7508060c4b07b791436a951f1d5e 100644
--- a/browserid/static/dialog/resources/crypto-api.js
+++ b/browserid/static/dialog/resources/crypto-api.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 /*global CryptoStubs:true */
 // This file is the cryptographic routines that are required for
 // BrowserID's HTML5 implementation
diff --git a/browserid/static/dialog/resources/main.js b/browserid/static/dialog/resources/main.js
index 6b2053d331529a77cfd7b76eedb7d57b9f7bce77..897dcb99b4281f0103b980c0e2a3c78dd657ed90 100644
--- a/browserid/static/dialog/resources/main.js
+++ b/browserid/static/dialog/resources/main.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 // this is the picker code!  it runs in the identity provider's domain, and
 // fiddles the dom expressed by picker.html
 var run = function() {
diff --git a/browserid/static/dialog/resources/storage.js b/browserid/static/dialog/resources/storage.js
index c8577e40c2f97631968a3651e2a6abe772b50fb7..0a97ee8149984de75bbe5463ec270fa6bbb57944 100644
--- a/browserid/static/dialog/resources/storage.js
+++ b/browserid/static/dialog/resources/storage.js
@@ -1,3 +1,37 @@
+/* ***** 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 ***** */
 
 var getEmails = function() {
   try {
diff --git a/browserid/static/dialog/style.css b/browserid/static/dialog/style.css
index 2612391d98bf3653b06cae42f1f79fcda8a8d263..dbcc58785844f4208f8adae36524e8ec5ab0bb4e 100644
--- a/browserid/static/dialog/style.css
+++ b/browserid/static/dialog/style.css
@@ -54,7 +54,7 @@ span.sitename, span.email {
     font-size: .7em;
 }
 
-#bottom-bar button {
+#bottom-bar button, #bottom-bar input[type=submit] {
     height: 25px;
     border: 1px solid rgb(145, 145, 145);
     -moz-border-radius: 4px;
@@ -67,13 +67,14 @@ span.sitename, span.email {
     margin-right: 1em;
     margin-top: 16px;
     z-index: 0;
+    float: left;
 }
 
-#bottom-bar button.righty {
+#bottom-bar button.righty, #bottom-bar input[type=submit].righty {
     float: right;
 }
 
-#bottom-bar button.action {
+#bottom-bar button.action, #bottom-bar input[type=submit] {
     background-color: rgb(0,128,211);
     color: #fff;
     font-weight: bold;
@@ -100,14 +101,13 @@ div.actions {
 #identities {
     width: 450px;
     margin: auto;
-    list-style: none;
 }
 
-#identities > ul, #identities > ul > li {
-  list-style: none;
+#identities, #identities > li {
+    list-style: none;
 }
 
-#identities > ul > li {
+#identities > li {
   margin-bottom: .5em;
   height: 1.4em;
   font-size: .9em;
@@ -118,11 +118,11 @@ input[type=radio], input[type=radio] + label {
   cursor: pointer;
 }
 
-button {
+button, input[type=submit] {
     cursor: pointer;
 }
 
-button.disabled {
+button.disabled, input[type=submit].disabled {
     cursor: default;
     opacity: .3;
 }
diff --git a/browserid/static/dialog/views/addemail.ejs b/browserid/static/dialog/views/addemail.ejs
index 2c2584ebd91deb1d1a333ed82de5713fe155898a..c8f21a33e95bafed00558251737fe0caef5f1072 100644
--- a/browserid/static/dialog/views/addemail.ejs
+++ b/browserid/static/dialog/views/addemail.ejs
@@ -1,9 +1,9 @@
   <h2> Add a new email address </h2>
   <div class="content">
-    <div class="summary">Setting up a up a new email address is easy, tell us what it is and we'll get started:</div>
+    <div class="summary">Setting up a new email address is easy, tell us what it is and we'll get started:</div>
     <div class="formRow">
       <label for="email_input">Email</label>
-      <input type="email" id="email_input" />
+      <input type="email" id="email_input" required/>
       <span class="note"></span>
     </div>
   </div>
diff --git a/browserid/static/dialog/views/body.ejs b/browserid/static/dialog/views/body.ejs
index b2952fe21134287a155a77a177fe39fa730d00bb..813ccae9848365cb1880d191a491886cf9f616f9 100644
--- a/browserid/static/dialog/views/body.ejs
+++ b/browserid/static/dialog/views/body.ejs
@@ -6,13 +6,15 @@
   <div id="labs_logo" class="sprite"></div>
 </header>
 
-<div id="dialog" class="dialog">
-</div>
+<form action="" onsubmit="return false;"> <!-- using the form element to hook into with js -->
+  <div id="dialog" class="dialog">
+  </div>
 
-<div id="bottom-bar">
-</div>
+  <div id="bottom-bar">
+  </div>
+</form>
 
 <footer id="terms">
-    BrowserID is a <a target="_blank" href="http://mozillalabs.org">Mozilla Labs</a> service.
+    BrowserID is a <a target="_blank" href="http://mozillalabs.com">Mozilla Labs</a> service.
     See our <a target="_blank" href="https://browserid.org/tos">terms</a> and <a target="_blank" href="https://browserid.org/privacy">privacy policy</a>, or <a target="_blank" href="https://browserid.org/users">learn more</a>.
 </footer>
diff --git a/browserid/static/dialog/views/bottom-addemail.ejs b/browserid/static/dialog/views/bottom-addemail.ejs
index 06c26d8a611151908f2a3d04167048e044da249b..a531ce5a16585fcf7ef858f311e4fb987278f9fa 100644
--- a/browserid/static/dialog/views/bottom-addemail.ejs
+++ b/browserid/static/dialog/views/bottom-addemail.ejs
@@ -1,3 +1,3 @@
-  <button id="back">Go Back</button>
   <button id="addemail_button" class="righty action submit">Add Email</button>
   <button id="cancel" class="righty">Cancel</button>
+  <button id="back">Go Back</button>
diff --git a/browserid/static/dialog/views/bottom-confirmemail.ejs b/browserid/static/dialog/views/bottom-confirmemail.ejs
index a12dda8935ab5ea8c63fcfc35cb3c6ab1517143a..431593fa8c219d94b22220ecb9d6a4b90828aa6e 100644
--- a/browserid/static/dialog/views/bottom-confirmemail.ejs
+++ b/browserid/static/dialog/views/bottom-confirmemail.ejs
@@ -1,2 +1,2 @@
-<button class="righty action submit" id="continue_button">Continue</button>
+<input type="submit" class="righty action submit" id="continue_button" value="Continue" />
 <button class="righty" id="cancel">Cancel</button>
diff --git a/browserid/static/dialog/views/bottom-continue.ejs b/browserid/static/dialog/views/bottom-continue.ejs
index 53d61073047ae36a5e2658c8c0269ff94970fb2e..9ac23c8079ca1c126f69682ff3a5a703eb6a0b7b 100644
--- a/browserid/static/dialog/views/bottom-continue.ejs
+++ b/browserid/static/dialog/views/bottom-continue.ejs
@@ -1,3 +1,3 @@
-  <button id="back">Go Back</button>
-  <button id="create_continue" class="righty action submit">Continue</button>
+  <input type="submit" id="create_continue" class="righty action submit" value="Continue" />
   <button id="cancel" class="righty">Cancel</button>
+  <button id="back">Go Back</button>
diff --git a/browserid/static/dialog/views/bottom-pickemail.ejs b/browserid/static/dialog/views/bottom-pickemail.ejs
index debe989ff1c3cb8865cb52cead510c6f58a5a59e..b1cf8b5133ab88f7825ae323079587497eb4fba0 100644
--- a/browserid/static/dialog/views/bottom-pickemail.ejs
+++ b/browserid/static/dialog/views/bottom-pickemail.ejs
@@ -1,2 +1,2 @@
-  <button id="pickemail" class="righty action submit">Sign In</button>
+  <input type="submit" id="pickemail" class="righty action submit" value="Sign In" />
   <button id="cancel" class="righty">Cancel</button>
diff --git a/browserid/static/dialog/views/bottom-signin.ejs b/browserid/static/dialog/views/bottom-signin.ejs
index f069d5033a3d9daf7826300cd74efcdbcb4d81c3..1c235718f4f8afa0cecf608791aa7cc18b5543af 100644
--- a/browserid/static/dialog/views/bottom-signin.ejs
+++ b/browserid/static/dialog/views/bottom-signin.ejs
@@ -1,2 +1,2 @@
-  <button id="signin" class="righty action submit">Sign In</button>
+  <input type="submit" id="signin" class="righty action submit" name="submit" value="Sign In"/ >
   <button id="cancel" class="righty">Cancel</button>
diff --git a/browserid/static/dialog/views/bottom.ejs b/browserid/static/dialog/views/bottom.ejs
index 5c9aca7b789465b1d9c5fc0b85e8cb90a646237c..3d07d27924b46a43fb6c5cf4f01950fac4bfdc46 100644
--- a/browserid/static/dialog/views/bottom.ejs
+++ b/browserid/static/dialog/views/bottom.ejs
@@ -1,3 +1,3 @@
-  <button id="back">Go Back</button>
   <button id="submit" class="righty action submit">Sign In</button>
   <button id="cancel" class="righty">Cancel</button>
+  <button id="back">Go Back</button>
diff --git a/browserid/static/dialog/views/create.ejs b/browserid/static/dialog/views/create.ejs
index 07b5c1e0a650ae2be95a698eab5b38cc39cb904f..16b4c58849199d9a779542e5caadc38013c22d37 100644
--- a/browserid/static/dialog/views/create.ejs
+++ b/browserid/static/dialog/views/create.ejs
@@ -2,18 +2,18 @@
     <div class="summary">BrowserID makes signing in <b>safer and easier</b>.  To begin, please provide an email address and pick a password:</div>
     <div class="formRow">
       <label for="email_input"> Email </label>
-      <input id="email_input" type="email"/>
+      <input id="email_input" type="email" required/>
       <span class="note" id="email_input_note" style="display:none;">
         <span class="good">Not registered</span>
       </span>
     </div>
     <div class="formRow">
       <label for="password_input"> Password </label>
-      <input id="password_input" type="password" pattern=".{5,}"/>
+      <input id="password_input" type="password" pattern=".{5,}" required/>
     </div>
     <div class="formRow">
       <label for="password_verify_input"> Verify </label>
-      <input id="password_verify_input" type="password" pattern=".{5,}"/>
+      <input id="password_verify_input" type="password" pattern=".{5,}" required/>
       <span class="note passwordnote" id="enter_a_password"><span class="bad">Enter a password</span></span>
       <span class="note passwordnote" id="passwords_different" style="display:none;"><span class="bad">Passwords different</span></span>
       <span class="note passwordnote" id="password_too_short" style="display:none;"><span class="bad">Password too short</span></span>
diff --git a/browserid/static/dialog/views/forgotpassword.ejs b/browserid/static/dialog/views/forgotpassword.ejs
index 7f0870ec35b09b86743a0a1c6efb103ee3b3eb60..9a038b93d5220d81c01a6db54a4f05cdfb2e625b 100644
--- a/browserid/static/dialog/views/forgotpassword.ejs
+++ b/browserid/static/dialog/views/forgotpassword.ejs
@@ -2,16 +2,16 @@
     <div class="summary"><b>Forgot your password?</b>  No problem!  Enter your email address, pick a new password, and we'll get you set up again!</div>
     <div class="formRow">
       <label for="email_input"> Email </label>
-      <input id="email_input" type="email"/>
+      <input id="email_input" type="email" required/>
       <span class="note" id="email_input_note" style="display:none;"><span class="good">Not registered</span></span>
     </div>
     <div class="formRow">
       <label for="password_input"> Password </label>
-      <input id="password_input" type="password" pattern=".{5,}"/>
+      <input id="password_input" type="password" pattern=".{5,}" required/>
     </div>
     <div class="formRow">
       <label for="password_verify_input"> Verify </label>
-      <input id="password_verify_input" type="password" pattern=".{5,}"/>
+      <input id="password_verify_input" type="password" pattern=".{5,}" required/>
       <span class="note passwordnote" id="enter_a_password"><span class="bad">Enter a password</span></span>
       <span class="note passwordnote" id="passwords_different" style="display:none;"><span class="bad">Passwords different</span></span>
       <span class="note passwordnote" id="password_too_short" style="display:none;"><span class="bad">Password too short</span></span>
diff --git a/browserid/static/dialog/views/signin.ejs b/browserid/static/dialog/views/signin.ejs
index 0fda9e7e17dbe370938da09d634787c02b2efcb3..25519d6ec68f1e732d0b9149226d23da75e27f33 100644
--- a/browserid/static/dialog/views/signin.ejs
+++ b/browserid/static/dialog/views/signin.ejs
@@ -1,12 +1,10 @@
   <div class="content">
     <p class="prompt">What email address would you like to use to sign into <span class="sitename bad"><%= sitename %></span>?</p>
-    <form id="identities" name="identities">
-      <ul>
-        <% _.each(identities, function(email_obj, email_address) { %>
-          <li><input type="radio" name="identity" value="<%=email_address%>" id="<%=email_address%>" /><label for="<%=email_address%>"><%= email_address %></label></li>
-        <% }); %>
-      </ul>
-    </form>
+    <ul id="identities">
+      <% _.each(identities, function(email_obj, email_address) { %>
+        <li><input type="radio" name="identity" value="<%=email_address%>" id="<%=email_address%>" /><label for="<%=email_address%>"><%= email_address %></label></li>
+      <% }); %>
+    </ul>
   </div>
   <div class="actions">
     <div class="action"><a id="addemail" href="#">Add a new email address</a></div>
diff --git a/browserid/static/include.js b/browserid/static/include.js
index 64b1ff758d4d6cb0ea8b269e08b99ba5d57702f7..b2083c867a5507225c4e0c476b86d9f629f7ed27 100644
--- a/browserid/static/include.js
+++ b/browserid/static/include.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 // this is the file that the RP includes to shim in the
 // navigator.id.getVerifiedEmail() function
 
@@ -8,8 +43,9 @@ if (!navigator.id) {
 if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed)
 {
   var ipServer = "https://browserid.org";
+  var isMobile = navigator.userAgent.indexOf('Fennec/') != -1;
 
-  // local embedded copy of jschannel: http://github.com/mozilla/jschannel 
+  // local embedded copy of jschannel: http://github.com/mozilla/jschannel
   var Channel = (function() {
     // current transaction id, start out at a random *odd* number between 1 and a million
     // There is one current transaction counter id per page, and it's shared between
@@ -538,9 +574,8 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed)
 
   function _open_window() {
       return window.open(
-                         //ipServer + "/dialog/dialog/dialog.html", "_mozid_signin",
-                         ipServer + "/sign_in", "_mozid_signin",
-          "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=520,height=350");
+          ipServer + "/sign_in", "_mozid_signin",
+          isMobile ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=520,height=350");
   }
 
   navigator.id.getVerifiedEmail = function(callback) {
@@ -608,7 +643,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed)
   navigator.id.getSpecificVerifiedEmail = function(email, token, onsuccess, onerror) {
     var doc = window.document;
 
-    // if we have a token, we should not be opening a window, rather we should be 
+    // if we have a token, we should not be opening a window, rather we should be
     // able to do this entirely through IFRAMEs
     if (token) {
         var iframe = _create_iframe(doc);
@@ -676,7 +711,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed)
         cleanup();
       }
     });
-  };  
+  };
 
   navigator.id._getVerifiedEmailIsShimmed = true;
 }
diff --git a/browserid/static/js/browserid.js b/browserid/static/js/browserid.js
index d13546d9dfae0c83a3a9001b83ac53df7e95270a..b497d80557276a21680b67f8c01eefa0a2be7683 100644
--- a/browserid/static/js/browserid.js
+++ b/browserid/static/js/browserid.js
@@ -1,3 +1,38 @@
+/* ***** 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() {
   if ($('#emailList')) {
     display_saved_ids();
diff --git a/browserid/tests/db-test.js b/browserid/tests/db-test.js
index bd7afd9145bf30bb15b9d2d0f9c6a645b1c353be..c0a492ece0dcbe822a3842be7adb4fe59e949b36 100755
--- a/browserid/tests/db-test.js
+++ b/browserid/tests/db-test.js
@@ -1,5 +1,40 @@
 #!/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 ***** */
+
 const
 assert = require('assert'),
 vows = require('vows'),
@@ -12,364 +47,425 @@ var suite = vows.describe('db');
 // disable vows (often flakey?) async error behavior
 suite.options.error = false;
 
-db.dbPath = temp.path({suffix: '.sqlite'});
-
-suite.addBatch({
-  "waiting for the database to become ready": {
-    topic: function() {
-      var cb = this.callback;
-      db.onReady(function() { cb(true) });
-    },
-    "the database is ready": function(r) {
-      assert.strictEqual(r, true);
-    }
-  }
-});
-
-// caching of secrets between test batches.
-var secret = undefined;
+function addTestsForDriver(driver) {
+  var dbPath = temp.path({suffix: '.db'});
 
-suite.addBatch({
-  "an email address is not reported as staged before it is": {
-    topic: function() {
-      return db.isStaged('lloyd@nowhe.re');
-    },
-    "isStaged returns false": function (r) {
-      assert.strictEqual(r, false);
-    }
-  },
-  "an email address is not reported as known before it is": {
-    topic: function() {
-      db.emailKnown('lloyd@nowhe.re', this.callback);
-    },
-    "emailKnown returns false": function (r) {
-      assert.strictEqual(r, false);
-    }
-  }
-});
-
-suite.addBatch({
-  "stage a user for creation pending verification": {
-    topic: function() {
-      return secret = db.stageUser({
-        email: 'lloyd@nowhe.re',
-        pubkey: 'fakepubkey',
-        hash: 'fakepasswordhash'
-      });
-    },
-    "staging returns a valid secret": function(r) {
-      assert.isString(secret);
-      assert.strictEqual(secret.length, 48);
-    }
+  if (driver === 'mysql') {
+    // let's check to see if we can connect and render a nice
+    // error message if not.  For community members making casual
+    // contributions, we should expect that they might not want to
+    // set up mysql.
+    suite.addBatch({
+      "mysql server": {
+        topic: function() { db.open({driver: driver, unit_test: true}, this.callback) },
+        "accepting connections": function(err) {
+          if (err) {
+            console.log("MYSQL TESTS WILL FAIL cause cannot connect to a local mysql database (" + err.message + ")");
+          }
+        },
+        "connection closes": {
+          topic: function() { db.close(this.callback); },
+          "without error": function(err) {
+            assert.isUndefined(err);
+          }
+        }
+      }
+    });
   }
-});
 
-suite.addBatch({
-  "an email address is reported": {
-    topic: function() {
-      return db.isStaged('lloyd@nowhe.re');
+  suite.addBatch({
+    "onReady": {
+      topic: function() { db.onReady(this.callback); },
+      "works": function(r) { }
     },
-    " as staged after it is": function (r) {
-      assert.strictEqual(r, true);
-    }
-  },
-  "an email address is not reported": {
-    topic: function() {
-      db.emailKnown('lloyd@nowhe.re', this.callback);
+    "onReady still": {
+      topic: function() { db.onReady(this.callback); },
+      "works for more than one caller": function(r) { }
     },
-    " as known when it is only staged": function (r) {
-      assert.strictEqual(r, false);
-    }
-  }
-});
-
-suite.addBatch({
-  "upon receipt of a secret": {
-    topic: function() {
-      db.gotVerificationSecret(secret, this.callback);
-    },
-    "gotVerificationSecret completes without error": function (r) {
-      assert.strictEqual(r, undefined);
+    "opening the database": {
+      topic: function() {
+        db.open({ driver: driver, unit_test: true, path: dbPath }, this.callback);
+      },
+      "and its ready": function(r) {
+        assert.isUndefined(r);
+      },
+      "doesn't prevent onReady": {
+        topic: function() { db.onReady(this.callback); },
+        "from working": function(r) { }
+      }
     }
-  }
-});
+  });
 
-suite.addBatch({
-  "an email address is not reported": {
-    topic: function() {
-      return db.isStaged('lloyd@nowhe.re');
-    },
-    "as staged immediately after its verified": function (r) {
-      assert.strictEqual(r, false);
-    }
-  },
-  "an email address is known": {
-    topic: function() {
-      db.emailKnown('lloyd@nowhe.re', this.callback);
-    },
-    "when it is": function (r) {
-      assert.strictEqual(r, true);
-    }
-  }
-});
+  // caching of secrets between test batches.
+  var secret = undefined;
 
-suite.addBatch({
-  "adding keys to email": {
-    topic: function() {
-      db.addKeyToEmail('lloyd@nowhe.re', 'lloyd@nowhe.re', 'fakepubkey2', this.callback);
-    },
-    "works": function(r) {
-      assert.isUndefined(r);
-    }
-  }
-});
-
-suite.addBatch({
-  "adding multiple keys to email": {
-    topic: function() {
-      db.addKeyToEmail('lloyd@nowhe.re', 'lloyd@nowhe.re', 'fakepubkey3', this.callback);
+  suite.addBatch({
+    "an email address is not reported as staged before it is": {
+      topic: function() {
+        db.isStaged('lloyd@nowhe.re', this.callback);
+      },
+      "isStaged returns false": function (r) {
+        assert.isFalse(r);
+      }
     },
-    "works too": function(r) {
-      assert.isUndefined(r);
+    "an email address is not reported as known before it is": {
+      topic: function() {
+        db.emailKnown('lloyd@nowhe.re', this.callback);
+      },
+      "emailKnown returns false": function (r) {
+        assert.isFalse(r);
+      }
     }
-  }
-});
+  });
 
-suite.addBatch({
-  "pubkeysForEmail": {
-    topic: function() {
-      db.pubkeysForEmail('lloyd@nowhe.re', this.callback);
-    },
-    "returns all public keys properly": function(r) {
-      assert.isArray(r);
-      assert.strictEqual(r.length, 3);
+  suite.addBatch({
+    "stage a user for creation pending verification": {
+      topic: function() {
+        db.stageUser({
+          email: 'lloyd@nowhe.re',
+          pubkey: 'fakepubkey',
+          hash: 'fakepasswordhash'
+        }, this.callback);
+      },
+      "staging returns a valid secret": function(r) {
+        secret = r;
+        assert.isString(secret);
+        assert.strictEqual(secret.length, 48);
+      }
     }
-  }
-});
+  });
 
-suite.addBatch({
-  "checkAuth returns": {
-    topic: function() {
-      db.checkAuth('lloyd@nowhe.re', this.callback);
+  suite.addBatch({
+    "an email address is reported": {
+      topic: function() {
+        db.isStaged('lloyd@nowhe.re', this.callback);
+      },
+      " as staged after it is": function (r) {
+        assert.strictEqual(r, true);
+      }
     },
-    "the correct password": function(r) {
-      assert.strictEqual(r, "fakepasswordhash");
+    "an email address is not reported": {
+      topic: function() {
+        db.emailKnown('lloyd@nowhe.re', this.callback);
+      },
+      " as known when it is only staged": function (r) {
+        assert.strictEqual(r, false);
+      }
     }
-  }
-});
+  });
 
-suite.addBatch({
-  "staging an email": {
-    topic: function() {
-      return db.stageEmail('lloyd@nowhe.re', 'lloyd@somewhe.re', 'fakepubkey4');
-    },
-    "yields a valid secret": function(secret) {
-      assert.isString(secret);
-      assert.strictEqual(secret.length, 48);
-    },
-    "makes email addr via isStaged": {
-      topic: function() { return db.isStaged('lloyd@somewhe.re'); },
-      "visible": function(r) { assert.isTrue(r); }
-    },
-    "and verifying it": {
-      topic: function(secret) {
+  suite.addBatch({
+    "upon receipt of a secret": {
+      topic: function() {
         db.gotVerificationSecret(secret, this.callback);
       },
-      "returns no error": function(r) {
-        assert.isUndefined(r);
-      },
-      "makes email addr via knownEmail": {
-        topic: function() { db.emailKnown('lloyd@somewhe.re', this.callback); },
-        "visible": function(r) { assert.isTrue(r); }
-      },
-      "makes email addr via isStaged": {
-        topic: function() { return db.isStaged('lloyd@somewhe.re'); },
-        "not visible": function(r) { assert.isFalse(r); }
+      "gotVerificationSecret completes without error": function (r) {
+        assert.strictEqual(r, undefined);
       }
     }
-  }
-});
+  });
 
-// exports.emailsBelongToSameAccount
-suite.addBatch({
-  "emails do belong to the same account": {
-    "is true": { 
+  suite.addBatch({
+    "an email address is not reported": {
       topic: function() {
-        db.emailsBelongToSameAccount('lloyd@nowhe.re', 'lloyd@somewhe.re', this.callback);
+        db.isStaged('lloyd@nowhe.re', this.callback);
       },
-      "when they do": function(r) {
-        assert.isTrue(r);
+      "as staged immediately after its verified": function (r) {
+        assert.strictEqual(r, false);
       }
     },
-    "is false": { 
+    "an email address is known": {
       topic: function() {
-        db.emailsBelongToSameAccount('lloyd@anywhe.re', 'lloyd@somewhe.re', this.callback);
+        db.emailKnown('lloyd@nowhe.re', this.callback);
       },
-      "when they don't": function(r) {
-        assert.isFalse(r);
+      "when it is": function (r) {
+        assert.strictEqual(r, true);
       }
     }
-  }
-});
+  });
 
-// exports.getSyncResponse
-suite.addBatch({
-  "sync responses": {  
-    "are empty": {
+  suite.addBatch({
+    "adding keys to email": {
       topic: function() {
-        db.getSyncResponse('lloyd@nowhe.re',
-                           {
-                             'lloyd@nowhe.re': 'fakepubkey',
-                             'lloyd@somewhe.re': 'fakepubkey4'
-                           },
-                           this.callback);
+        db.addKeyToEmail('lloyd@nowhe.re', 'lloyd@nowhe.re', 'fakepubkey2', this.callback);
       },
-      "when everything is in sync": function (err, resp) {
-        assert.isUndefined(err);
-        assert.isArray(resp.unknown_emails);
-        assert.isArray(resp.key_refresh);
-        assert.strictEqual(resp.unknown_emails.length, 0);
-        assert.strictEqual(resp.key_refresh.length, 0);
+      "works": function(r) {
+        assert.isUndefined(r);
       }
-    },
-    "handles client unknown emails": {
+    }
+  });
+
+  suite.addBatch({
+    "adding multiple keys to email": {
       topic: function() {
-        db.getSyncResponse('lloyd@nowhe.re',
-                           {
-                             'lloyd@nowhe.re': 'fakepubkey'
-                           },
-                           this.callback);
+        db.addKeyToEmail('lloyd@nowhe.re', 'lloyd@nowhe.re', 'fakepubkey3', this.callback);
       },
-      "by returning them in the key_refresh list": function (err, resp) {
-        assert.isUndefined(err);
-        assert.isArray(resp.unknown_emails);
-        assert.isArray(resp.key_refresh);
-        assert.strictEqual(resp.unknown_emails.length, 0);
-        assert.strictEqual(resp.key_refresh.length, 1);
-        assert.strictEqual(resp.key_refresh[0], 'lloyd@somewhe.re');
+      "works too": function(r) {
+        assert.isUndefined(r);
       }
-    },
-    "handles server unknown emails": {
+    }
+  });
+
+  suite.addBatch({
+    "pubkeysForEmail": {
       topic: function() {
-        db.getSyncResponse('lloyd@nowhe.re',
-                           {
-                             'lloyd@nowhe.re': 'fakepubkey',
-                             'lloyd@somewhe.re': 'fakepubkey4',
-                             'lloyd@anywhe.re': 'nofakepubkey',
-                           },
-                           this.callback);
+        db.pubkeysForEmail('lloyd@nowhe.re', this.callback);
       },
-      "by returning them in the unknown_emails list": function (err, resp) {
-        assert.isUndefined(err);
-        assert.isArray(resp.unknown_emails);
-        assert.strictEqual(resp.unknown_emails.length, 1);
-        assert.strictEqual(resp.unknown_emails[0], 'lloyd@anywhe.re');
-        assert.isArray(resp.key_refresh);
-        assert.strictEqual(resp.key_refresh.length, 0);
+      "returns all public keys properly": function(r) {
+        assert.isArray(r);
+        assert.strictEqual(r.length, 3);
       }
-    },
-    "handles server unknown keys": {
+    }
+  });
+
+  suite.addBatch({
+    "checkAuth returns": {
       topic: function() {
-        db.getSyncResponse('lloyd@nowhe.re',
-                           {
-                             'lloyd@nowhe.re': 'fakepubkeyINVALID',
-                             'lloyd@somewhe.re': 'fakepubkey4'
-                           },
-                           this.callback);
+        db.checkAuth('lloyd@nowhe.re', this.callback);
       },
-      "by returning them in the key_refresh list": function (err, resp) {
-        assert.isUndefined(err);
-        assert.isArray(resp.unknown_emails);
-        assert.strictEqual(resp.unknown_emails.length, 0);
-        assert.isArray(resp.key_refresh);
-        assert.strictEqual(resp.key_refresh.length, 1);
-        assert.strictEqual(resp.key_refresh[0], 'lloyd@nowhe.re');
+      "the correct password": function(r) {
+        assert.strictEqual(r, "fakepasswordhash");
       }
-    },
-    "handle more than one case at a time": {
+    }
+  });
+
+  suite.addBatch({
+    "staging an email": {
       topic: function() {
-        db.getSyncResponse('lloyd@nowhe.re',
-                           {
-                             'lloyd@somewhe.re': 'fakepubkeyINVALID',
-                             'lloyd@anywhe.re': 'notreally'
-                           },
-                           this.callback);
+        db.stageEmail('lloyd@nowhe.re', 'lloyd@somewhe.re', 'fakepubkey4', this.callback);
       },
-      "when everything is outta sync": function (err, resp) {
-        assert.isUndefined(err);
-        assert.isArray(resp.unknown_emails);
-        assert.strictEqual(resp.unknown_emails.length, 1);
-        assert.strictEqual(resp.unknown_emails[0], 'lloyd@anywhe.re');
+      "yields a valid secret": function(secret) {
+        assert.isString(secret);
+        assert.strictEqual(secret.length, 48);
+      },
+      "then": {
+        topic: function(secret) {
+          var cb = this.callback;
+          db.isStaged('lloyd@somewhe.re', function(r) { cb(secret, r); });
+        },
+        "makes it visible via isStaged": function(sekret, r) { assert.isTrue(r); },
+        "and lets you verify it": {
+          topic: function(secret, r) {
+            db.gotVerificationSecret(secret, this.callback);
+          },
+          "successfully": function(r) {
+            assert.isUndefined(r);
+          },
+          "and knownEmail": {
+            topic: function() { db.emailKnown('lloyd@somewhe.re', this.callback); },
+            "returns true": function(r) { assert.isTrue(r); }
+          },
+          "and isStaged": {
+            topic: function() { db.isStaged('lloyd@somewhe.re', this.callback); },
+            "returns false": function(r) { assert.isFalse(r); }
+          }
+        }
+      }
+    }
+  });
 
-        assert.isArray(resp.key_refresh);
-        assert.strictEqual(resp.key_refresh.length, 2);
-        assert.strictEqual(resp.key_refresh[0], 'lloyd@nowhe.re');
-        assert.strictEqual(resp.key_refresh[1], 'lloyd@somewhe.re');
+  // exports.emailsBelongToSameAccount
+  suite.addBatch({
+    "emails do belong to the same account": {
+      "is true": { 
+        topic: function() {
+          db.emailsBelongToSameAccount('lloyd@nowhe.re', 'lloyd@somewhe.re', this.callback);
+        },
+        "when they do": function(r) {
+          assert.isTrue(r);
+        }
+      },
+      "is false": { 
+        topic: function() {
+          db.emailsBelongToSameAccount('lloyd@anywhe.re', 'lloyd@somewhe.re', this.callback);
+        },
+        "when they don't": function(r) {
+          assert.isFalse(r);
+        }
       }
     }
-  }
-});
+  });
 
-suite.addBatch({
-  "removing an existing email": {
-    topic: function() {
-      db.removeEmail("lloyd@somewhe.re", "lloyd@nowhe.re", this.callback);
-    },
-    "returns no error": function(r) {
-      assert.isUndefined(r);
-    },
-    "causes emailKnown": {
-      topic: function() {
-        db.emailKnown('lloyd@nowhe.re', this.callback);
+  // exports.getSyncResponse
+  suite.addBatch({
+    "sync responses": {  
+      "are empty": {
+        topic: function() {
+          db.getSyncResponse('lloyd@nowhe.re',
+                             {
+                               'lloyd@nowhe.re': 'fakepubkey',
+                               'lloyd@somewhe.re': 'fakepubkey4'
+                             },
+                             this.callback);
+        },
+        "when everything is in sync": function (err, resp) {
+          assert.isUndefined(err);
+          assert.isArray(resp.unknown_emails);
+          assert.isArray(resp.key_refresh);
+          assert.strictEqual(resp.unknown_emails.length, 0);
+          assert.strictEqual(resp.key_refresh.length, 0);
+        }
       },
-      "to return false": function (r) {
-        assert.strictEqual(r, false);
+      "handles client unknown emails": {
+        topic: function() {
+          db.getSyncResponse('lloyd@nowhe.re',
+                             {
+                               'lloyd@nowhe.re': 'fakepubkey'
+                             },
+                             this.callback);
+        },
+        "by returning them in the key_refresh list": function (err, resp) {
+          assert.isUndefined(err);
+          assert.isArray(resp.unknown_emails);
+          assert.isArray(resp.key_refresh);
+          assert.strictEqual(resp.unknown_emails.length, 0);
+          assert.strictEqual(resp.key_refresh.length, 1);
+          assert.strictEqual(resp.key_refresh[0], 'lloyd@somewhe.re');
+        }
+      },
+      "handles server unknown emails": {
+        topic: function() {
+          db.getSyncResponse('lloyd@nowhe.re',
+                             {
+                               'lloyd@nowhe.re': 'fakepubkey',
+                               'lloyd@somewhe.re': 'fakepubkey4',
+                               'lloyd@anywhe.re': 'nofakepubkey',
+                             },
+                             this.callback);
+        },
+        "by returning them in the unknown_emails list": function (err, resp) {
+          assert.isUndefined(err);
+          assert.isArray(resp.unknown_emails);
+          assert.strictEqual(resp.unknown_emails.length, 1);
+          assert.strictEqual(resp.unknown_emails[0], 'lloyd@anywhe.re');
+          assert.isArray(resp.key_refresh);
+          assert.strictEqual(resp.key_refresh.length, 0);
+        }
+      },
+      "handles server unknown keys": {
+        topic: function() {
+          db.getSyncResponse('lloyd@nowhe.re',
+                             {
+                               'lloyd@nowhe.re': 'fakepubkeyINVALID',
+                               'lloyd@somewhe.re': 'fakepubkey4'
+                             },
+                             this.callback);
+        },
+        "by returning them in the key_refresh list": function (err, resp) {
+          assert.isUndefined(err);
+          assert.isArray(resp.unknown_emails);
+          assert.strictEqual(resp.unknown_emails.length, 0);
+          assert.isArray(resp.key_refresh);
+          assert.strictEqual(resp.key_refresh.length, 1);
+          assert.strictEqual(resp.key_refresh[0], 'lloyd@nowhe.re');
+        }
+      },
+      "handle more than one case at a time": {
+        topic: function() {
+          db.getSyncResponse('lloyd@nowhe.re',
+                             {
+                               'lloyd@somewhe.re': 'fakepubkeyINVALID',
+                               'lloyd@anywhe.re': 'notreally'
+                             },
+                             this.callback);
+        },
+        "when everything is outta sync": function (err, resp) {
+          assert.isUndefined(err);
+          assert.isArray(resp.unknown_emails);
+          assert.strictEqual(resp.unknown_emails.length, 1);
+          assert.strictEqual(resp.unknown_emails[0], 'lloyd@anywhe.re');
+
+          assert.isArray(resp.key_refresh);
+          assert.strictEqual(resp.key_refresh.length, 2);
+          assert.strictEqual(resp.key_refresh[0], 'lloyd@nowhe.re');
+          assert.strictEqual(resp.key_refresh[1], 'lloyd@somewhe.re');
+        }
       }
     }
-  }
-});
+  });
 
-suite.addBatch({
-  "canceling an account": {
-    topic: function() {
-      db.cancelAccount("lloyd@somewhe.re", this.callback);
-    },
-    "returns no error": function(r) {
-      assert.isUndefined(r);
-    },
-    "causes emailKnown": {
+  suite.addBatch({
+    "removing an existing email": {
       topic: function() {
-        db.emailKnown('lloyd@somewhe.re', this.callback);
+        db.removeEmail("lloyd@somewhe.re", "lloyd@nowhe.re", this.callback);
       },
-      "to return false": function (r) {
-        assert.strictEqual(r, false);
+      "returns no error": function(r) {
+        assert.isUndefined(r);
+      },
+      "causes emailKnown": {
+        topic: function() {
+          db.emailKnown('lloyd@nowhe.re', this.callback);
+        },
+        "to return false": function (r) {
+          assert.strictEqual(r, false);
+        }
       }
     }
-  }
-});
+  });
 
-// exports.cancelAccount
-// exports.removeEmail
+  suite.addBatch({
+    "canceling an account": {
+      topic: function() {
+        db.cancelAccount("lloyd@somewhe.re", this.callback);
+      },
+      "returns no error": function(r) {
+        assert.isUndefined(r);
+      },
+      "causes emailKnown": {
+        topic: function() {
+          db.emailKnown('lloyd@somewhe.re', this.callback);
+        },
+        "to return false": function (r) {
+          assert.strictEqual(r, false);
+        }
+      }
+    }
+  });
 
-suite.addBatch({
-  "remove the database file": {
-    topic: function() {
-      fs.unlink(db.dbPath, this.callback);
-    },
-    "and unlink should not error": function(err) {
-      assert.isNull(err);
-    },
-    "and the file": {
+  suite.addBatch({
+    "closing the database": {
       topic: function() {
-        path.exists(db.dbPath, this.callback);
+        db.close(this.callback);
       },
-      "should be missing": function(r) {
-        assert.isFalse(r);
+      "should work": function(err) {
+        assert.isUndefined(err);
       }
     }
+  });
+
+  if (driver !== 'mysql') {
+    suite.addBatch({
+      "remove the database file": {
+        topic: function() {
+          fs.unlink(dbPath, this.callback);
+        },
+        "and unlink should not error": function(err) {
+          assert.isNull(err);
+        },
+        "and the file": {
+          topic: function() {
+            path.exists(dbPath, this.callback);
+          },
+          "should be missing": function(r) {
+            assert.isFalse(r);
+          }
+        }
+      }
+    });
+  }
+}
+
+// test all available drivers
+files = fs.readdirSync(path.join(__dirname, "..", "lib"));
+
+files.forEach(function(f) {
+  var m = /^db_(.+)\.js$/.exec(f);
+  if (m) {
+    addTestsForDriver(m[1]);
   }
 });
 
 // run or export the suite.
 if (process.argv[1] === __filename) suite.run();
 else suite.export(module);
+
diff --git a/browserid/tests/forgotten-email-test.js b/browserid/tests/forgotten-email-test.js
index 1a7d015476b3ec4076256c9fe4f3ace339a597f5..0c67fc92b39d3021a747ed5d9f768bbff77946bf 100755
--- a/browserid/tests/forgotten-email-test.js
+++ b/browserid/tests/forgotten-email-test.js
@@ -1,13 +1,51 @@
 #!/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 ***** */
+
 const assert = require('assert'),
-      vows = require('vows'),
-      start_stop = require('./lib/start-stop.js'),
-      wsapi = require('./lib/wsapi.js'),
-      interceptor = require('./lib/email-interceptor.js');
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js'),
+interceptor = require('./lib/email-interceptor.js');
 
 var suite = vows.describe('forgotten-email');
 
+// disable vows (often flakey?) async error behavior
+suite.options.error = false;
+
 start_stop.addStartupBatches(suite);
 
 // ever time a new token is sent out, let's update the global
diff --git a/browserid/tests/lib/email-interceptor.js b/browserid/tests/lib/email-interceptor.js
index 3ac9c7212ae35f6b054f5ff25ba96abc33b3b8c9..ac255fbb12455dce2a3e35dc7d76cb31f5680d58 100644
--- a/browserid/tests/lib/email-interceptor.js
+++ b/browserid/tests/lib/email-interceptor.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 // a tiny abstraction which kludges its way into nodemailer to intercept
 // outbound emails for testing
 
diff --git a/browserid/tests/lib/start-stop.js b/browserid/tests/lib/start-stop.js
index 33892996f398707fa5c374a7bccd93ded08ac0fe..636c042a4c8efb27542813eeb1b26691ea6dfcf8 100644
--- a/browserid/tests/lib/start-stop.js
+++ b/browserid/tests/lib/start-stop.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 const assert = require('assert'),
       fs = require('fs'),
       path = require('path'),
diff --git a/browserid/tests/lib/wsapi.js b/browserid/tests/lib/wsapi.js
index dc3a9c93c2cdc720ddab5f156984b66af2f0fb15..cd2f08e683093e4b1a49b60c3812e069f009071d 100644
--- a/browserid/tests/lib/wsapi.js
+++ b/browserid/tests/lib/wsapi.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 const
 http = require('http'),
 querystring = require('querystring');
diff --git a/browserid/tests/registration-status-wsapi-test.js b/browserid/tests/registration-status-wsapi-test.js
index 1d45c2f8d2a77985af296894640123c414d8fe4f..da482e9184483981b7fdf9d11e7be6e45e2da287 100755
--- a/browserid/tests/registration-status-wsapi-test.js
+++ b/browserid/tests/registration-status-wsapi-test.js
@@ -1,5 +1,40 @@
 #!/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 ***** */
+
 const assert = require('assert'),
       vows = require('vows'),
       start_stop = require('./lib/start-stop.js'),
diff --git a/browserid/views/users.ejs b/browserid/views/users.ejs
index dc0a4f6d09346cdf1446f0873d91beefe986b9c7..97b25729dd23968bffd40153b97c3f1000f57e48 100644
--- a/browserid/views/users.ejs
+++ b/browserid/views/users.ejs
@@ -3,6 +3,6 @@
       As a user of BrowserID, you confirm your email addresses once. Then, you can sign into any web site that supports BrowserID with just two clicks.
       </p>
 
-      <center><iframe width="480" height="390" src="http://www.youtube.com/embed/l0t9yDLAmFo" frameborder="0" allowfullscreen></iframe></center>
+      <center><iframe width="480" height="390" src="https://www.youtube.com/embed/l0t9yDLAmFo" frameborder="0" allowfullscreen></iframe></center>
       
     </div>
diff --git a/libs/configuration.js b/libs/configuration.js
index b3cfa84f70ec3f3773e7f0e9503d555ae30db65d..ecddccb7be0aa2f3e4767d3267531b8eed0c0fc4 100644
--- a/libs/configuration.js
+++ b/libs/configuration.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 /*
  * An abstraction which contains various pre-set deployment
  * environments and adjusts runtime configuration appropriate for
@@ -7,7 +42,9 @@
  *   exports.configure(app);
  */
 
-const substitution = require('./substitute.js');
+const
+substitution = require('./substitute.js'),
+path = require('path');
 
 var g_config = {
 };
@@ -19,31 +56,45 @@ exports.get = function(val) {
   return g_config[val];
 }
 
+var defaultHostedDatabaseConfig = {
+  driver: "mysql",
+  user: 'browserid',
+  password: 'browserid'
+};
+
 // various deployment configurations
 const g_configs = {
   production: {
     hostname: 'browserid.org',
     port: '443',
     scheme: 'https',
-    use_minified_resources: true
+    use_minified_resources: true,
+    log_path: '/home/browserid/var/',
+    database: defaultHostedDatabaseConfig
   },
   development: {
     hostname: 'dev.diresworb.org',
     port: '443',
     scheme: 'https',
-    use_minified_resources: true
+    use_minified_resources: true,
+    log_path: '/home/browserid/var/',
+    database: defaultHostedDatabaseConfig
   },
   beta: {
     hostname: 'diresworb.org',
     port: '443',
     scheme: 'https',
-    use_minified_resources: true
+    use_minified_resources: true,
+    log_path: '/home/browserid/var/',
+    database: defaultHostedDatabaseConfig
   },
   local: {
     hostname: '127.0.0.1',
     port: '10002',
     scheme: 'http',
-    use_minified_resources: false
+    use_minified_resources: false,
+    log_path: path.join(__dirname, "..", "var", "logs"),
+    database: { driver: "json" }
   }
 };
 
diff --git a/libs/logging.js b/libs/logging.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d4c3422d27fe84732274aad15652f1d75025a4f
--- /dev/null
+++ b/libs/logging.js
@@ -0,0 +1,101 @@
+/* ***** 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 ***** */
+
+const
+winston = require("winston"),
+configuration = require("./configuration"),
+path = require('path'),
+fs = require('fs');
+
+// go through the configuration and determine log location
+// for now we only log to one place
+// FIXME: separate logs depending on purpose?
+
+var log_path = configuration.get('log_path');
+var LOGGERS = [];
+
+// simple inline function for creation of dirs
+function mkdir_p(p) {
+  if (!path.existsSync(p)) {
+    mkdir_p(path.dirname(p));
+    console.log("mkdir", p);
+    fs.mkdirSync(p, "0755");
+  }
+}
+
+function setupLogger(category) {
+  if (!log_path)
+    return console.log("no log path! Not logging!");
+  else
+    mkdir_p(log_path);
+
+
+  // don't create the logger if it already exists
+  if (LOGGERS[category])
+    return;
+
+  var filename = path.join(log_path, category + "-log.txt");
+
+  LOGGERS[category] = new (winston.Logger)({
+      transports: [new (winston.transports.File)({filename: filename})]
+    });
+}
+
+// entry is an object that will get JSON'ified
+exports.log = function(category, entry) {
+  // entry must have at least a type
+  if (!entry.type)
+    throw new Error("every log entry needs a type");
+
+  // setup the logger if need be
+  setupLogger(category);
+
+  // timestamp
+  entry.at = new Date().toUTCString();
+
+  // if no logger, go to console (FIXME: do we really want to log to console?)
+  LOGGERS[category].info(JSON.stringify(entry));
+};
+
+// utility function to log a bunch of stuff at user entry point
+exports.userEntry = function(category, req) {
+  exports.log(category, {
+      type: 'signin',
+      browser: req.headers['user-agent'],
+      rp: req.headers['referer'],
+      // IP address (this probably needs to be replaced with the X-forwarded-for value
+      ip: req.connection.remoteAddress
+    });
+};
\ No newline at end of file
diff --git a/libs/substitute.js b/libs/substitute.js
index ab04aeec1e4f2ff6a1bb9912b044435cb9583dd2..2c4c526aedbbaac25231c855ebddd27fcd26287b 100644
--- a/libs/substitute.js
+++ b/libs/substitute.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 // return a function that is substitution middleware, capable
 // of being installed to perform textual replacement on
 // all server output
diff --git a/package.json b/package.json
index 78fdad42fca3a034200031b76e68b1a78cbccad4..4691170022a0f6cfdb923871eabe62af751055ca 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,6 @@
   , "dependencies": {
       "express": "2.4.3"
     , "xml2js": "0.1.5"
-    , "sqlite": "1.0.3"
     , "nodemailer": "0.1.18"
     , "mustache": "0.3.1-dev"
     , "cookie-sessions": "0.0.2"
@@ -15,5 +14,9 @@
     , "temp": "0.2.0"
     , "express-csrf": "0.3.2"
     , "uglify-js": "1.0.6"
+    , "JSONSelect": "0.2.1"
+    , "winston" : "0.3.3"
+    , "connect-cookie-session" : "0.0.1"
+    , "mysql" : "0.9.1"
   }
-}
\ No newline at end of file
+}
diff --git a/run.js b/run.js
index c9e78c4050682ad9cbd1e32674099f9a77810e0a..d710da851074ff0fb3c2d308f8889e182520fe81 100755
--- a/run.js
+++ b/run.js
@@ -1,5 +1,40 @@
 #!/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 ***** */
+
 // a little node webserver designed to run the unit tests herein
 
 var      sys = require("sys"),
diff --git a/scripts/branch_train.sh b/scripts/branch_train.sh
new file mode 100755
index 0000000000000000000000000000000000000000..fe73cf673fa3ea34fc8764f80add229ef1528fcb
--- /dev/null
+++ b/scripts/branch_train.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+git branch train-$(date +'%Y.%m.%d') dev
+
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a0ef1563a8e8f5556edc3d85fce2c29c7936278f
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+VOWS=`which vows 2> /dev/null`
+if [ ! -x "$VOWS" ]; then
+    echo "vows not found in your path.  try:  npm install -g vows"
+    exit 1
+fi
+
+for file in browserid/tests/*.js ; do
+    vows $file
+    if [[ $? != 0 ]] ; then
+        exit 1
+    fi
+done
diff --git a/verifier/app.js b/verifier/app.js
index 2503a31be9d9a96453519c08f6a5a04623e69773..9b8f8e1989ba94e9bbb2187d89e67875bb028d87 100644
--- a/verifier/app.js
+++ b/verifier/app.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 const   path = require('path'),
          url = require('url'),
           fs = require('fs'),
@@ -5,6 +40,7 @@ const   path = require('path'),
  idassertion = require('./lib/idassertion.js'),
          jwt = require('./lib/jwt.js'),
      express = require('express');
+     logging = require('../libs/logging.js');
 
 // create the var directory if it doesn't exist
 var VAR_DIR = path.join(__dirname, "var");
@@ -35,6 +71,13 @@ function doVerify(req, resp, next) {
       .verify(
         audience,
         function(payload) {
+          // log it!
+          logging.log('verifier', {
+              type: 'verify',
+                result: 'success',
+                rp: payload.audience
+            });
+          
           result = {
             status : "okay",
             email : payload.email,
@@ -45,11 +88,21 @@ function doVerify(req, resp, next) {
           resp.json(result);
         },
         function(errorObj) {
+          logging.log('verifier', {
+              type: 'verify',
+                result: 'failure',
+                rp: audience
+            });
           resp.json({ status: "failure", reason: errorObj });
         }
       );
   } catch (e) {
     console.log(e.stack);
+    logging.log('verifier', {
+        type: 'verify',
+          result: 'failure',
+          rp: audience
+          });
     resp.json({ status: "failure", reason: e.toString() });
   }
 }
diff --git a/verifier/lib/httputils.js b/verifier/lib/httputils.js
index ef7abf3130a2dcb24e9cf0058a23c1c3a125e43c..aad0bd98983f83731ed9326b2cd23649c6452c6f 100644
--- a/verifier/lib/httputils.js
+++ b/verifier/lib/httputils.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 // various little utilities to make crafting boilerplate responses
 // simple
 
diff --git a/verifier/lib/idassertion.js b/verifier/lib/idassertion.js
index 4845f9805c59eabb1ced138cb65d64fd52061aae..0893488a9d55f99c64f14a438aa32e04aae075dd 100644
--- a/verifier/lib/idassertion.js
+++ b/verifier/lib/idassertion.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 /*
 * bug garage:
 *
diff --git a/verifier/lib/jwt.js b/verifier/lib/jwt.js
index fdf96f8c54196cb77a26316d833013d3eed2d512..a70d68ec9bc2b77662fe13fd52189d43ebc17059 100644
--- a/verifier/lib/jwt.js
+++ b/verifier/lib/jwt.js
@@ -1,3 +1,38 @@
+/* ***** 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 ***** */
+
 const crypto = require("crypto");
 const rsa = require("./rsa.js");
 
diff --git a/verifier/lib/make_assertion.js b/verifier/lib/make_assertion.js
index 54afcdd70069fd4ab2424e7fb0be4dcf508a7a30..38374a5be99d5295471f692f54117ac79cc0d1b7 100644
--- a/verifier/lib/make_assertion.js
+++ b/verifier/lib/make_assertion.js
@@ -1,5 +1,40 @@
 #!/usr/local/bin/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 ***** */
+
 const fs = require("fs");
 const jwt = require("./jwt.js");
 const idassertion = require("./idassertion.js");
diff --git a/verifier/run.js b/verifier/run.js
index 34adb7f8e4bbd9b2765a0ed395c545166a883b30..1ab4c936e1273f6f92016975af1f1283eff166ce 100755
--- a/verifier/run.js
+++ b/verifier/run.js
@@ -1,5 +1,40 @@
 #!/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 ***** */
+
 var   sys = require("sys"),
      path = require("path"),
        fs = require("fs"),
diff --git a/verifier/tests/run.js b/verifier/tests/run.js
index c7c457c5c52b87f9340ac049ddd54b035a053ade..8e6d85ab85c31406b4310e0edabfa5a92960c66b 100755
--- a/verifier/tests/run.js
+++ b/verifier/tests/run.js
@@ -1,5 +1,40 @@
 #!/usr/local/bin/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 ***** */
+
 const jwt = require("../lib/jwt.js");
 const idassertion = require("../lib/idassertion.js");
 const vows = require('vows');