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/dialog_controller.js b/browserid/static/dialog/controllers/dialog_controller.js
index 6fdb1477aa9bb6a8aaae06a01aae1145f7411458..995b16440e8b78d47f3fbead47e12bcd59fb5219 100644
--- a/browserid/static/dialog/controllers/dialog_controller.js
+++ b/browserid/static/dialog/controllers/dialog_controller.js
@@ -1,5 +1,40 @@
 /*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, runErrorDialog: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
 //
@@ -144,7 +179,7 @@ PageController.extend("Dialog", {}, {
         // email, keypair, and that fact
         self.persistAddressAndKeyPair(self.confirmEmail, 
           self.confirmKeypair, "browserid.org:443");
-        self.syncidentities();
+        self.syncIdentities();
 
     },
 
diff --git a/browserid/static/dialog/dialog.js b/browserid/static/dialog/dialog.js
index 5ac3581f1af28ba9f7bad62e1ff2358b5b8fe189..ac0019d157f8092d7aec3ac45cbc1d80780b8c13 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(
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/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/views/body.ejs b/browserid/static/dialog/views/body.ejs
index 7b17ff10777bde550932a82b8babc700b22e9474..fe7538a603fec23471ede2b51ef7ec184a14817b 100644
--- a/browserid/static/dialog/views/body.ejs
+++ b/browserid/static/dialog/views/body.ejs
@@ -15,6 +15,6 @@
 </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/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');