diff --git a/lib/browserid/views.js b/lib/browserid/views.js
index 53fcf01427448db8ed61dd22d00e6de3f6f796f5..b26361e2c6a04ff4e381cafe12b62c80132764b1 100644
--- a/lib/browserid/views.js
+++ b/lib/browserid/views.js
@@ -41,6 +41,7 @@ function renderCachableView(req, res, template, options) {
 
   options.enable_development_menu = config.get('enable_development_menu');
 
+  res.local('util', util);
   res.render(template, options);
 }
 
@@ -138,11 +139,10 @@ exports.setup = function(app) {
   });
 
   app.get("/forgot", function(req, res) {
-    // !cachable!  email embedded in DOM
-    res.render('forgot.ejs', {
+    res.local('util', util);
+    renderCachableView(req, res, 'forgot.ejs', {
       title: 'Forgot Password',
       fullpage: false,
-      email: req.query.email,
       enable_development_menu: config.get('enable_development_menu')
     });
   });
@@ -164,11 +164,10 @@ exports.setup = function(app) {
   });
 
   app.get("/verify_email_address", function(req, res) {
-    // !cachable!  token is embedded in DOM
-    res.render('verify_email_address.ejs', {
+    res.local('util', util);
+    renderCachableView(req, res, 'verify_email_address.ejs', {
       title: 'Complete Registration',
       fullpage: true,
-      token: req.query.token,
       enable_development_menu: config.get('enable_development_menu')
     });
   });
diff --git a/lib/load_gen/common.js b/lib/load_gen/common.js
index b02f17aea798a56dea9cb150e55aea79b85781fe..ae64681616cc1b80b85b268d105320dfb4d74048 100644
--- a/lib/load_gen/common.js
+++ b/lib/load_gen/common.js
@@ -47,7 +47,7 @@ exports.authAndKey = function(cfg, user, ctx, email, cb) {
           try {
             if (err) throw err;
             if (resp.code !== 200) throw "non-200 status: " + resp.code +
-              + " - " + resp.body;
+              " - " + resp.body;
             if (typeof resp.body !== 'string') throw cb("no response body");
             userdb.addCertToUserCtx(ctx, email, resp.body);
             cb();
@@ -75,7 +75,9 @@ exports.genAssertionAndVerify = function(cfg, user, ctx, email, audience, cb) {
     // the contents so much
     try {
       if (err) throw err;
-      if (!typeof JSON.parse(r.body) === 'object') throw 'bogus response';
+      if (typeof JSON.parse(r.body) !== 'object') {
+        throw 'response is not a JSON object: ' + r.body;
+      }
     } catch(e) {
       return cb(e.toString() + (r ? (" - " + r.body) : ""));
     }
@@ -98,7 +100,8 @@ exports.genAssertionAndVerify = function(cfg, user, ctx, email, audience, cb) {
         try {
           if (err) throw err;
           if (r.code !== 200) throw "non-200 status: " + r.code;
-          if (!JSON.parse(r.body).status === 'okay') throw "verification failed with: " + r.reason;
+          var body = JSON.parse(r.body);
+          if (body.status !== 'okay') throw "verification failed with: " + body.reason;
           cb(undefined);
         } catch(e) {
           return cb("can't verify: " + e.toString());
diff --git a/lib/load_gen/crypto.js b/lib/load_gen/crypto.js
index cadacd231c2483baee6476de3d16a6cc47bfcf5a..6429f9c52ea492fb598c3e9a02c59fe5c8b0e617 100644
--- a/lib/load_gen/crypto.js
+++ b/lib/load_gen/crypto.js
@@ -59,9 +59,10 @@ exports.getAssertion = function(obj, cb) {
       {
         audience: obj.audience,
         expiresAt: expirationDate
-      }, obj.secretKey, function(err, assertion) {
+      }, obj.secretKey, function(err, signedAssertion) {
         if (err) cb(err);
         else {
+          var assertion = jwcrypto.cert.bundle(obj.cert, signedAssertion);
           cb(null, {
             audience: obj.audience,
             assertion: assertion,
diff --git a/package.json b/package.json
index eecc9a09648dccf0957d9ccd54744eefca6de0ed..c1bf949a8a05aba32cba092a7433f35c5c6f2a2b 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
         "mysql": "0.9.5",
         "node-gettext": "0.1.1",
         "node-statsd": "https://github.com/downloads/lloyd/node-statsd/0509f85.tgz",
-        "nodemailer": "0.1.18",
+        "nodemailer": "0.1.24",
         "mkdirp": "0.3.0",
         "optimist": "0.2.8",
         "postprocess": "0.2.4",
diff --git a/resources/static/css/common.css b/resources/static/css/common.css
index 7c22ed582dbce5ff26fe771c1e3dd25bd0d05927..536e08d287441d726b1be96f44443ccfade38f7a 100644
--- a/resources/static/css/common.css
+++ b/resources/static/css/common.css
@@ -180,6 +180,12 @@ button:focus,
     background-image:          linear-gradient(top, #43a6e2, #277ac1);
 }
 
+button:focus,
+.button:focus {
+    box-shadow: 0 0 1px #fff, 0 0 1px 3px #49ADE3;
+    box-shadow: 0 0 1px rgba(255, 255, 255, 0.5), 0 0 1px 3px rgba(73, 173, 227, 0.6);
+}
+
 button:active,
 .button:active {
     background-color: #184a73;
diff --git a/resources/static/dialog/css/m.css b/resources/static/dialog/css/m.css
index ff2ee544b1ecc1f17c11781afb4dfa69e4e2313b..ac8b2285240c5f6f43d8bce5d1c9547415d87b1c 100644
--- a/resources/static/dialog/css/m.css
+++ b/resources/static/dialog/css/m.css
@@ -2,6 +2,9 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* If the max-width changes, the size given in screen_size_hacks.js must be
+ * updated as well.
+ */
 @media screen and (max-width: 640px) {
 
   header, footer {
diff --git a/resources/static/dialog/resources/screen_size_hacks.js b/resources/static/dialog/resources/screen_size_hacks.js
index c304ca0e4cd3cac9d242d47d07a78f3993e78e30..8cc8871bbf27b819841052c9aeb5f3220c7b5a11 100644
--- a/resources/static/dialog/resources/screen_size_hacks.js
+++ b/resources/static/dialog/resources/screen_size_hacks.js
@@ -12,7 +12,10 @@
         signInEl = $("#signIn");
 
     selectEmailEl.css("position", "static");
-    if($(window).width() >= 640) {
+
+    // The mobile breakpoint is 640px in the CSS.  If the max-width is changed
+    // there, it must be changed here as well.
+    if($(window).width() > 640) {
       // First, remove the mobile hacks
       selectEmailEl.css("width", "");
       contentEl.css("min-height", "");
diff --git a/resources/static/dialog/views/add_email.ejs b/resources/static/dialog/views/add_email.ejs
index 0679af5dec513d1e8bf425473db53572c72479e7..15ebbad134b0779d228dc3f5cbb9102a1193fa2f 100644
--- a/resources/static/dialog/views/add_email.ejs
+++ b/resources/static/dialog/views/add_email.ejs
@@ -8,8 +8,8 @@
 
       <ul class="inputs">
           <li>
-              <label for="newEmail" class="serif"><%= gettext('New email address') %></label>
-              <input id="newEmail" name="newEmail" type="email" class="sans" autocapitalize="off" autocorrect="off" maxlength="254" <% if (typeof email !== "undefined") { %> value="<%= email %>" <% } %>/>
+              <label for="newEmail"><%= gettext('New email address') %></label>
+              <input id="newEmail" name="newEmail" type="email" autocapitalize="off" autocorrect="off" maxlength="254" <% if (typeof email !== "undefined") { %> value="<%= email %>" <% } %>/>
 
               <div id="email_format" class="tooltip" for="newEmail">
                 <%= gettext('This field must be an email address.') %>
diff --git a/resources/static/dialog/views/authenticate.ejs b/resources/static/dialog/views/authenticate.ejs
index 238cba3a5980f81e16d25ecf58aa771e59364c5a..a95c45b441638b50424e8ca14dc1dc21df6fa421 100644
--- a/resources/static/dialog/views/authenticate.ejs
+++ b/resources/static/dialog/views/authenticate.ejs
@@ -7,7 +7,7 @@
 
           <li>
               <label for="email"><%= gettext('Email') %></label>
-              <input id="email" class="sans" type="email" autocapitalize="off" autocorrect="off" value="<%= email %>" maxlength="254" tabindex="1"/>
+              <input id="email" type="email" autocapitalize="off" autocorrect="off" value="<%= email %>" maxlength="254" tabindex="1"/>
 
               <div id="email_format" class="tooltip" for="email">
                 <%= gettext('This field must be an email address.') %>
@@ -40,7 +40,7 @@
               <a id="forgotPassword" class="forgot right" href="#" tabindex="4"><%= gettext('forgot your password?') %></a>
               <label for="password"><%= gettext('Password') %></label>
 
-              <input id="password" class="sans" type="password" maxlength="80" tabindex="2" />
+              <input id="password" type="password" maxlength="80" tabindex="2" />
 
               <div id="password_required" class="tooltip" for="password">
                 <%= gettext('The password field is required.') %>
diff --git a/resources/static/dialog/views/primary_user_verified.ejs b/resources/static/dialog/views/primary_user_verified.ejs
index c765b76eb4f386a6df31b9933c7f8cebff306f59..0b2cc30af00ec7e6e03e7cb15ed8632db2823445 100644
--- a/resources/static/dialog/views/primary_user_verified.ejs
+++ b/resources/static/dialog/views/primary_user_verified.ejs
@@ -7,8 +7,8 @@
       <ul class="inputs">
 
           <li>
-              <label for="email" class="serif"><%= gettext("Email") %></label>
-              <input id="email" class="sans" type="email" disabled value="<%= email %>" />
+              <label for="email"><%= gettext("Email") %></label>
+              <input id="email" type="email" disabled value="<%= email %>" />
           </li>
 
           <li>
diff --git a/resources/static/dialog/views/required_email.ejs b/resources/static/dialog/views/required_email.ejs
index e2b362b5d6022c8c510524a14e7249228041feaa..5245319c2e48ee6849561757f1a8d9b7b9e7ef83 100644
--- a/resources/static/dialog/views/required_email.ejs
+++ b/resources/static/dialog/views/required_email.ejs
@@ -13,7 +13,7 @@
       <ul class="inputs">
 
           <li>
-              <label for="email" ><%= gettext("Email") %></label>
+              <label for="email"><%= gettext("Email") %></label>
               <input id="required_email" type="email" value="<%= email %>" disabled />
               <div id="could_not_add" class="tooltip" for="required_email">
                 <%= gettext("We just sent an email to that address! If you really want to send another, wait a minute or two and try again.") %>
@@ -32,7 +32,7 @@
           <% if (password) { %>
               <li id="password_section">
                   <a id="forgotPassword" class="right forgot" href="#" tabindex="4"><%= gettext("forgot your password?") %></a>
-                  <label for="password" ><%= gettext("Password") %></label>
+                  <label for="password"><%= gettext("Password") %></label>
 
                   <input id="password" type="password" maxlength="80" tabindex="2" />
 
diff --git a/resources/static/dialog/views/set_password.ejs b/resources/static/dialog/views/set_password.ejs
index 2d5a3cd48c5cbaae5ca344b1d2b0cc16ae291867..c16a1f2d9fc6378601fde28faeefe159cbb416e5 100644
--- a/resources/static/dialog/views/set_password.ejs
+++ b/resources/static/dialog/views/set_password.ejs
@@ -16,8 +16,8 @@
 
 
           <li>
-              <label for="password" class="serif"><%= gettext('Password') %></label>
-              <input id="password" class="sans" type="password" maxlength="80" />
+              <label for="password"><%= gettext('Password') %></label>
+              <input id="password" type="password" maxlength="80" />
 
               <div class="tooltip" id="password_required" for="password">
                 <%= gettext('Password is required.') %>
@@ -33,8 +33,8 @@
           </li>
 
           <li>
-              <label class="serif" for="vpassword"><%= gettext('Verify Password') %></label>
-              <input class="sans" id="vpassword" placeholder="<%= gettext('Repeat Password') %>" type="password" maxlength=80 />
+              <label for="vpassword"><%= gettext('Verify Password') %></label>
+              <input id="vpassword" placeholder="<%= gettext('Repeat Password') %>" type="password" maxlength=80 />
 
               <div class="tooltip" id="vpassword_required" for="vpassword">
                 <%= gettext('Verification password is required.') %>
@@ -49,7 +49,7 @@
 
       <div class="submit cf">
         <p class="cf">
-          <button tabindex="1" id="<%= password_reset ? "password_reset" : "verify_user" %>">
+          <button id="<%= password_reset ? "password_reset" : "verify_user" %>">
             <%= password_reset ? gettext('reset password') : gettext('verify email') %>
           </button>
           <% if(cancelable) { %>
diff --git a/resources/static/dialog/views/verify_primary_user.ejs b/resources/static/dialog/views/verify_primary_user.ejs
index cad3c7e4ea5652b143a36f96051d8bdb41164fd8..c2d4da92de3cdeb7a34d18a1be6a242a1823f370 100644
--- a/resources/static/dialog/views/verify_primary_user.ejs
+++ b/resources/static/dialog/views/verify_primary_user.ejs
@@ -8,8 +8,8 @@
       <ul class="inputs">
 
           <li>
-              <label for="email" class="serif"><%= gettext('Email') %></label>
-              <input id="required_email" class="sans" type="email" value="<%= email %>" disabled />
+              <label for="email"><%= gettext('Email') %></label>
+              <input id="required_email" type="email" value="<%= email %>" disabled />
               <div id="could_not_add" class="tooltip" for="required_email">
                 <%= gettext("We just sent an email to that address! If you really want to send another, wait a minute or two and try again.") %>
               </div>
diff --git a/resources/static/fonts/LICENSE.txt b/resources/static/fonts/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0b365b09f4a75a94f6e9baa26b7283ce7c873d0d
--- /dev/null
+++ b/resources/static/fonts/LICENSE.txt
@@ -0,0 +1,203 @@
+Fonts obtained from Google's Web Font service at: http://www.google.com/webfonts
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js
index e3b60e5ff29ed5e25d3be57dd2cbeebc1b41328a..f81e73d4356726eac2b5ebb3ba0334d0cb029f04 100644
--- a/resources/static/include_js/include.js
+++ b/resources/static/include_js/include.js
@@ -111,7 +111,7 @@
         }
       }
       if (s_boundChans[origin][scope].length === 0) {
-        delete s_boundChans[origin][scope]
+        delete s_boundChans[origin][scope];
       }
     }
 
@@ -170,9 +170,9 @@
       if (typeof meth === 'string') {
         var delivered = false;
         if (s_boundChans[o] && s_boundChans[o][s]) {
-          for (var i = 0; i < s_boundChans[o][s].length; i++) {
-            if (s_boundChans[o][s][i].win === w) {
-              s_boundChans[o][s][i].handler(o, meth, m);
+          for (var j = 0; j < s_boundChans[o][s].length; j++) {
+            if (s_boundChans[o][s][j].win === w) {
+              s_boundChans[o][s][j].handler(o, meth, m);
               delivered = true;
               break;
             }
@@ -180,9 +180,9 @@
         }
 
         if (!delivered && s_boundChans['*'] && s_boundChans['*'][s]) {
-          for (var i = 0; i < s_boundChans['*'][s].length; i++) {
-            if (s_boundChans['*'][s][i].win === w) {
-              s_boundChans['*'][s][i].handler(o, meth, m);
+          for (var j = 0; j < s_boundChans['*'][s].length; j++) {
+            if (s_boundChans['*'][s][j].win === w) {
+              s_boundChans['*'][s][j].handler(o, meth, m);
               break;
             }
           }
@@ -238,7 +238,7 @@
             try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { }
             console.log("["+chanId+"] " + m);
           }
-        }
+        };
 
         /* browser capabilities check */
         if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage");
@@ -272,7 +272,7 @@
 
         if (typeof cfg.scope !== 'undefined') {
           if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string';
-          if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'"
+          if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'";
         }
 
         /* private variables */
@@ -341,7 +341,7 @@
               return completed;
             }
           };
-        }
+        };
 
         var setTransactionTimeout = function(transId, timeout, method) {
           return window.setTimeout(function() {
@@ -353,7 +353,7 @@
               delete s_transIds[transId];
             }
           }, timeout);
-        }
+        };
 
         var onMessage = function(origin, method, m) {
           // if an observer was specified at allocation time, invoke it
@@ -392,7 +392,7 @@
                       var cbName = path;
                       return function(params) {
                         return trans.invoke(cbName, params);
-                      }
+                      };
                     })();
                   }
                 }
@@ -469,7 +469,7 @@
               // what can we do?  Also, here we'll ignore return values
             }
           }
-        }
+        };
 
         // now register our bound channel for msg routing
         s_addBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage);
@@ -478,7 +478,7 @@
         var scopeMethod = function(m) {
           if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::");
           return m;
-        }
+        };
 
         // a small wrapper around postmessage whose primary function is to handle the
         // case that clients start sending messages before the other end is "ready"
@@ -501,7 +501,7 @@
 
             cfg.window.postMessage(JSON.stringify(msg), cfg.origin);
           }
-        }
+        };
 
         var onReady = function(trans, type) {
           debug('ready msg received');
@@ -683,7 +683,7 @@
       var loc = window.location;
       var frames = window.opener.frames;
       var origin = loc.protocol + '//' + loc.host;
-      for (i = frames.length - 1; i >= 0; i++) {
+      for (var i = frames.length - 1; i >= 0; i++) {
         try {
           if (frames[i].location.href.indexOf(origin) === 0 &&
               frames[i].name === RELAY_FRAME_NAME)
@@ -746,7 +746,7 @@
             iframe.setAttribute('src', opts.relay_url);
             iframe.style.display = "none";
             iframe.setAttribute('name', RELAY_FRAME_NAME);
-            document.body.appendChild(iframe)
+            document.body.appendChild(iframe);
             messageTarget = iframe.contentWindow;
           }
 
@@ -797,7 +797,7 @@
             }
           };
         }
-      }
+      };
     } else {
       return {
         open: function(url, winopts, arg, cb) {
@@ -1147,7 +1147,7 @@
           onlogin: function(assertion) {
             if (callback) {
               callback(assertion);
-              callback = null
+              callback = null;
             }
           },
           onlogout: function() {}
@@ -1175,4 +1175,3 @@
     };
   }
 }());
-
diff --git a/resources/static/lib/jschannel.js b/resources/static/lib/jschannel.js
index c2a5ee41093b37086c9742b8615ff3b8a5756d5c..d821167a51cb0698911afb9ec8179b7adfc1295f 100644
--- a/resources/static/lib/jschannel.js
+++ b/resources/static/lib/jschannel.js
@@ -99,7 +99,7 @@
             }
         }
         if (s_boundChans[origin][scope].length === 0) {
-            delete s_boundChans[origin][scope]
+            delete s_boundChans[origin][scope];
         }
     }
 
@@ -158,9 +158,9 @@
         if (typeof meth === 'string') {
             var delivered = false;
             if (s_boundChans[o] && s_boundChans[o][s]) {
-                for (var i = 0; i < s_boundChans[o][s].length; i++) {
-                    if (s_boundChans[o][s][i].win === w) {
-                        s_boundChans[o][s][i].handler(o, meth, m);
+                for (var j = 0; j < s_boundChans[o][s].length; j++) {
+                    if (s_boundChans[o][s][j].win === w) {
+                        s_boundChans[o][s][j].handler(o, meth, m);
                         delivered = true;
                         break;
                     }
@@ -168,9 +168,9 @@
             }
 
             if (!delivered && s_boundChans['*'] && s_boundChans['*'][s]) {
-                for (var i = 0; i < s_boundChans['*'][s].length; i++) {
-                    if (s_boundChans['*'][s][i].win === w) {
-                        s_boundChans['*'][s][i].handler(o, meth, m);
+                for (var j = 0; j < s_boundChans['*'][s].length; j++) {
+                    if (s_boundChans['*'][s][j].win === w) {
+                        s_boundChans['*'][s][j].handler(o, meth, m);
                         break;
                     }
                 }
@@ -226,7 +226,7 @@
                     try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { }
                     console.log("["+chanId+"] " + m);
                 }
-            }
+            };
 
             /* browser capabilities check */
             if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage");
@@ -260,7 +260,7 @@
 
             if (typeof cfg.scope !== 'undefined') {
                 if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string';
-                if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'"
+                if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'";
             }
 
             /* private variables */
@@ -329,7 +329,7 @@
                         return completed;
                     }
                 };
-            }
+            };
 
             var setTransactionTimeout = function(transId, timeout, method) {
               return window.setTimeout(function() {
@@ -341,8 +341,8 @@
                   delete s_transIds[transId];
                 }
               }, timeout);
-            }
-            
+            };
+
             var onMessage = function(origin, method, m) {
                 // if an observer was specified at allocation time, invoke it
                 if (typeof cfg.gotMessageObserver === 'function') {
@@ -380,7 +380,7 @@
                                         var cbName = path;
                                         return function(params) {
                                             return trans.invoke(cbName, params);
-                                        }
+                                        };
                                     })();
                                 }
                             }
@@ -457,7 +457,7 @@
                         // what can we do?  Also, here we'll ignore return values
                     }
                 }
-            }
+            };
 
             // now register our bound channel for msg routing
             s_addBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage);
@@ -466,7 +466,7 @@
             var scopeMethod = function(m) {
                 if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::");
                 return m;
-            }
+            };
 
             // a small wrapper around postmessage whose primary function is to handle the
             // case that clients start sending messages before the other end is "ready"
@@ -489,7 +489,7 @@
 
                     cfg.window.postMessage(JSON.stringify(msg), cfg.origin);
                 }
-            }
+            };
 
             var onReady = function(trans, type) {
                 debug('ready msg received');
diff --git a/resources/static/lib/winchan.js b/resources/static/lib/winchan.js
index e604cd2bb3d99e9588109389ca99fc4b62112136..7e8188e04bc4f11954f5c39c6eb69b8d8518ca20 100644
--- a/resources/static/lib/winchan.js
+++ b/resources/static/lib/winchan.js
@@ -115,7 +115,7 @@
           iframe.setAttribute('src', opts.relay_url);
           iframe.style.display = "none";
           iframe.setAttribute('name', RELAY_FRAME_NAME);
-          document.body.appendChild(iframe)
+          document.body.appendChild(iframe);
           messageTarget = iframe.contentWindow;
         }
 
@@ -215,7 +215,7 @@
           }
         };
       }
-    }
+    };
   } else {
     return {
       open: function(url, winopts, arg, cb) {
diff --git a/resources/static/provisioning_api.js b/resources/static/provisioning_api.js
index a03bfbfb9f49ae6f5603be8489eaa577b3625389..0398e9cd47e4ccb0e4e7c38fedf7d01b553218b9 100644
--- a/resources/static/provisioning_api.js
+++ b/resources/static/provisioning_api.js
@@ -107,7 +107,7 @@
         }
       }
       if (s_boundChans[origin][scope].length === 0) {
-        delete s_boundChans[origin][scope]
+        delete s_boundChans[origin][scope];
       }
     }
 
@@ -166,9 +166,9 @@
       if (typeof meth === 'string') {
         var delivered = false;
         if (s_boundChans[o] && s_boundChans[o][s]) {
-          for (var i = 0; i < s_boundChans[o][s].length; i++) {
-            if (s_boundChans[o][s][i].win === w) {
-              s_boundChans[o][s][i].handler(o, meth, m);
+          for (var j = 0; j < s_boundChans[o][s].length; j++) {
+            if (s_boundChans[o][s][j].win === w) {
+              s_boundChans[o][s][j].handler(o, meth, m);
               delivered = true;
               break;
             }
@@ -176,9 +176,9 @@
         }
 
         if (!delivered && s_boundChans['*'] && s_boundChans['*'][s]) {
-          for (var i = 0; i < s_boundChans['*'][s].length; i++) {
-            if (s_boundChans['*'][s][i].win === w) {
-              s_boundChans['*'][s][i].handler(o, meth, m);
+          for (var j = 0; j < s_boundChans['*'][s].length; j++) {
+            if (s_boundChans['*'][s][j].win === w) {
+              s_boundChans['*'][s][j].handler(o, meth, m);
               break;
             }
           }
@@ -234,7 +234,7 @@
             try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { }
             console.log("["+chanId+"] " + m);
           }
-        }
+        };
 
         /* browser capabilities check */
         if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage");
@@ -268,7 +268,7 @@
 
         if (typeof cfg.scope !== 'undefined') {
           if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string';
-          if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'"
+          if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'";
         }
 
         /* private variables */
@@ -337,7 +337,7 @@
               return completed;
             }
           };
-        }
+        };
 
         var setTransactionTimeout = function(transId, timeout, method) {
           return window.setTimeout(function() {
@@ -349,8 +349,8 @@
               delete s_transIds[transId];
             }
           }, timeout);
-        }
-        
+        };
+
         var onMessage = function(origin, method, m) {
           // if an observer was specified at allocation time, invoke it
           if (typeof cfg.gotMessageObserver === 'function') {
@@ -388,7 +388,7 @@
                       var cbName = path;
                       return function(params) {
                         return trans.invoke(cbName, params);
-                      }
+                      };
                     })();
                   }
                 }
@@ -465,7 +465,7 @@
               // what can we do?  Also, here we'll ignore return values
             }
           }
-        }
+        };
 
         // now register our bound channel for msg routing
         s_addBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage);
@@ -474,7 +474,7 @@
         var scopeMethod = function(m) {
           if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::");
           return m;
-        }
+        };
 
         // a small wrapper around postmessage whose primary function is to handle the
         // case that clients start sending messages before the other end is "ready"
@@ -497,7 +497,7 @@
 
             cfg.window.postMessage(JSON.stringify(msg), cfg.origin);
           }
-        }
+        };
 
         var onReady = function(trans, type) {
           debug('ready msg received');
diff --git a/tests/cache-header-tests.js b/tests/cache-header-tests.js
index 4713b0d6bbe711c5d1bf9ae556cb15a4d1e0a58a..9649791ba48c1ae17cb9902af3e1822dd77dd40e 100755
--- a/tests/cache-header-tests.js
+++ b/tests/cache-header-tests.js
@@ -125,12 +125,12 @@ suite.addBatch({
   '/authenticate_with_primary': hasProperCacheHeaders('/authenticate_with_primary'),
   '/signup': hasProperCacheHeaders('/signup'),
   '/idp_auth_complete': hasProperCacheHeaders('/idp_auth_complete'),
-//  '/forgot': hasProperCacheHeaders('/forgot'), */
+  '/forgot': hasProperCacheHeaders('/forgot'),
   '/signin': hasProperCacheHeaders('/signin'),
   '/about': hasProperCacheHeaders('/about'),
   '/tos': hasProperCacheHeaders('/tos'),
   '/privacy': hasProperCacheHeaders('/privacy'),
-//  '/verify_email_address': hasProperCacheHeaders('/verify_email_address'), */
+  '/verify_email_address': hasProperCacheHeaders('/verify_email_address'),
   '/add_email_address': hasProperCacheHeaders('/add_email_address'),
 //  '/pk': hasProperCacheHeaders('/pk'),
 //  '/.well-known/browserid': hasProperCacheHeaders('/.well-known/browserid')