diff --git a/bin/browserid b/bin/browserid
index e1db01419e9a46588e9280758af75b5fdf11cfe9..3e88bd93f1a0194bf0c28d5504d7cec0dfa883dd 100755
--- a/bin/browserid
+++ b/bin/browserid
@@ -144,6 +144,12 @@ app.use(function(req, res, next) {
   next();
 });
 
+// if we're not serving minified resources (local dev), then we should add
+// .ejs to the mime table so it's properly substituted.  issue #1875
+if (!config.get('use_minified_resources')) {
+  express.static.mime.types['ejs'] = 'text/html';
+}
+
 app.use(express.static(static_root));
 
 // open the databse
diff --git a/example/primary/provision.html b/example/primary/provision.html
index c8b7cd583c4c38008d9df3181fa2ee3539bbce92..01394cb48057b0a3ac2cdb3863b538686876e991 100644
--- a/example/primary/provision.html
+++ b/example/primary/provision.html
@@ -9,9 +9,6 @@
 <script type="text/javascript" src="/jquery.js"></script>
 <script type="text/javascript">
 
-  // an alias
-  var fail = navigator.id.raiseProvisioningFailure;
-
   // begin provisioning!  This both gives us indicated to browserid that we're
   // a well formed provisioning page and gives us the parameters of the provisioning
   navigator.id.beginProvisioning(function(email, cert_duration) {
@@ -22,7 +19,7 @@
     $.get('/api/whoami')
       .success(function(who) {
         if (user != who) {
-          return fail('user is not authenticated as target user');
+          return navigator.id.raiseProvisioningFailure('user is not authenticated as target user');
         }
 
         // Awesome!  The user is authenticated as who we want to provision.  let's
@@ -44,13 +41,13 @@
               navigator.id.registerCertificate(r.cert);
             },
             error: function(r) {
-              fail("couldn't certify key");
+              navigator.id.raiseProvisioningFailure("couldn't certify key");
             }
           });
         });
       })
       .error(function() {
-        fail('user is not authenticated');
+        navigator.id.raiseProvisioningFailure('user is not authenticated');
       });
   });
 </script>
diff --git a/example/primary/sign_in.html b/example/primary/sign_in.html
index 51a0eaccae58c47b4ffa31f88220cb0a9b2aec60..838080d2fb96fb83dfe87695538437e7269e1d4d 100644
--- a/example/primary/sign_in.html
+++ b/example/primary/sign_in.html
@@ -33,37 +33,30 @@ button { line-height: 20px; }
 </div>
 
 <script type="text/javascript" src="jquery.js"></script>
+<script type="text/javascript" src="https://login.persona.org/authentication_api.js"></script>
 <script type="text/javascript">
 
-function getParameterByName(name)
-{
-  name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
-  var regexS = "[\\?&]" + name + "=([^&#]*)";
-  var regex = new RegExp(regexS);
-  var results = regex.exec(window.location.href);
-  if(results == null)
-    return "";
-  else
-    return decodeURIComponent(results[1].replace(/\+/g, " "));
-}
+var who = null;
 
 $(document).ready(function() {
   try {
-    var who = getParameterByName("email").replace(/@.*$/, "");
-    $('#who').text(who);
+    navigator.id.beginAuthentication(function(email) {
+      who = /^([^@]+)@/.exec(email)[1];
+      $('#who').text(who);
+    });
   } catch(e) {
     alert("uh oh: " + e);
   }
   $("button").click(function(e) {
     $.get('/api/login', { user: who })
       .success(function(r) {
-        window.location = getParameterByName('return_to');
+        navigator.id.completeAuthentication();
       });
   });
 
   $("#cancel").click(function(e) {
     e.preventDefault();
-    window.location = getParameterByName('return_to');
+    navigator.id.raiseAuthenticationFailure('cancel');
   });
 });
 </script>
diff --git a/example/rp/index.html b/example/rp/index.html
index 19ddc0533b343d33ef00de82318cc0f11ddf8655..50f5808a8da6400b14728ef5d541909b156e29a3 100644
--- a/example/rp/index.html
+++ b/example/rp/index.html
@@ -77,9 +77,6 @@ pre {
       <input type="checkbox" id="returnTo">
       <label for="returnTo">Supply returnTo</label><br />
     </li>
-    </li><li>
-      <input type="text" id="requiredEmail" width="80">
-      <label for="requiredEmail">Require a specific email</label><br />
     </li>
   </ul>
     <button class="assertion">Get an assertion</button>
@@ -181,15 +178,6 @@ navigator.id.watch({
 
 $(document).ready(function() {
   $(".specify button.assertion").click(function() {
-    var requiredEmail = $.trim($('#requiredEmail').val());
-    if (!requiredEmail.length) requiredEmail = undefined;
-    if (requiredEmail && requiredEmail.indexOf('@') === -1) {
-      alert('Invalid Email in "Require a specific email" field. Adding @example.com');
-      $('#requiredEmail').val(requiredEmail + '@example.com');
-      $('#requiredEmail').focus();
-      return;
-    }
-
     $(".specify button.assertion").attr('disabled', 'true');
 
     navigator.id.request({
@@ -198,7 +186,6 @@ $(document).ready(function() {
       siteName: $('#siteName').attr('checked') ? "Persona Test Relying Party" : undefined,
       siteLogo: $('#siteLogo').attr('checked') ? "/i/logo.png" : undefined,
       returnTo: $('#returnTo').attr('checked') ? "/postVerificationReturn.html" : undefined,
-      requiredEmail: requiredEmail,
       oncancel: function() {
         loggit("oncancel");
         $(".specify button.assertion").removeAttr('disabled');
diff --git a/lib/configuration.js b/lib/configuration.js
index 0c5b68dce08d4963c117b79ced4bf9663e2d4c09..1d0bd7a30883b49af16ba955e5068e698a53a367 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -108,10 +108,6 @@ var conf = module.exports = convict({
       format: 'string?',
       env: 'DATABASE_NAME'
     },
-    password: {
-      format: 'string?',
-      env: 'MYSQL_PASSWORD'
-    },
     max_query_time_ms: {
       format: 'integer = 5000',
       doc: "The maximum amount of time we'll allow a query to run before considering the database to be sick",
diff --git a/lib/static_resources.js b/lib/static_resources.js
index 7a6863eab6dc14be472780fe7f44b08d8859fd97..7b14d260da15c063e0260419c81c2420b4c03eb8 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -131,6 +131,9 @@ exports.resources = {
     '/common/css/ie8.css',
     '/dialog/css/ie8.css'
   ],
+  '/production/html5shim.js': [
+    '/common/js/lib/html5shim.js'
+  ],
   '/production/communication_iframe.js': [
     '/common/js/lib/jschannel.js',
     '/common/js/lib/winchan.js',
diff --git a/package.json b/package.json
index c1bf949a8a05aba32cba092a7433f35c5c6f2a2b..24a3bdf1e613fdced528e5c488f7be92f699ae34 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
         "express": "2.5.0",
         "iconv": "1.1.3",
         "mustache": "0.3.1-dev",
-        "jwcrypto": "0.2.2",
+        "jwcrypto": "0.3.2",
         "mysql": "0.9.5",
         "node-gettext": "0.1.1",
         "node-statsd": "https://github.com/downloads/lloyd/node-statsd/0509f85.tgz",
diff --git a/resources/static/authentication_api.js b/resources/static/authentication_api.js
index 5bc44cd704cabf66c811e6933da34cc720c4bc3a..ca9b16c49990977dac2d828dacfdf2a9ea669571 100644
--- a/resources/static/authentication_api.js
+++ b/resources/static/authentication_api.js
@@ -23,7 +23,6 @@
       return decodeURIComponent(results[1].replace(/\+/g, " "));
   }
 
-
   if (!navigator.id.beginAuthentication || navigator.id._primaryAPIIsShimmed) {
     navigator.id.beginAuthentication = function(cb) {
       if (typeof cb !== 'function') {
@@ -34,11 +33,11 @@
     };
 
     navigator.id.completeAuthentication = function(cb) {
-      window.location = getParameterByName('return_to');
+      window.location = 'https://login.persona.org/sign_in#AUTH_RETURN';
     };
 
     navigator.id.raiseAuthenticationFailure = function(reason) {
-      window.location = getParameterByName('return_to');
+      window.location = 'https://login.persona.org/sign_in#AUTH_RETURN_CANCEL';
     };
 
     navigator.id._primaryAPIIsShimmed = true;
diff --git a/resources/static/common/css/style.css b/resources/static/common/css/style.css
index c29a95bffb2b1b82c387529fbe41b5b656a71e2f..dfd8cd6d3bfbeb86b3bf7b3814a612276db5b020 100644
--- a/resources/static/common/css/style.css
+++ b/resources/static/common/css/style.css
@@ -415,6 +415,20 @@ footer .help {
   color: #333;
 }
 
+.submit {
+    margin-top: 10px;
+    line-height: 14px;
+}
+
+.submit > p {
+  margin-top: 10px;
+}
+
+.tospp {
+  line-height: 1.2;
+}
+
+
 #showDevelopment {
   position: absolute;
   top: 0;
diff --git a/resources/static/common/js/lib/dom-jquery.js b/resources/static/common/js/lib/dom-jquery.js
index 6438fec068304658384f58d1e86e2c0c1c309dcb..0faeb1ccc0db906754e3461716bbfd2b8c3bd1dc 100644
--- a/resources/static/common/js/lib/dom-jquery.js
+++ b/resources/static/common/js/lib/dom-jquery.js
@@ -313,7 +313,7 @@ BrowserID.DOM = ( function() {
         },
 
         /**
-         * Show an element/elements
+         * Show an element
          * @method show
          * @param {selector || element} elementToShow
          */
@@ -322,15 +322,13 @@ BrowserID.DOM = ( function() {
         },
 
         /**
-         * Hide an element/elements
+         * Hide an element
          * @method hide
          * @param {selector || element} elementToHide
          */
         hide: function( elementToHide ) {
           return jQuery( elementToHide ).hide();
         }
-
-
     };
 
     function isValBased( target ) {
diff --git a/resources/static/common/js/lib/micrajax.js b/resources/static/common/js/lib/micrajax.js
index f56dbcec347c3ffc1edbbdae17a4e438f9e23951..1bf9522b13ce4025c33f926d76f4fae938403cba 100644
--- a/resources/static/common/js/lib/micrajax.js
+++ b/resources/static/common/js/lib/micrajax.js
@@ -1,4 +1,4 @@
-/*jshint browsers:true, forin: true, laxbreak: true */
+/*jshint browser:true, forin: true, laxbreak: true */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
@@ -17,12 +17,16 @@ window.Micrajax = (function() {
   function getXHRObject() {
     var xhrObject;
 
-    if (window.ActiveXObject) {
-      xhrObject = new ActiveXObject('Microsoft.XMLHTTP');
-    }
-    else if (window.XMLHttpRequest) {
+    // From  http://blogs.msdn.com/b/ie/archive/2011/08/31/browsing-without-plug-ins.aspx
+    // Best Practice: Use Native XHR, if available
+    if (window.XMLHttpRequest) {
+      // If IE7+, Gecko, WebKit: Use native object
       xhrObject = new XMLHttpRequest();
     }
+    else if (window.ActiveXObject) {
+      // ...if not, try the ActiveX control
+      xhrObject = new ActiveXObject('Microsoft.XM/LHTTP');
+    }
 
     return xhrObject;
   }
diff --git a/resources/static/common/js/modules/page_module.js b/resources/static/common/js/modules/page_module.js
index 36aa78c7c29a72aae98d2d6552b08100467c54b2..2afa4634e9e26725e67933e5c2ab0cd20148162e 100644
--- a/resources/static/common/js/modules/page_module.js
+++ b/resources/static/common/js/modules/page_module.js
@@ -119,6 +119,8 @@ BrowserID.Modules.PageModule = (function() {
       self.hideError();
       self.hideDelay();
 
+      dom.removeClass("body", "rptospp");
+
       screens.form.show(template, data);
       dom.focus("input:visible:not(:disabled):eq(0)");
       // XXX jQuery.  bleck.
diff --git a/resources/static/common/js/network.js b/resources/static/common/js/network.js
index 752ce427df57ff8903fea6a742ccd7b795b97703..d423f12d32fa4ee7694d5730643c95e17bf64b68 100644
--- a/resources/static/common/js/network.js
+++ b/resources/static/common/js/network.js
@@ -6,7 +6,8 @@
 BrowserID.Network = (function() {
   "use strict";
 
-  var bid = BrowserID,
+  var jwcrypto = require("./lib/jwcrypto"),
+      bid = BrowserID,
       complete = bid.Helpers.complete,
       context,
       server_time,
@@ -45,10 +46,7 @@ BrowserID.Network = (function() {
     setUserID(result.userid);
 
     // seed the PRNG
-    // FIXME: properly abstract this out, probably by exposing a jwcrypto
-    // interface for randomness
-    // require("./libs/all").sjcl.random.addEntropy(result.random_seed);
-    // FIXME: this wasn't doing anything for real, so commenting this out for now
+    jwcrypto.addEntropy(result.random_seed);
   }
 
   function withContext(cb, onFailure) {
diff --git a/resources/static/common/js/user.js b/resources/static/common/js/user.js
index f39d3935d9bdae5f9bcdb3cf966f1212769c2318..95b7c0893c34fd7d36fab3942fa6bc589a6ab36f 100644
--- a/resources/static/common/js/user.js
+++ b/resources/static/common/js/user.js
@@ -264,6 +264,14 @@ BrowserID.User = (function() {
       return origin;
     },
 
+    setOriginEmail: function(email) {
+      storage.site.set(origin, "email", email);
+    },
+
+    getOriginEmail: function() {
+      return storage.site.get(origin, "email");
+    },
+
     /**
      * Get the hostname for the set origin
      * @method getHostname
diff --git a/resources/static/dialog/css/style.css b/resources/static/dialog/css/style.css
index e6b73ab45f00bf649718671635dc162ffb108947..3aa1e6dd335193524e1d94460afd5daaa9cf9488 100644
--- a/resources/static/dialog/css/style.css
+++ b/resources/static/dialog/css/style.css
@@ -284,24 +284,25 @@ section > .contents {
 #favicon img {
     display: block;
     margin: 0 auto;
-    max-height: 128px;
-    max-width: 128px;
+    max-height: 100px;
+    max-width: 100px;
 }
 
 #favicon h2, #favicon h3 {
     white-space: nowrap;
     text-overflow: ellipsis;
-    line-height: 1.3;  /* the 1.3em is to keep y, g, j, etc from having their bottoms chopped off */
-    overflow: hidden;
+    line-height: 1.3em;  /* the 1.3em is to keep y, g, j, etc from having their bottoms chopped off */
+    overflow: hidden;    /* Use the same overflow for everything, this allows us to show the ellipsis.  Trying to set overflow-x only causes a scroll bar to be shown in firefox.  */
+    margin: 10px 0 0 0;
 }
 
 #favicon h2 {
     margin: 10px 0 0 0;
-    font-size: 18px;
+    font-size: 20px;
 }
 
 #favicon h3 {
-    font-size: 14px;
+    font-size: 16px;
     margin-top: 0;
 }
 
@@ -380,19 +381,24 @@ div#required_email {
 }
 
 .submit {
-    margin-top: 10px;
     color: #333;
     font-size: 11px;
-    line-height: 14px;
 }
 
-.submit > p {
-  margin-top: 10px;
-}
 
 .tospp {
-    color: #787878;
-    clear: right;
+  font-size: 11px;
+  color: #787878;
+}
+
+#rptospp {
+  display: none;
+  margin: 10px auto;
+  max-width: 280px;
+}
+
+.rptospp #rptospp {
+  display: block;
 }
 
 a.emphasize {
@@ -431,6 +437,7 @@ a.emphasize:active {
 #back {
   color: #000;
   border-bottom: 1px dotted;
+  font-weight: normal;
 }
 
 .submit > button {
diff --git a/resources/static/dialog/js/misc/helpers.js b/resources/static/dialog/js/misc/helpers.js
index af595a5338e5dba843f45572329d1c648496daf7..23831652fef2a4e8c5807a1f89b2d7b354faf4da 100644
--- a/resources/static/dialog/js/misc/helpers.js
+++ b/resources/static/dialog/js/misc/helpers.js
@@ -11,7 +11,8 @@
       complete = helpers.complete,
       user = bid.User,
       tooltip = bid.Tooltip,
-      errors = bid.Errors;
+      errors = bid.Errors,
+      dom = bid.DOM;
 
   function animateClose(callback) {
     var body = $("body"),
@@ -135,6 +136,10 @@
     }, self.getErrorDialog(errors.addEmail, callback));
   }
 
+  function showRPTosPP() {
+    dom.addClass("body", "rptospp");
+  }
+
   helpers.Dialog = helpers.Dialog || {};
 
   helpers.extend(helpers.Dialog, {
@@ -145,7 +150,8 @@
     addSecondaryEmail: addSecondaryEmail,
     resetPassword: resetPassword,
     cancelEvent: helpers.cancelEvent,
-    animateClose: animateClose
+    animateClose: animateClose,
+    showRPTosPP: showRPTosPP
   });
 
 }());
diff --git a/resources/static/dialog/js/misc/state.js b/resources/static/dialog/js/misc/state.js
index 032bd85f683c1f9a9698fcecf87af9a9ad2c5af5..dc228b145d1713516c67e615e9d8307266943cf7 100644
--- a/resources/static/dialog/js/misc/state.js
+++ b/resources/static/dialog/js/misc/state.js
@@ -1,9 +1,11 @@
-/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
+/*jshint browser:true, jquery: true, forin: true, laxbreak:true */
 /*global BrowserID: true */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 BrowserID.State = (function() {
+  "use strict";
+
   var bid = BrowserID,
       storage = bid.Storage,
       network = bid.Network,
@@ -42,8 +44,7 @@ BrowserID.State = (function() {
 
     handleState("start", function(msg, info) {
       self.hostname = info.hostname;
-      self.privacyURL = info.privacyURL;
-      self.tosURL = info.tosURL;
+      self.siteTOSPP = !!(info.privacyPolicy && info.termsOfService);
       requiredEmail = info.requiredEmail;
 
       startAction(false, "doRPInfo", info);
@@ -80,8 +81,10 @@ BrowserID.State = (function() {
         self.email = requiredEmail;
         startAction("doAuthenticateWithRequiredEmail", {
           email: requiredEmail,
-          privacyURL: self.privacyURL,
-          tosURL: self.tosURL
+          // New users are handled by either the "new_user" flow or the
+          // "primary_user" flow. The Persona TOS/PP will be shown to users in
+          // these stages.
+          siteTOSPP: self.siteTOSPP && !user.getOriginEmail()
         });
       }
       else if (authenticated) {
@@ -92,8 +95,7 @@ BrowserID.State = (function() {
     });
 
     handleState("authenticate", function(msg, info) {
-      info.privacyURL = self.privacyURL;
-      info.tosURL = self.tosURL;
+      info.siteTOSPP = self.siteTOSPP;
       startAction("doAuthenticate", info);
     });
 
@@ -104,9 +106,23 @@ BrowserID.State = (function() {
       // know when we are losing users due to the email verification.
       mediator.publish("kpi_data", { new_account: true });
 
-      // cancel is disabled if the user is doing the initial password set
-      // for a requiredEmail.
-      info.cancelable = !requiredEmail;
+      _.extend(info, {
+        // cancel is disabled if the user is doing the initial password set
+        // for a requiredEmail.
+        cancelable: !requiredEmail,
+
+        // New users in the requiredEmail flow are sent directly to the
+        // set_password screen.  If this happens, they have not yet seen the
+        // TOS/PP agreement.
+
+        // Always show the Persona TOS/PP to new requiredEmail users.
+        personaTOSPP: !!requiredEmail,
+
+        // The site TOS/PP must be shown to new requiredEmail users if there is
+        // a site TOS/PP
+        siteTOSPP: !!requiredEmail && self.siteTOSPP
+      });
+
       startAction(false, "doSetPassword", info);
     });
 
@@ -176,12 +192,24 @@ BrowserID.State = (function() {
     });
 
     handleState("primary_user_unauthenticated", function(msg, info) {
-      info = helpers.extend(info, {
+      // a user who lands here is not authenticated with their identity
+      // provider.
+      _.extend(info, {
         add: !!addPrimaryUser,
         email: email,
         requiredEmail: !!requiredEmail,
-        privacyURL: self.privacyURL,
-        tosURL: self.tosURL
+
+        // In the requiredEmail flow, a user who is not authenticated with
+        // their primary will be sent directly to the "you must verify
+        // with your IdP" screen.
+        //
+        // Show the siteTOSPP to all requiredEmail users who have never visited
+        // the site before.
+        siteTOSPP: requiredEmail && self.siteTOSPP && !user.getOriginEmail(),
+
+        // Show the persona TOS/PP only to requiredEmail users who are creating
+        // a new account.
+        personaTOSPP: requiredEmail && !addPrimaryUser
       });
 
       if (primaryVerificationInfo) {
@@ -218,8 +246,7 @@ BrowserID.State = (function() {
     handleState("pick_email", function() {
       startAction("doPickEmail", {
         origin: self.hostname,
-        privacyURL: self.privacyURL,
-        tosURL: self.tosURL
+        siteTOSPP: self.siteTOSPP && !user.getOriginEmail()
       });
     });
 
@@ -260,8 +287,12 @@ BrowserID.State = (function() {
               startAction("doAuthenticateWithRequiredEmail", {
                 email: email,
                 secondary_auth: true,
-                privacyURL: self.privacyURL,
-                tosURL: self.tosURL
+
+                // This is a user is already authenticated to the assertion
+                // level who has chosen a secondary email address from the
+                // pick_email screen. They would have been shown the
+                // siteTOSPP there.
+                siteTOSPP: false
               });
             }
             else {
@@ -359,12 +390,26 @@ BrowserID.State = (function() {
       redirectToState("email_chosen", info);
     });
 
-    handleState("add_email", function(msg, info) {
-      info = helpers.extend(info, {
-        privacyURL: self.privacyURL,
-        tosURL: self.tosURL
-      });
+    handleState("reset_password", function(msg, info) {
+      info = info || {};
+      // reset_password says the user has confirmed that they want to
+      // reset their password.  doResetPassword will attempt to invoke
+      // the create_user wsapi.  If the wsapi call is successful,
+      // the user will be shown the "go verify your account" message.
+
+      // We have to save the staged email address here for when the user
+      // verifies their account and user_confirmed is called.
+      self.stagedEmail = info.email;
+      startAction(false, "doResetPassword", info);
+    });
 
+    handleState("add_email", function(msg, info) {
+      // add_email indicates the user wishes to add an email to the account,
+      // the add_email screen must be displayed.  After the user enters the
+      // email address they wish to add, add_email will trigger
+      // either 1) primary_user or 2) email_staged. #1 occurs if the email
+      // address is a primary address, #2 occurs if the address is a secondary
+      // and the verification email has been sent.
       startAction("doAddEmail", info);
     });
 
@@ -372,9 +417,26 @@ BrowserID.State = (function() {
       user.passwordNeededToAddSecondaryEmail(function(passwordNeeded) {
         if(passwordNeeded) {
           self.addEmailEmail = info.email;
-          // cancel is disabled if the user is doing the initial password set
-          // for a requiredEmail.
-          info.cancelable = !requiredEmail;
+
+          _.extend(info, {
+            // cancel is disabled if the user is doing the initial password set
+            // for a requiredEmail.
+            cancelable: !requiredEmail,
+
+            // stage_email is called to add an email to an already existing
+            // account.  Since it is an already existing account, the
+            // personaTOSPP does not need to be shown.
+            personaTOSPP: false,
+
+            // requiredEmail users who are adding an email but must set their
+            // password will be redirected here without seeing any other
+            // screens. non-requiredEmail users will have already seen the site
+            // TOS/PP in the pick-email screen if it was necessary.  Since
+            // requiredEmail users may not have seen the screen before, show it
+            // here if there is no originEmail.
+            siteTOSPP: self.siteTOSPP && requiredEmail && !user.getOriginEmail()
+          });
+
           startAction(false, "doSetPassword", info);
         }
         else {
diff --git a/resources/static/dialog/js/modules/actions.js b/resources/static/dialog/js/modules/actions.js
index 3a95c89155d98f12c71b9f8eb11231f0a1f82bfe..9144b37521a54ad9dae4245493476addd06cde77 100644
--- a/resources/static/dialog/js/modules/actions.js
+++ b/resources/static/dialog/js/modules/actions.js
@@ -57,10 +57,6 @@ BrowserID.Modules.Actions = (function() {
       if(data.ready) _.defer(data.ready);
     },
 
-    doRPInfo: function(info) {
-      startService("rp_info", info);
-    },
-
     doCancel: function() {
       if(onsuccess) onsuccess(null);
     },
@@ -156,6 +152,10 @@ BrowserID.Modules.Actions = (function() {
 
     doGenerateAssertion: function(info) {
       startService("generate_assertion", info);
+    },
+
+    doRPInfo: function(info) {
+      startService("rp_info", info);
     }
   });
 
diff --git a/resources/static/dialog/js/modules/add_email.js b/resources/static/dialog/js/modules/add_email.js
index da642e0d2ced87d3cdf769b0d2389e0b2ad46e9a..07f6242caf302bed2cb3aaa49ee7416522ffb811 100644
--- a/resources/static/dialog/js/modules/add_email.js
+++ b/resources/static/dialog/js/modules/add_email.js
@@ -1,5 +1,5 @@
 /*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
-/*global _: true, BrowserID: true, PageController: true */
+/*global _: true, BrowserID: true, PageController: true, gettext: true */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
@@ -9,6 +9,7 @@ BrowserID.Modules.AddEmail = (function() {
   var bid = BrowserID,
       dom = bid.DOM,
       helpers = bid.Helpers,
+      user = bid.User,
       dialogHelpers = helpers.Dialog,
       errors = bid.Errors,
       complete = helpers.complete,
@@ -54,12 +55,9 @@ BrowserID.Modules.AddEmail = (function() {
   var Module = bid.Modules.PageModule.extend({
     start: function(options) {
       var self=this,
-          templateData = helpers.extend({}, options, {
-            privacy_url: options.privacyURL || null,
-            tos_url: options.tosURL || null
-          });
+          originEmail = user.getOriginEmail();
 
-      self.renderDialog("add_email", templateData);
+      self.renderDialog("add_email", options);
       hideHint("addressInfo");
 
       self.click("#cancel", cancelAddEmail);
diff --git a/resources/static/dialog/js/modules/authenticate.js b/resources/static/dialog/js/modules/authenticate.js
index c316e55a5e5822825c2ad2bb69f1b32a6bf106fa..d1c13818901917c5ae93120aa100ce0cc6e579c8 100644
--- a/resources/static/dialog/js/modules/authenticate.js
+++ b/resources/static/dialog/js/modules/authenticate.js
@@ -33,6 +33,7 @@ BrowserID.Modules.Authenticate = (function() {
     }
     else {
       showHint("start");
+      enterEmailState.call(self);
       complete(info.ready);
     }
   }
@@ -98,7 +99,7 @@ BrowserID.Modules.Authenticate = (function() {
   function showHint(showSelector, callback) {
     _.each(hints, function(className) {
       if(className != showSelector) {
-        $("." + className).not("." + showSelector).hide();
+        dom.hide("." + className + ":not(." + showSelector + ")");
       }
     });
 
@@ -112,8 +113,8 @@ BrowserID.Modules.Authenticate = (function() {
     });
   }
 
-  function enterEmailState(el) {
-    if (!$("#email").is(":disabled")) {
+  function enterEmailState() {
+    if (!dom.is("#email", ":disabled")) {
       this.submit = checkEmail;
       showHint("start");
     }
@@ -129,6 +130,8 @@ BrowserID.Modules.Authenticate = (function() {
     showHint("returning", function() {
       dom.focus("#password");
     });
+
+
     complete(callback);
   }
 
@@ -158,12 +161,19 @@ BrowserID.Modules.Authenticate = (function() {
       var self=this;
       self.renderDialog("authenticate", {
         sitename: user.getHostname(),
-        email: lastEmail,
-        privacy_url: options.privacyURL,
-        tos_url: options.tosURL
+        email: lastEmail
       });
 
-      $(".returning,.start").hide();
+      dom.hide(".returning,.start");
+
+      // We have to show the TOS/PP agreements to *all* users here. Users who
+      // are already authenticated to their IdP but do not have a Persona
+      // account automatically have an account created with no further
+      // interaction.  To make sure they see the TOS/PP agreement, show it
+      // here.
+      if (options.siteTOSPP) {
+        dialogHelpers.showRPTosPP.call(self);
+      }
 
       self.bind("#email", "keyup", emailKeyUp);
       self.click("#forgotPassword", forgotPassword);
diff --git a/resources/static/dialog/js/modules/dialog.js b/resources/static/dialog/js/modules/dialog.js
index 930b7707eeb571d26fa6e2ec81ce267bc8e0fa06..8a4169fa8e5a3467885c6740109beb6b7ff1a9bc 100644
--- a/resources/static/dialog/js/modules/dialog.js
+++ b/resources/static/dialog/js/modules/dialog.js
@@ -12,6 +12,7 @@ BrowserID.Modules.Dialog = (function() {
       user = bid.User,
       errors = bid.Errors,
       dom = bid.DOM,
+      helpers = bid.Helpers,
       win = window,
       startExternalDependencies = true,
       channel,
@@ -73,10 +74,6 @@ BrowserID.Modules.Dialog = (function() {
     channel && channel.detach();
   }
 
-  function setOrigin(origin) {
-    user.setOrigin(origin);
-  }
-
   function onWindowUnload() {
     this.publish("window_unload");
   }
@@ -139,7 +136,7 @@ BrowserID.Modules.Dialog = (function() {
       var self=this,
           hash = win.location.hash;
 
-      setOrigin(origin_url);
+      user.setOrigin(origin_url);
 
 
       if (startExternalDependencies) {
@@ -159,9 +156,7 @@ BrowserID.Modules.Dialog = (function() {
       // verify params
       try {
         if (paramsFromRP.requiredEmail) {
-          if (!bid.verifyEmail(paramsFromRP.requiredEmail))
-            throw "invalid requiredEmail: (" + paramsFromRP.requiredEmail + ")";
-          params.requiredEmail = paramsFromRP.requiredEmail;
+          helpers.log("requiredEmail has been deprecated");
         }
 
         // support old parameter names...
@@ -169,8 +164,8 @@ BrowserID.Modules.Dialog = (function() {
         if (paramsFromRP.privacyURL) paramsFromRP.privacyPolicy = paramsFromRP.privacyURL;
 
         if (paramsFromRP.termsOfService && paramsFromRP.privacyPolicy) {
-          params.tosURL = fixupURL(origin_url, paramsFromRP.termsOfService);
-          params.privacyURL = fixupURL(origin_url, paramsFromRP.privacyPolicy);
+          params.termsOfService = fixupURL(origin_url, paramsFromRP.termsOfService);
+          params.privacyPolicy = fixupURL(origin_url, paramsFromRP.privacyPolicy);
         }
 
         if (paramsFromRP.siteLogo) {
@@ -196,24 +191,19 @@ BrowserID.Modules.Dialog = (function() {
           user.setReturnTo(returnTo);
         }
 
-
-        if (hash.indexOf("#CREATE_EMAIL=") === 0) {
-          var email = hash.replace(/#CREATE_EMAIL=/, "");
-          if (!bid.verifyEmail(email))
-            throw "invalid #CREATE_EMAIL= (" + email + ")";
+        if (hash.indexOf("#AUTH_RETURN") === 0) {
+          var primaryParams = JSON.parse(win.sessionStorage.primaryVerificationFlow);
+          params.email = primaryParams.email;
+          params.add = primaryParams.add;
           params.type = "primary";
-          params.email = email;
-          params.add = false;
-        }
-        else if (hash.indexOf("#ADD_EMAIL=") === 0) {
-          var email = hash.replace(/#ADD_EMAIL=/, "");
-          if (!bid.verifyEmail(email))
-            throw "invalid #ADD_EMAIL= (" + email + ")";
-          params.type = "primary";
-          params.email = email;
-          params.add = true;
+
+          // FIXME: if it's AUTH_RETURN_CANCEL, we should short-circuit
+          // the attempt at provisioning. For now, we let provisioning
+          // be tried and fail.
         }
 
+        // no matter what, we clear the primary flow state for this window
+        win.sessionStorage.primaryVerificationFlow = undefined;
       } catch(e) {
         // note: renderError accepts HTML and cheerfully injects it into a
         // frame with a powerful origin. So convert 'e' first.
diff --git a/resources/static/dialog/js/modules/pick_email.js b/resources/static/dialog/js/modules/pick_email.js
index b666c95e90d2d95f26bb9ca71f65695f1f581466..c28a23babeeefe78540ac346f93f96e5212894b1 100644
--- a/resources/static/dialog/js/modules/pick_email.js
+++ b/resources/static/dialog/js/modules/pick_email.js
@@ -93,10 +93,13 @@ BrowserID.Modules.PickEmail = (function() {
 
       self.renderDialog("pick_email", {
         identities: identities,
-        siteemail: storage.site.get(origin, "email"),
-        privacy_url: options.privacyURL,
-        tos_url: options.tosURL
+        siteemail: user.getOriginEmail()
       });
+
+      if (options.siteTOSPP) {
+        dialogHelpers.showRPTosPP.call(self);
+      }
+
       dom.getElements("body").css("opacity", "1");
       if (dom.getElements("#selectEmail input[type=radio]:visible").length === 0) {
         // If there is only one email address, the radio button is never shown,
diff --git a/resources/static/dialog/js/modules/required_email.js b/resources/static/dialog/js/modules/required_email.js
index 6e5f97e59d25682fbda2022dba60da2a733440e8..037621924d000b0b2c5925f1ad34b22efd58f550 100644
--- a/resources/static/dialog/js/modules/required_email.js
+++ b/resources/static/dialog/js/modules/required_email.js
@@ -147,7 +147,13 @@ BrowserID.Modules.RequiredEmail = (function() {
               // We know the user has control of this address, give them
               // a chance to hit "sign in" before we kick them off to the
               // primary flow account.
-              showTemplate({ signin: true, primary: true });
+
+              // Show the Persona TOS/PP to any primary user who is authed with
+              // their IdP but not with Persona.  Unfortunately, addressInfo
+              // does not tell us whether a primary address already has an
+              // account, so we have to show the personaTOSPP to any user who
+              // is not authenticated.
+              showTemplate({ signin: true, primary: true, personaTOSPP: !auth_level });
             }
             else if(info.type === "primary" && !info.authed) {
               // User who does not control a primary address.
@@ -213,12 +219,15 @@ BrowserID.Modules.RequiredEmail = (function() {
           password: false,
           secondary_auth: false,
           primary: false,
-          privacy_url: options.privacyURL || null,
-          tos_url: options.tosURL || null
+          personaTOSPP: false
         }, templateData);
 
         self.renderDialog("required_email", templateData);
 
+        if (options.siteTOSPP) {
+          dialogHelpers.showRPTosPP.call(self);
+        }
+
         self.click("#sign_in", signIn);
         self.click("#verify_address", verifyAddress);
         self.click("#forgotPassword", forgotPassword);
diff --git a/resources/static/dialog/js/modules/rp_info.js b/resources/static/dialog/js/modules/rp_info.js
index 3bc9b20edd9efb34d9466a815024b6e09c0775e9..55d91d276edf7e861841dc1c3243920fd44f536b 100644
--- a/resources/static/dialog/js/modules/rp_info.js
+++ b/resources/static/dialog/js/modules/rp_info.js
@@ -7,7 +7,8 @@
 
 /**
  * Purpose:
- *  Display to the user RP related data such as hostname, name, and logo.
+ *  Display to the user RP related data such as hostname, sitename, logo,
+ *  TOS/PP, etc.
  */
 BrowserID.Modules.RPInfo = (function() {
   "use strict";
@@ -32,7 +33,9 @@ BrowserID.Modules.RPInfo = (function() {
       renderer.render("#rp_info", "rp_info", {
         hostname: options.hostname,
         siteName: options.siteName,
-        siteLogo: options.siteLogo
+        siteLogo: options.siteLogo,
+        privacyPolicy: options.privacyPolicy,
+        termsOfService: options.termsOfService
       });
 
       sc.start.call(this, options);
diff --git a/resources/static/dialog/js/modules/set_password.js b/resources/static/dialog/js/modules/set_password.js
index 7bc6848c59663f9f30ea5f8023feb0e97c9cb984..02c6de1038dd03df26c63c2dc377a9708e8af66f 100644
--- a/resources/static/dialog/js/modules/set_password.js
+++ b/resources/static/dialog/js/modules/set_password.js
@@ -36,9 +36,14 @@ BrowserID.Modules.SetPassword = (function() {
 
       self.renderDialog("set_password", {
         password_reset: !!options.password_reset,
-        cancelable: options.cancelable !== false
+        cancelable: options.cancelable !== false,
+        personaTOSPP: options.personaTOSPP
       });
 
+      if (options.siteTOSPP) {
+        dialogHelpers.showRPTosPP.call(self);
+      }
+
       self.click("#cancel", cancel);
 
       sc.start.call(self, options);
diff --git a/resources/static/dialog/js/modules/verify_primary_user.js b/resources/static/dialog/js/modules/verify_primary_user.js
index 9e38e6be13bfec2f9039451cb860310b7b03a2f3..8a35914e2665c6a07d5814f439da440e5699a752 100644
--- a/resources/static/dialog/js/modules/verify_primary_user.js
+++ b/resources/static/dialog/js/modules/verify_primary_user.js
@@ -12,21 +12,22 @@ BrowserID.Modules.VerifyPrimaryUser = (function() {
       add,
       email,
       auth_url,
+      dom = bid.DOM,
       helpers = bid.Helpers,
+      dialogHelpers = helpers.Dialog,
       complete = helpers.complete;
 
   function verify(callback) {
     this.publish("primary_user_authenticating");
 
-    // replace any hashes that may be there already.
-    var returnTo = win.document.location.href.replace(/#.*$/, "");
-
-    var type = add ? "ADD_EMAIL" : "CREATE_EMAIL";
-    var url = helpers.toURL(auth_url, {
-      email: email,
-      return_to: returnTo + "#" + type + "=" +email
+    // set up some information about what we're doing
+    win.sessionStorage.primaryVerificationFlow = JSON.stringify({
+      add: add,
+      email: email
     });
 
+    var url = helpers.toURL(auth_url, {email: email});
+    
     win.document.location = url;
 
     complete(callback);
@@ -47,12 +48,16 @@ BrowserID.Modules.VerifyPrimaryUser = (function() {
       email = data.email;
       auth_url = data.auth_url;
 
-      var templateData = helpers.extend({}, data, {
+      self.renderDialog("verify_primary_user", {
+        email: data.email,
+        auth_url: data.auth_url,
         requiredEmail: data.requiredEmail || false,
-        privacy_url: data.privacyURL || null,
-        tos_url: data.tosURL || null
+        personaTOSPP: data.personaTOSPP
       });
-      self.renderDialog("verify_primary_user", templateData);
+
+      if (data.siteTOSPP) {
+        dialogHelpers.showRPTosPP.call(self);
+      }
 
       self.click("#cancel", cancel);
 
diff --git a/resources/static/dialog/js/start.js b/resources/static/dialog/js/start.js
index 70c574bad3f2e645532c48aee54fdec314b886fc..3536e1f25e1b9f346debb65f5ecc0966f04d0d21 100644
--- a/resources/static/dialog/js/start.js
+++ b/resources/static/dialog/js/start.js
@@ -15,7 +15,7 @@
   network.init();
 
   var hash = window.location.hash || "",
-      continuation = hash.indexOf("#CREATE_EMAIL") > -1 || hash.indexOf("#ADD_EMAIL") > -1;
+      continuation = hash.indexOf("#AUTH_RETURN") > -1;
 
   moduleManager.register("interaction_data", modules.InteractionData);
   moduleManager.start("interaction_data", { continuation: continuation });
diff --git a/resources/static/dialog/views/add_email.ejs b/resources/static/dialog/views/add_email.ejs
index b98b22ab8708478c0dd7392de90e4bd2a78632eb..15ebbad134b0779d228dc3f5cbb9102a1193fa2f 100644
--- a/resources/static/dialog/views/add_email.ejs
+++ b/resources/static/dialog/views/add_email.ejs
@@ -34,22 +34,8 @@
       </ul>
 
       <div class="submit cf">
-        <% if (privacy_url && tos_url) { %>
-          <p class="tospp">
-            <%= format(
-                  gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
-                       [ gettext('add'),
-                         format(' href="%s" target="_new"', [tos_url]),
-                         format(' href="%s" target="_new"', [privacy_url])
-                       ]) %>
-          </p>
-          <p>
-        <% } %>
           <button id="addNewEmail"><%= gettext('add') %></button>
           <a href="#" id="cancel" class="action"><%= gettext('cancel') %></a>
-        <% if (privacy_url && tos_url) { %>
-          </p>
-        <% } %>
       </div>
 
   </div>
diff --git a/resources/static/dialog/views/authenticate.ejs b/resources/static/dialog/views/authenticate.ejs
index 55e461d5ceaed29e52ef2f148466d2698beb7c7e..a95c45b441638b50424e8ca14dc1dc21df6fa421 100644
--- a/resources/static/dialog/views/authenticate.ejs
+++ b/resources/static/dialog/views/authenticate.ejs
@@ -58,16 +58,15 @@
           <button class="start addressInfo" tabindex="3"><%= gettext('next') %></button>
           <button class="returning" tabindex="3"><%= gettext('sign in') %></button>
         </p>
-        <% if (privacy_url && tos_url) { %>
-           <p class="tospp">
-            <%= format(
-                  gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
-                       [ gettext('next'),
-                         format(' href="%s" target="_new"', [tos_url]),
-                         format(' href="%s" target="_new"', [privacy_url])
-                       ]) %>
+
+        <p class="tospp">
+           <%= format(
+                gettext('By proceeding, you agree to %s\'s <a %s>Terms</a> and <a %s>Privacy Policy</a>.'),
+                     [ "Persona",
+                       ' href="https://login.persona.org/tos" target="_new"',
+                       ' href="https://login.persona.org/privacy" target="_new"',
+                     ]) %>
           </p>
-      <% } %>
 
       </div>
   </div>
diff --git a/resources/static/dialog/views/pick_email.ejs b/resources/static/dialog/views/pick_email.ejs
index c189f521055d156ee397366df6d6306a22645f98..059072425b93d6be854125b4290d444f26c019ad 100644
--- a/resources/static/dialog/views/pick_email.ejs
+++ b/resources/static/dialog/views/pick_email.ejs
@@ -23,18 +23,8 @@
 
 
       <div class="submit add cf">
-      <p class="cf">
-        <button id="signInButton"><%= gettext('sign in') %></button>
-      </p>
-      <% if (privacy_url && tos_url) { %>
-        <p class="tospp">
-          <%= format(
-            gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
-                    [ gettext('sign in'),
-                      format(' href="%s" target="_new"', [tos_url]),
-                      format(' href="%s" target="_new"', [privacy_url])
-                    ]) %>
+        <p class="cf">
+          <button id="signInButton"><%= gettext('sign in') %></button>
         </p>
-      <% } %>
       </div>
   </div>
diff --git a/resources/static/dialog/views/required_email.ejs b/resources/static/dialog/views/required_email.ejs
index a1e0fdace2304f322d97fcac29b3cf65a664c10e..5245319c2e48ee6849561757f1a8d9b7b9e7ef83 100644
--- a/resources/static/dialog/views/required_email.ejs
+++ b/resources/static/dialog/views/required_email.ejs
@@ -50,17 +50,7 @@
       </ul>
 
       <div class="submit cf">
-        <% if (privacy_url && tos_url) { %>
-          <p class="tospp">
-            <%= format(
-                  gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
-                       [ gettext('sign in'),
-                         format(' href="%s" target="_new"', [tos_url]),
-                         format(' href="%s" target="_new"', [privacy_url])
-                       ]) %>
-          </p>
-          <p>
-        <% } %>
+          <p class="cf">
             <% if (signin) { %>
               <button id="sign_in" tabindex="3"><%= gettext("sign in") %></button>
             <% } else if (verify) { %>
@@ -70,8 +60,16 @@
             <% if (secondary_auth) { %>
               <a href="#" id="cancel" class="action" tabindex="4"><%= gettext("cancel") %></a>
             <% } %>
-        <% if (privacy_url && tos_url) { %>
           </p>
-        <% } %>
+          <% if (personaTOSPP) { %>
+            <p class="tospp">
+               <%= format(
+                    gettext('By proceeding, you agree to %s\'s <a %s>Terms</a> and <a %s>Privacy Policy</a>.'),
+                         [ "Persona",
+                           ' href="https://login.persona.org/tos" target="_new"',
+                           ' href="https://login.persona.org/privacy" target="_new"',
+                         ]) %>
+            </p>
+          <% } %>
       </div>
   </div>
diff --git a/resources/static/dialog/views/rp_info.ejs b/resources/static/dialog/views/rp_info.ejs
index 5928a18d6c945d726598017558fbb69f1f4d16e1..724a3e1925dfbf9a1c5d58d6e8bfc67278d31c44 100644
--- a/resources/static/dialog/views/rp_info.ejs
+++ b/resources/static/dialog/views/rp_info.ejs
@@ -11,11 +11,18 @@
   <h2 id="rp_name"><%= siteName %></h2>
 <% } %>
 
-<% if(hostname) { %>
-  <% if(siteName) { %>
-    <h3 id="rp_hostname"><%= hostname %></h3>
-  <% } else { %>
-    <h2 id="rp_hostname"><%= hostname %></h2>
-  <% } %>
+<% if(siteName) { %>
+  <h3 id="rp_hostname"><%= hostname %></h3>
+<% } else { %>
+  <h2 id="rp_hostname"><%= hostname %></h2>
 <% } %>
 
+<% if(privacyPolicy && termsOfService) { %>
+  <p id="rptospp" class="tospp">
+    <%= format(gettext("By proceeding, you agree to %s\'s <a %s>Terms</a> and <a %s>Privacy Policy</a>."),
+    [ siteName || hostname,
+     ' href="' + termsOfService + '" id="rp_tos" target="_blank"',
+     ' href="' + privacyPolicy + '" id="rp_pp" target="_blank"'
+     ]) %>
+   </p>
+<% } %>
diff --git a/resources/static/dialog/views/set_password.ejs b/resources/static/dialog/views/set_password.ejs
index d244293925fc8e8f299af68f59774d156bc32e87..c16a1f2d9fc6378601fde28faeefe159cbb416e5 100644
--- a/resources/static/dialog/views/set_password.ejs
+++ b/resources/static/dialog/views/set_password.ejs
@@ -48,11 +48,23 @@
       </ul>
 
       <div class="submit cf">
+        <p class="cf">
           <button id="<%= password_reset ? "password_reset" : "verify_user" %>">
             <%= password_reset ? gettext('reset password') : gettext('verify email') %>
           </button>
           <% if(cancelable) { %>
             <a id="cancel" class="action" href="#"><%= gettext('cancel') %></a>
           <% } %>
+        </p>
+        <% if (personaTOSPP) { %>
+          <p id="persona_tospp" class="tospp">
+             <%= format(
+                gettext('By proceeding, you agree to %s\'s <a %s>Terms</a> and <a %s>Privacy Policy</a>.'),
+                     [ "Persona",
+                       ' href="https://login.persona.org/tos" target="_new"',
+                       ' href="https://login.persona.org/privacy" target="_new"',
+                     ]) %>
+          </p>
+        <% } %>
       </div>
   </div>
diff --git a/resources/static/dialog/views/verify_primary_user.ejs b/resources/static/dialog/views/verify_primary_user.ejs
index 0a3de6851ed2412d1e3fdefbddf12548b777bb66..c2d4da92de3cdeb7a34d18a1be6a242a1823f370 100644
--- a/resources/static/dialog/views/verify_primary_user.ejs
+++ b/resources/static/dialog/views/verify_primary_user.ejs
@@ -4,7 +4,7 @@
 
 <% if (requiredEmail) { %>
   <strong><%= gettext('The site requested you sign in using') %></strong>
-  <div class="form_section">
+  <div id="verify_primary_user" class="cf form_section">
       <ul class="inputs">
 
           <li>
@@ -15,7 +15,7 @@
               </div>
           </li>
 
-          <li>
+          <li class="small">
               <%= gettext("You must sign in with your email provider to verify ownership of this address. This window will be redirected to") %>
               <p>
                   <strong><%= auth_url %></strong>.
@@ -24,48 +24,47 @@
       </ul>
 
       <div class="submit cf">
-        <% if (privacy_url && tos_url) { %>
-          <p class="tospp">
-            <%= format(
-                  gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
-                       [ gettext('verify'),
-                         format(' href="%s" target="_new"', [tos_url]),
-                         format(' href="%s" target="_new"', [privacy_url])
-                       ]) %>
-          </p>
-          <p>
-        <% } %>
-          <button id="verifyWithPrimary"><%= gettext("verify") %></button>
-        <% if (privacy_url && tos_url) { %>
+          <p class="cf">
+            <button id="verifyWithPrimary"><%= gettext("verify") %></button>
           </p>
-        <% } %>
+
+          <% if (personaTOSPP) { %>
+            <p id="persona_tospp" class="tospp">
+               <%= format(
+                  gettext('By proceeding, you agree to %s\'s <a %s>Terms</a> and <a %s>Privacy Policy</a>.'),
+                       [ "Persona",
+                         ' href="https://login.persona.org/tos" target="_new"',
+                         ' href="https://login.persona.org/privacy" target="_new"',
+                       ]) %>
+            </p>
+          <% } %>
       </div>
   </div>
 
 <% } else { %>
   <strong><%= gettext("Verify With Email Provider") %></strong>
 
-  <div class="cf form_section">
-    <%= gettext("You must sign in with your email provider to verify ownership of this address. This window will be redirected to") %>
-    <p>
-      <strong><%= auth_url %></strong>.
-    </p>
+  <div id="verify_primary_user" class="cf form_section">
+    <div class="small">
+      <%= gettext("You must sign in with your email provider to verify ownership of this address. This window will be redirected to") %>
+      <p>
+        <strong><%= auth_url %></strong>.
+      </p>
+    </div>
 
     <div class="submit cf">
-        <% if (privacy_url && tos_url) { %>
-          <p class="tospp">
-            <%= format(
-                  gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
-                       [ gettext('verify'),
-                         format(' href="%s" target="_new"', [tos_url]),
-                         format(' href="%s" target="_new"', [privacy_url])
-                       ]) %>
-          </p>
-          <p>
-        <% } %>
+        <p class="cf">
           <button id="verifyWithPrimary"><%= gettext("verify") %></button>
           <a href="#" id="cancel" class="action"><%= gettext("cancel") %></a>
-        <% if (privacy_url && tos_url) { %>
+        </p>
+        <% if (personaTOSPP) { %>
+          <p id="persona_tospp" class="tospp">
+             <%= format(
+                gettext('By proceeding, you agree to %s\'s <a %s>Terms</a> and <a %s>Privacy Policy</a>.'),
+                     [ "Persona",
+                       ' href="https://login.persona.org/tos" target="_new"',
+                       ' href="https://login.persona.org/privacy" target="_new"',
+                     ]) %>
           </p>
         <% } %>
     </div>
diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js
index f81e73d4356726eac2b5ebb3ba0334d0cb029f04..3516f7af707e0fc15aa87f1f4140ec8b68b34539 100644
--- a/resources/static/include_js/include.js
+++ b/resources/static/include_js/include.js
@@ -1027,18 +1027,42 @@
 
       _open_hidden_iframe();
 
+      // back compat support for loggedInEmail
+      if (typeof options.loggedInEmail !== 'undefined' &&
+          typeof options.loggedInUser !== 'undefined') {
+        throw "you cannot supply *both* loggedInEmail and loggedInUser";
+      }
+      else if(typeof options.loggedInEmail !== 'undefined') {
+        try {
+          console.log("loggedInEmail has been deprecated");
+        } catch(e) {
+          /* ignore error */
+        }
+
+        options.loggedInUser = options.loggedInEmail;
+        delete options.loggedInEmail;
+      }
+
       // check that the commChan was properly initialized before interacting with it.
       // on unsupported browsers commChan might still be undefined, in which case
       // we let the dialog display the "unsupported browser" message upon spawning.
-      if (typeof options.loggedInEmail !== 'undefined' && commChan) {
+      if (typeof options.loggedInUser !== 'undefined' && commChan) {
         commChan.notify({
           method: 'loggedInUser',
-          params: options.loggedInEmail
+          params: options.loggedInUser
         });
       }
     }
 
     function internalRequest(options) {
+      if (options.requiredEmail) {
+        try {
+          console.log("requiredEmail has been deprecated");
+        } catch(e) {
+          /* ignore error */
+        }
+      }
+
       // focus an existing window
       if (w) {
         try {
diff --git a/resources/static/pages/css/style.css b/resources/static/pages/css/style.css
index 96034587943cb6247e9a382cd8149d6b020691e8..d73c65a9f1fbfe26c3f45fbffef015accc062cf3 100644
--- a/resources/static/pages/css/style.css
+++ b/resources/static/pages/css/style.css
@@ -447,10 +447,6 @@ button.delete:active {
   padding: 0;
 }
 
-#signUpForm .submit {
-  height: 28px;
-}
-
 #signUpForm > .siteinfo {
   margin-bottom: 10px;
 }
@@ -459,10 +455,18 @@ button.delete:active {
   display: none;
 }
 
+.submit > p {
+  line-height: 28px;
+}
+
 .submit .remember {
   float: left;
 }
 
+.tospp {
+  font-size: 13px;
+}
+
 .enter_password .password_entry, .enter_verify_password #verify_password, .known_secondary .password_entry,
 .unknown_secondary #unknown_secondary, .verify_primary #verify_primary {
   display: block;
diff --git a/resources/static/test/cases/common/js/user.js b/resources/static/test/cases/common/js/user.js
index 26c47010104631cc42cdd9ac2c77caff04b6c9e5..26813f1815568a5b5626606aeedabed04e7ae751 100644
--- a/resources/static/test/cases/common/js/user.js
+++ b/resources/static/test/cases/common/js/user.js
@@ -92,6 +92,22 @@ var jwcrypto = require("./lib/jwcrypto");
     equal(lib.getReturnTo(), returnTo, "get/setReturnTo work as expected");
   });
 
+  test("setOriginEmail/getOriginEmail", function() {
+    storage.addEmail("testuser@testuser.com", { type: "primary" });
+    storage.addEmail("testuser2@testuser.com", { type: "primary" });
+
+    lib.setOrigin("http://testdomain.org");
+
+    lib.setOriginEmail("testuser@testuser.com");
+    equal(lib.getOriginEmail(), "testuser@testuser.com", "correct email");
+
+    lib.setOrigin("http://othertestdomain.org");
+    lib.setOriginEmail("testuser2@testuser.com");
+
+    lib.setOrigin("http://testdomain.org");
+    equal(lib.getOriginEmail(), "testuser@testuser.com", "correct email");
+  });
+
   test("getStoredEmailKeypairs without key - return all identities", function() {
     var identities = lib.getStoredEmailKeypairs();
     equal("object", typeof identities, "object returned");
diff --git a/resources/static/test/cases/dialog/js/misc/state.js b/resources/static/test/cases/dialog/js/misc/state.js
index 8d6850d399428fb18c5348332367ffc39a9db33d..3d384a0eb72c216073c080033acbfdf4d148d6aa 100644
--- a/resources/static/test/cases/dialog/js/misc/state.js
+++ b/resources/static/test/cases/dialog/js/misc/state.js
@@ -34,6 +34,14 @@
     }
   }
 
+  function testActionStarted(actionName, requiredOptions) {
+    ok(actions.called[actionName], actionName + "called");
+    for(var key in requiredOptions) {
+      equal(actions.info[actionName][key], requiredOptions[key],
+          actionName + " called with " + key + "=" + requiredOptions[key]);
+    }
+  }
+
   function createMachine() {
     machine = bid.State.create();
     actions = new ActionsMock();
@@ -127,6 +135,16 @@
     equal(actions.info.doResetPassword.email, TEST_EMAIL, "correct email sent to doResetPassword");
   });
 
+  test("start - RPInfo always started", function() {
+    mediator.publish("start", {
+      termsOfService: "https://browserid.org/TOS.html",
+      privacyPolicy: "https://browserid.org/priv.html"
+    });
+
+    ok(actions.info.doRPInfo.termsOfService, "doRPInfo called with termsOfService set");
+    ok(actions.info.doRPInfo.privacyPolicy, "doRPInfo called with privacyPolicy set");
+  });
+
   test("user_staged - call doConfirmUser", function() {
     mediator.publish("user_staged", { email: TEST_EMAIL });
 
@@ -254,13 +272,13 @@
     mediator.publish("authenticated", { email: TEST_EMAIL });
   });
 
-  test("forgot_password", function() {
+  test("forgot_password - call doForgotPassword with correct options", function() {
+    mediator.publish("start", { privacyPolicy: "priv.html", termsOfService: "tos.html" });
     mediator.publish("forgot_password", {
       email: TEST_EMAIL,
       requiredEmail: true
     });
-    equal(actions.info.doForgotPassword.email, TEST_EMAIL, "correct email passed");
-    equal(actions.info.doForgotPassword.requiredEmail, true, "correct requiredEmail passed");
+    testActionStarted("doForgotPassword", { email: TEST_EMAIL, requiredEmail: true });
   });
 
   test("password_reset to user_confirmed - call doUserStaged then doEmailConfirmed", function() {
@@ -379,12 +397,11 @@
     ok(actions.called.doNotMe, "doNotMe has been called");
   });
 
-  test("authenticate", function() {
-    mediator.publish("authenticate", {
-      email: TEST_EMAIL
-    });
+  test("authenticate - call doAuthenticate with the correct options", function() {
+    mediator.publish("start", { privacyPolicy: "priv.html", termsOfService: "tos.html" });
+    mediator.publish("authenticate", { email: TEST_EMAIL });
 
-    equal(actions.info.doAuthenticate.email, TEST_EMAIL, "authenticate with testuser@testuser.com");
+    testActionStarted("doAuthenticate", { email: TEST_EMAIL, siteTOSPP: true });
   });
 
   test("start with no special parameters - go straight to checking auth", function() {
@@ -410,16 +427,23 @@
   });
 
 
+  test("add_email - call doAddEmail with correct options", function() {
+    mediator.publish("start", { privacyPolicy: "priv.html", termsOfService: "tos.html" });
+    mediator.publish("add_email");
+    testActionStarted("doAddEmail");
+  });
+
   asyncTest("email_chosen with secondary email, user must authenticate - call doAuthenticateWithRequiredEmail", function() {
     var email = TEST_EMAIL;
     storage.addEmail(email, { type: "secondary" });
 
     xhr.setContextInfo("auth_level", "assertion");
 
+    mediator.publish("start", { privacyPolicy: "priv.html", termsOfService: "tos.html" });
     mediator.publish("email_chosen", {
       email: email,
       complete: function() {
-        equal(actions.called.doAuthenticateWithRequiredEmail, true, "doAuthenticateWithRequiredEmail called");
+        testActionStarted("doAuthenticateWithRequiredEmail", { siteTOSPP: false });
         start();
       }
     });
@@ -469,15 +493,14 @@
   test("null assertion generated - preserve original options in doPickEmail", function() {
     mediator.publish("start", {
       hostname: "http://example.com",
-      privacyURL: "http://example.com/priv.html",
-      tosURL: "http://example.com/tos.html"
+      privacyPolicy: "http://example.com/priv.html",
+      termsOfService: "http://example.com/tos.html"
     });
     mediator.publish("assertion_generated", { assertion: null });
 
     equal(actions.called.doPickEmail, true, "doPickEmail callled");
     equal(actions.info.doPickEmail.origin, "http://example.com", "hostname preserved");
-    equal(actions.info.doPickEmail.privacyURL, "http://example.com/priv.html", "privacyURL preserved");
-    equal(actions.info.doPickEmail.tosURL, "http://example.com/tos.html", "tosURL preserved");
+    equal(actions.info.doPickEmail.siteTOSPP, true, "siteTOSPP preserved");
   });
 
   test("add_email - call doAddEmail", function() {
diff --git a/resources/static/test/cases/dialog/js/modules/actions.js b/resources/static/test/cases/dialog/js/modules/actions.js
index 8d90d69f88dfd2bcd2b72865732ff267e1593e3a..27c54f781f818759fa4527d1c1c0910675b5bcee 100644
--- a/resources/static/test/cases/dialog/js/modules/actions.js
+++ b/resources/static/test/cases/dialog/js/modules/actions.js
@@ -88,7 +88,6 @@
     testActionStartsModule('doGenerateAssertion', { email: TEST_EMAIL }, "generate_assertion");
   });
 
-
   asyncTest("doStageUser with successful creation - trigger user_staged", function() {
     createController({
       ready: function() {
@@ -109,5 +108,21 @@
   asyncTest("doForgotPassword - call the set_password controller with reset_password true", function() {
     testActionStartsModule('doForgotPassword', { email: TEST_EMAIL }, "set_password");
   });
+
+  asyncTest("doRPInfo - start the rp_info service", function() {
+    createController({
+      ready: function() {
+        var error;
+        try {
+          controller.doRPInfo({ name: "browserid.org" });
+        } catch(e) {
+          error = e;
+        }
+
+        equal(error, "module not registered for rp_info", "correct service started");
+        start();
+      }
+    });
+  });
 }());
 
diff --git a/resources/static/test/cases/dialog/js/modules/add_email.js b/resources/static/test/cases/dialog/js/modules/add_email.js
index 6d635e82286fcb606da9e1fe8dd8c99307b40b9e..43f19e385b3091fcdd8411ee30f0c6891f38a123 100644
--- a/resources/static/test/cases/dialog/js/modules/add_email.js
+++ b/resources/static/test/cases/dialog/js/modules/add_email.js
@@ -41,16 +41,6 @@
     controller.start(options || {});
   }
 
-  test("privacyURL and tosURL specified - show TOS/PP", function() {
-    equal($(".tospp").length, 0, "tospp has not yet been added to the DOM");
-    createController({
-      privacyURL: "http://testuser.com/priv.html",
-      tosURL: "http://testuser.com/tos.html",
-    });
-
-    equal($(".tospp").length, 1, "tospp has been added to the DOM");
-  });
-
   test("addEmail with specified email address - fill in email", function() {
     createController({ email: "testuser@testuser.com" });
     ok($("#newEmail").val(), "testuser@testuser.com", "email prepopulated");
diff --git a/resources/static/test/cases/dialog/js/modules/dialog.js b/resources/static/test/cases/dialog/js/modules/dialog.js
index f1ddb37c6f57dfe7afc6d744d0bde0dafb1419b8..2519af8c14c3c13e0c77ae4b66b80fcd156036fc 100644
--- a/resources/static/test/cases/dialog/js/modules/dialog.js
+++ b/resources/static/test/cases/dialog/js/modules/dialog.js
@@ -50,6 +50,8 @@
     },
 
     navigator: {},
+
+    sessionStorage: {}
   };
 
   function createController(config) {
@@ -134,8 +136,12 @@
     });
   });
 
-  asyncTest("initialization with #CREATE_EMAIL=testuser@testuser.com - trigger start with correct params", function() {
-    winMock.location.hash = "#CREATE_EMAIL=testuser@testuser.com";
+  asyncTest("initialization with #AUTH_RETURN and add=false - trigger start with correct params", function() {
+    winMock.location.hash = "#AUTH_RETURN";
+    winMock.sessionStorage.primaryVerificationFlow = JSON.stringify({
+      add: false,
+      email: TESTEMAIL
+    });
 
     createController({
       ready: function() {
@@ -157,8 +163,12 @@
     });
   });
 
-  asyncTest("initialization with #ADD_EMAIL=testuser@testuser.com - trigger start with correct params", function() {
-    winMock.location.hash = "#ADD_EMAIL=testuser@testuser.com";
+  asyncTest("initialization with #AUTH_RETURN and add=true - trigger start with correct params", function() {
+    winMock.location.hash = "#AUTH_RETURN";
+    winMock.sessionStorage.primaryVerificationFlow = JSON.stringify({
+      add: true,
+      email: TESTEMAIL
+    });
 
     createController({
       ready: function() {
@@ -198,66 +208,8 @@
     });
   });
 
-  asyncTest("get with invalid requiredEmail - print error screen", function() {
-    createController({
-      ready: function() {
-        mediator.subscribe("start", function(msg, info) {
-          ok(false, "start should not have been called");
-        });
-
-        var retval = controller.get(HTTP_TEST_DOMAIN, {
-          requiredEmail: "bademail"
-        });
-        equal(retval, "invalid requiredEmail: (bademail)", "expected error");
-        testErrorVisible();
-        start();
-      }
-    });
-  });
-
-  asyncTest("get with script containing requiredEmail - print error screen", function() {
-    createController({
-      ready: function() {
-        mediator.subscribe("start", function(msg, info) {
-          ok(false, "start should not have been called");
-        });
-
-        var retval = controller.get(HTTP_TEST_DOMAIN, {
-          requiredEmail: "<script>window.scriptRun=true;</script>testuser@testuser.com"
-        });
-
-        // If requiredEmail is not properly escaped, scriptRun will be true.
-        equal(typeof window.scriptRun, "undefined", "script was not run");
-        equal(retval, "invalid requiredEmail: (<script>window.scriptRun=true;</script>testuser@testuser.com)", "expected error");
-        testErrorVisible();
-        start();
-      }
-    });
-  });
-
-  asyncTest("get with valid requiredEmail - go to start", function() {
-    createController({
-      ready: function() {
-        var startInfo;
-        mediator.subscribe("start", function(msg, info) {
-          startInfo = info;
-        });
-
-        var retval = controller.get(HTTP_TEST_DOMAIN, {
-          requiredEmail: TESTEMAIL
-        });
-
-        testHelpers.testObjectValuesEqual(startInfo, {
-          requiredEmail: TESTEMAIL
-        });
-        equal(typeof retval, "undefined", "no error expected");
-        testErrorNotVisible();
-        start();
-      }
-    });
-  });
 
-  asyncTest("get with relative tosURL & valid privacyURL - print error screen", function() {
+  asyncTest("get with relative termsOfService & valid privacyPolicy - print error screen", function() {
     createController({
       ready: function() {
         mediator.subscribe("start", function(msg, info) {
@@ -265,8 +217,8 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "relative.html",
-          privacyURL: "/privacy.html"
+          termsOfService: "relative.html",
+          privacyPolicy: "/privacy.html"
         });
         equal(retval, "relative urls not allowed: (relative.html)", "expected error");
         testErrorVisible();
@@ -275,7 +227,7 @@
     });
   });
 
-  asyncTest("get with script containing tosURL - print error screen", function() {
+  asyncTest("get with script containing termsOfService - print error screen", function() {
     createController({
       ready: function() {
         mediator.subscribe("start", function(msg, info) {
@@ -283,11 +235,11 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "relative.html<script>window.scriptRun=true;</script>",
-          privacyURL: "/privacy.html"
+          termsOfService: "relative.html<script>window.scriptRun=true;</script>",
+          privacyPolicy: "/privacy.html"
         });
 
-        // If tosURL is not properly escaped, scriptRun will be true.
+        // If termsOfService is not properly escaped, scriptRun will be true.
         equal(typeof window.scriptRun, "undefined", "script was not run");
         equal(retval, "relative urls not allowed: (relative.html<script>window.scriptRun=true;</script>)", "expected error");
         testErrorVisible();
@@ -296,7 +248,7 @@
     });
   });
 
-  asyncTest("get with valid tosURL & relative privacyURL - print error screen", function() {
+  asyncTest("get with valid termsOfService & relative privacyPolicy - print error screen", function() {
     createController({
       ready: function() {
         mediator.subscribe("start", function(msg, info) {
@@ -304,8 +256,8 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "/tos.html",
-          privacyURL: "relative.html"
+          termsOfService: "/tos.html",
+          privacyPolicy: "relative.html"
         });
         equal(retval, "relative urls not allowed: (relative.html)", "expected error");
         testErrorVisible();
@@ -314,7 +266,7 @@
     });
   });
 
-  asyncTest("get with script containing privacyURL - print error screen", function() {
+  asyncTest("get with script containing privacyPolicy - print error screen", function() {
     createController({
       ready: function() {
         mediator.subscribe("start", function(msg, info) {
@@ -322,11 +274,11 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "/tos.html",
-          privacyURL: "relative.html<script>window.scriptRun=true;</script>"
+          termsOfService: "/tos.html",
+          privacyPolicy: "relative.html<script>window.scriptRun=true;</script>"
         });
 
-        // If privacyURL is not properly escaped, scriptRun will be true.
+        // If privacyPolicy is not properly escaped, scriptRun will be true.
         equal(typeof window.scriptRun, "undefined", "script was not run");
         equal(retval, "relative urls not allowed: (relative.html<script>window.scriptRun=true;</script>)", "expected error");
         testErrorVisible();
@@ -335,7 +287,7 @@
     });
   });
 
-  asyncTest("get with privacyURL - print error screen", function() {
+  asyncTest("get with privacyPolicy - print error screen", function() {
     createController({
       ready: function() {
         mediator.subscribe("start", function(msg, info) {
@@ -343,11 +295,11 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "/tos.html",
-          privacyURL: "relative.html<script>window.scriptRun=true;</script>"
+          termsOfService: "/tos.html",
+          privacyPolicy: "relative.html<script>window.scriptRun=true;</script>"
         });
 
-        // If privacyURL is not properly escaped, scriptRun will be true.
+        // If privacyPolicy is not properly escaped, scriptRun will be true.
         equal(typeof window.scriptRun, "undefined", "script was not run");
         equal(retval, "relative urls not allowed: (relative.html<script>window.scriptRun=true;</script>)", "expected error");
         testErrorVisible();
@@ -356,7 +308,7 @@
     });
   });
 
-  asyncTest("get with javascript protocol for privacyURL - print error screen", function() {
+  asyncTest("get with javascript protocol for privacyPolicy - print error screen", function() {
     createController({
       ready: function() {
         mediator.subscribe("start", function(msg, info) {
@@ -364,8 +316,8 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "/tos.html",
-          privacyURL: "javascript:alert(1)"
+          termsOfService: "/tos.html",
+          privacyPolicy: "javascript:alert(1)"
         });
 
         equal(retval, "relative urls not allowed: (javascript:alert(1))", "expected error");
@@ -375,7 +327,7 @@
     });
   });
 
-  asyncTest("get with invalid httpg protocol for privacyURL - print error screen", function() {
+  asyncTest("get with invalid httpg protocol for privacyPolicy - print error screen", function() {
     createController({
       ready: function() {
         mediator.subscribe("start", function(msg, info) {
@@ -383,8 +335,8 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "/tos.html",
-          privacyURL: "httpg://testdomain.com/privacy.html"
+          termsOfService: "/tos.html",
+          privacyPolicy: "httpg://testdomain.com/privacy.html"
         });
 
         equal(retval, "relative urls not allowed: (httpg://testdomain.com/privacy.html)", "expected error");
@@ -395,7 +347,7 @@
   });
 
 
-  asyncTest("get with valid absolute tosURL & privacyURL - go to start", function() {
+  asyncTest("get with valid absolute termsOfService & privacyPolicy - go to start", function() {
     createController({
       ready: function() {
         var startInfo;
@@ -404,13 +356,13 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: "/tos.html",
-          privacyURL: "/privacy.html"
+          termsOfService: "/tos.html",
+          privacyPolicy: "/privacy.html"
         });
 
         testHelpers.testObjectValuesEqual(startInfo, {
-          tosURL: HTTP_TEST_DOMAIN + "/tos.html",
-          privacyURL: HTTP_TEST_DOMAIN + "/privacy.html"
+          termsOfService: HTTP_TEST_DOMAIN + "/tos.html",
+          privacyPolicy: HTTP_TEST_DOMAIN + "/privacy.html"
         });
 
         equal(typeof retval, "undefined", "no error expected");
@@ -420,7 +372,7 @@
     });
   });
 
-  asyncTest("get with valid fully qualified http tosURL & privacyURL - go to start", function() {
+  asyncTest("get with valid fully qualified http termsOfService & privacyPolicy - go to start", function() {
     createController({
       ready: function() {
         var startInfo;
@@ -429,13 +381,13 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: HTTP_TEST_DOMAIN + "/tos.html",
-          privacyURL: HTTP_TEST_DOMAIN + "/privacy.html"
+          termsOfService: HTTP_TEST_DOMAIN + "/tos.html",
+          privacyPolicy: HTTP_TEST_DOMAIN + "/privacy.html"
         });
 
         testHelpers.testObjectValuesEqual(startInfo, {
-          tosURL: HTTP_TEST_DOMAIN + "/tos.html",
-          privacyURL: HTTP_TEST_DOMAIN + "/privacy.html"
+          termsOfService: HTTP_TEST_DOMAIN + "/tos.html",
+          privacyPolicy: HTTP_TEST_DOMAIN + "/privacy.html"
         });
 
         equal(typeof retval, "undefined", "no error expected");
@@ -446,7 +398,7 @@
   });
 
 
-  asyncTest("get with valid fully qualified https tosURL & privacyURL - go to start", function() {
+  asyncTest("get with valid fully qualified https termsOfService & privacyPolicy - go to start", function() {
     createController({
       ready: function() {
         var startInfo;
@@ -455,13 +407,13 @@
         });
 
         var retval = controller.get(HTTP_TEST_DOMAIN, {
-          tosURL: HTTPS_TEST_DOMAIN + "/tos.html",
-          privacyURL: HTTPS_TEST_DOMAIN + "/privacy.html"
+          termsOfService: HTTPS_TEST_DOMAIN + "/tos.html",
+          privacyPolicy: HTTPS_TEST_DOMAIN + "/privacy.html"
         });
 
         testHelpers.testObjectValuesEqual(startInfo, {
-          tosURL: HTTPS_TEST_DOMAIN + "/tos.html",
-          privacyURL: HTTPS_TEST_DOMAIN + "/privacy.html"
+          termsOfService: HTTPS_TEST_DOMAIN + "/tos.html",
+          privacyPolicy: HTTPS_TEST_DOMAIN + "/privacy.html"
         });
         equal(typeof retval, "undefined", "no error expected");
         testErrorNotVisible();
diff --git a/resources/static/test/cases/dialog/js/modules/required_email.js b/resources/static/test/cases/dialog/js/modules/required_email.js
index 53fbed0c1c6c2290a752ec66f4c88cd2de427581..97800512f5c0df71e88c4529c20d7fc3e71b3658 100644
--- a/resources/static/test/cases/dialog/js/modules/required_email.js
+++ b/resources/static/test/cases/dialog/js/modules/required_email.js
@@ -95,17 +95,16 @@
   }
 
 
-  asyncTest("privacyURL and tosURL specified - show TOS/PP", function() {
+  asyncTest("siteTOSPP specified - show TOS/PP", function() {
     var email = "registered@testuser.com";
     xhr.useResult("known_secondary");
+    xhr.setContextInfo("auth_level", "password");
 
-    equal($(".tospp").length, 0, "tospp has not yet been added to the DOM");
     createController({
-      email: "registered@testuser.com",
-      privacyURL: "http://testuser.com/priv.html",
-      tosURL: "http://testuser.com/tos.html",
+      email: email,
+      siteTOSPP: true,
       ready: function() {
-        equal($(".tospp").length, 1, "tospp has been added to the DOM");
+        testHelpers.testRPTosPPShown();
         start();
       }
     });
diff --git a/resources/static/test/cases/dialog/js/modules/rp_info.js b/resources/static/test/cases/dialog/js/modules/rp_info.js
index 0b89868ab973653fcec4d665f707b5f77b5580dc..c50a3127c4253f84ef0663a2e1836b3db32ecfb5 100644
--- a/resources/static/test/cases/dialog/js/modules/rp_info.js
+++ b/resources/static/test/cases/dialog/js/modules/rp_info.js
@@ -13,8 +13,11 @@
       register = bid.TestHelpers.register,
       WindowMock = bid.Mocks.WindowMock,
       RP_HOSTNAME = "hostname.org",
-      RP_NAME = "RP Name",
-      RP_HTTPS_LOGO = "https://en.gravatar.com/userimage/6966791/c4feac761b8544cce13e0406f36230aa.jpg";
+      RP_NAME = "The Planet's Most Awesome Site",
+      RP_TOS_URL = "https://browserid.org/TOS.html",
+      RP_PP_URL = "https://browserid.org/priv.html",
+      RP_HTTPS_LOGO = "https://en.gravatar.com/userimage/6966791/c4feac761b8544cce13e0406f36230aa.jpg",
+      mediator = bid.Mediator;
 
   module("controllers/rp_info", {
     setup: testHelpers.setup,
@@ -84,5 +87,17 @@
     equal($("#rp_logo").attr("src"), RP_HTTPS_LOGO, "rp logo shown");
   });
 
+  test("privacyPolicy, termsOfService specified - show TOS/PP info", function() {
+    createController({
+      siteName: RP_NAME,
+      privacyPolicy: RP_PP_URL,
+      termsOfService: RP_TOS_URL
+    });
+
+    equal($("#rp_name").text(), RP_NAME, "RP's name is set");
+    equal($("#rp_tos").attr("href"), RP_TOS_URL, "RP's TOS is set");
+    equal($("#rp_pp").attr("href"), RP_PP_URL, "RP's Privacy Policy is set");
+  });
+
 }());
 
diff --git a/resources/static/test/cases/dialog/js/modules/set_password.js b/resources/static/test/cases/dialog/js/modules/set_password.js
index a1ae3b3c683434ebf000678e3b77943fbaf15d82..0d64302f07c16ac0042558e60dd26840fdc97db3 100644
--- a/resources/static/test/cases/dialog/js/modules/set_password.js
+++ b/resources/static/test/cases/dialog/js/modules/set_password.js
@@ -10,6 +10,8 @@
       el = $("body"),
       bid = BrowserID,
       testHelpers = bid.TestHelpers,
+      testElementExists = testHelpers.testElementExists,
+      testElementNotExists = testHelpers.testElementDoesNotExist,
       register = testHelpers.register,
       controller;
 
@@ -33,22 +35,29 @@
 
   test("create with no options - show template, user must verify email, can cancel", function() {
     ok($("#set_password").length, "set_password template added");
-    equal($("#verify_user").length, 1, "correct button shown");
-    equal($("#cancel").length, 1, "cancel button shown");
+    testElementExists("#verify_user");
+    testElementExists("#cancel");
+    testElementNotExists("#persona_tospp");
   });
 
   test("create with password_reset option - show template, show reset password button", function() {
     controller.destroy();
     createController({ password_reset: true });
-    ok($("#set_password").length, "set_password template added");
-    equal($("#password_reset").length, 1, "correct button shown");
-    equal($("#cancel").length, 1, "cancel button shown");
+    testElementExists("#set_password");
+    testElementExists("#password_reset");
+    testElementExists("#cancel");
+  });
+
+  test("create with personaTOSPP option - show Persona TOS/PP", function() {
+    controller.destroy();
+    createController({ personaTOSPP: true });
+    testElementExists("#persona_tospp");
   });
 
   test("create with cancelable=false option - cancel button not shown", function() {
     controller.destroy();
     createController({ cancelable: false });
-    equal($("#cancel").length, 0, "cancel button not shown");
+    testElementNotExists("#cancel");
   });
 
   asyncTest("submit with good password/vpassword - password_set message raised", function() {
diff --git a/resources/static/test/cases/dialog/js/modules/verify_primary_user.js b/resources/static/test/cases/dialog/js/modules/verify_primary_user.js
index 5203792b726a532a914f7ef376e269a4f1e86863..08083a9fe9d3d0ef7908a0d18c1e958a8bed00e9 100644
--- a/resources/static/test/cases/dialog/js/modules/verify_primary_user.js
+++ b/resources/static/test/cases/dialog/js/modules/verify_primary_user.js
@@ -10,6 +10,8 @@
       controller,
       el,
       testHelpers = bid.TestHelpers,
+      testElementExists = testHelpers.testElementExists,
+      testElementNotExists = testHelpers.testElementDoesNotExist,
       WindowMock = bid.Mocks.WindowMock,
       win,
       mediator = bid.Mediator;
@@ -33,41 +35,44 @@
     }
   });
 
-  test("create with privacyURL and tosURL defined - show TOS/PP", function() {
+  test("personaTOSPP true, requiredEmail: true - show TOS/PP", function() {
     createController({
       window: win,
       add: false,
       email: "unregistered@testuser.com",
       auth_url: "http://testuser.com/sign_in",
-      privacyURL: "http://testuser.com/priv.html",
-      tosURL: "http://testuser.com/tos.html"
+      requiredEmail: true,
+      personaTOSPP: false
     });
 
-    equal($(".tospp").length, 1, "tospp has been added to the DOM");
+    testElementNotExists("#persona_tospp");
   });
 
-  test("create with requiredEmail, privacyURL and tosURL defined - show TOS/PP", function() {
+  test("personaTOSPP true, requiredEmail: false - show TOS/PP", function() {
     createController({
       window: win,
       add: false,
-      requiredEmail: "unregistered@testuser.com",
       email: "unregistered@testuser.com",
       auth_url: "http://testuser.com/sign_in",
-      privacyURL: "http://testuser.com/priv.html",
-      tosURL: "http://testuser.com/tos.html"
+      requiredEmail: false,
+      personaTOSPP: false
     });
 
-    equal($(".tospp").length, 1, "tospp has been added to the DOM");
+    testElementNotExists("#persona_tospp");
   });
 
-  asyncTest("submit with `add: false` option opens a new tab with CREATE_EMAIL URL", function() {
+  asyncTest("submit with `add: false` option opens a new tab with proper URL (updated for sessionStorage)", function() {
     var messageTriggered = false;
     createController({
       window: win,
       add: false,
       email: "unregistered@testuser.com",
-      auth_url: "http://testuser.com/sign_in"
+      auth_url: "http://testuser.com/sign_in",
+      personaTOSPP: true
     });
+
+    testElementExists("#persona_tospp");
+
     mediator.subscribe("primary_user_authenticating", function() {
       messageTriggered = true;
     });
@@ -77,26 +82,29 @@
     win.document.location.hash = "#NATIVE";
 
     controller.submit(function() {
-      equal(win.document.location, "http://testuser.com/sign_in?email=unregistered%40testuser.com&return_to=sign_in%23CREATE_EMAIL%3Dunregistered%40testuser.com");
+      equal(win.document.location, "http://testuser.com/sign_in?email=unregistered%40testuser.com");
       equal(messageTriggered, true, "primary_user_authenticating triggered");
       start();
     });
   });
 
-  asyncTest("submit with `add: true` option opens a new tab with ADD_EMAIL URL", function() {
+  asyncTest("submit with `add: true` option opens a new tab with proper URL (updated for sessionStorage)", function() {
     createController({
       window: win,
       add: true,
       email: "unregistered@testuser.com",
-      auth_url: "http://testuser.com/sign_in"
+      auth_url: "http://testuser.com/sign_in",
+      personaTOSPP: true
     });
 
+    testElementExists("#persona_tospp");
+
     // Also checking to make sure the NATIVE is stripped out.
     win.document.location.href = "sign_in";
     win.document.location.hash = "#NATIVE";
 
     controller.submit(function() {
-      equal(win.document.location, "http://testuser.com/sign_in?email=unregistered%40testuser.com&return_to=sign_in%23ADD_EMAIL%3Dunregistered%40testuser.com");
+      equal(win.document.location, "http://testuser.com/sign_in?email=unregistered%40testuser.com");
       start();
     });
   });
diff --git a/resources/static/test/mocks/window.js b/resources/static/test/mocks/window.js
index 510514288a7897747ba90d6c00b38be5641a0ba4..a633ba0d20180139f7bcca5d6230f0d30379c1aa 100644
--- a/resources/static/test/mocks/window.js
+++ b/resources/static/test/mocks/window.js
@@ -15,6 +15,7 @@ BrowserID.Mocks.WindowMock = (function() {
 
   function WindowMock() {
     this.document = new DocumentMock();
+    this.sessionStorage = {};
   }
   WindowMock.prototype = {
     open: function(url, name, options) {
diff --git a/resources/static/test/testHelpers/helpers.js b/resources/static/test/testHelpers/helpers.js
index 119bd73f64a8f23eec6b6655e63781e11459b751..66868076a8c8cd477b25b2db0e5e1e6023595037 100644
--- a/resources/static/test/testHelpers/helpers.js
+++ b/resources/static/test/testHelpers/helpers.js
@@ -221,10 +221,6 @@ BrowserID.TestHelpers = (function() {
       }
     },
 
-    testHasClass: function(selector, className, msg) {
-      ok($(selector).hasClass(className), msg || selector + " has className: " + className);
-    },
-
     testUndefined: function(toTest, msg) {
       equal(typeof toTest, "undefined", msg || "object is undefined");
     },
@@ -235,8 +231,33 @@ BrowserID.TestHelpers = (function() {
 
     testVisible: function(selector, msg) {
       ok($(selector).is(":visible"), msg || selector + " should be visible");
-    }
+    },
 
+    testHasClass: function(selector, className, msg) {
+      ok($(selector).hasClass(className),
+          msg || (selector + " has className " + className));
+    },
+
+    testNotHasClass: function(selector, className, msg) {
+      ok(!$(selector).hasClass(className),
+          msg || (selector + " does not have className " + className));
+    },
+
+    testElementExists: function(selector, msg) {
+      ok($(selector).length, msg || ("element '" + selector + "' exists"));
+    },
+
+    testElementDoesNotExist: function(selector, msg) {
+      ok(!$(selector).length, msg || ("element '" + selector + "' does not exist"));
+    },
+
+    testRPTosPPShown: function(msg) {
+      TestHelpers.testHasClass("body", "rptospp", msg || "RP TOS/PP shown");
+    },
+
+    testRPTosPPNotShown: function(msg) {
+      TestHelpers.testNotHasClass("body", "rptospp", msg || "RP TOS/PP not shown");
+    }
   };
 
   return TestHelpers;
diff --git a/resources/views/about.ejs b/resources/views/about.ejs
index b16b28ac52a5a143b96774b0c7622c6b9e29b890..4ea433948b01e4d7b87b5c5957fad13fcda54322 100644
--- a/resources/views/about.ejs
+++ b/resources/views/about.ejs
@@ -13,13 +13,13 @@
                 </div>
 
                 <div class="graphic">
-                    <img src="/pages/i/one-password-graphic.png" alt="One password to rule them all.">
+                    <img src="<%- cachify('/pages/i/one-password-graphic.png') %>" alt="One password to rule them all.">
                 </div>
             </article>
 
             <article class="blurb flexible">
                 <div class="graphic first">
-                    <img src="/pages/i/flexible-graphic.png" alt="Use multiple email addresses">
+                    <img src="<%- cachify('/pages/i/flexible-graphic.png') %>" alt="Use multiple email addresses">
                 </div>
 
                 <div class="info">
@@ -42,7 +42,7 @@
             </article>
         </section>
 
-        <a href="https://developer.mozilla.org/en/BrowserID/Quick_Setup" class="developers"><img src="/pages/i/developers-link.png" alt="Persona for developers"><span>Implement Persona on your site </span>Developer guides and API documentation</a>
+        <a href="https://developer.mozilla.org/en/BrowserID/Quick_Setup" class="developers"><img src="<%- cachify('/pages/i/developers-link.png') %>" alt="Persona for developers"><span>Implement Persona on your site </span>Developer guides and API documentation</a>
     </div><!-- #dashboard -->
 </div>
 
diff --git a/resources/views/layout.ejs b/resources/views/layout.ejs
index bfaa83682d1ecbd45b97896476d22b672b498aef..cbc7cc078e9ea6f1f6e0dbb0f6b13347196e1094 100644
--- a/resources/views/layout.ejs
+++ b/resources/views/layout.ejs
@@ -8,7 +8,7 @@
   <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, width=device-width" />
   <meta name="format-detection" content="email=no" />
   <!--[if lt IE 9]>
-    <script src="/common/js/lib/html5shim.js"></script>
+    <%- cachify_js('/production/html5shim.js') %>
   <![endif]-->
   <%- cachify_css('/production/browserid.css') %>
   <!--[if lt IE 9]>
diff --git a/resources/views/signup.ejs b/resources/views/signup.ejs
index dcf66e0702c932803c7ca991217b509a4e69323e..9e92bd83b6e5be5865af429ece9f1b226ddd9cb6 100644
--- a/resources/views/signup.ejs
+++ b/resources/views/signup.ejs
@@ -82,10 +82,20 @@
             </ul>
 
             <div class="submit cf forminputs">
-                <button>Verify Email</button>
-                <div class="remember cf">
-                    <a class="action" href="/signin" tabindex="2">Existing account? Sign in.</a>
-                </div>
+                <p class="cf">
+                  <button>Verify Email</button>
+                  <a class="action remember" href="/signin" tabindex="2">Existing account? Sign in.</a>
+                </p>
+
+                <p class="tospp">
+                   <%- format(
+                        gettext('By proceeding, you agree to %s\'s <a %s>Terms</a> and <a %s>Privacy Policy</a>.'),
+                             [ "Persona",
+                               ' href="https://login.persona.org/tos" target="_new"',
+                               ' href="https://login.persona.org/privacy" target="_new"',
+                             ]) %>
+                  </p>
+
             </div>
 
             <ul class="notifications">
diff --git a/scripts/compress b/scripts/compress
index 7c8b797d6157699f19f7eb316bc7cb6ff9670e97..55e5a241f3cc1758d256c607b7c7fc233b19e871 100755
--- a/scripts/compress
+++ b/scripts/compress
@@ -41,7 +41,7 @@ Object.keys(all).forEach(function(resource) {
   // in prod '/build/templates.js' has all templates glommed into it,
   // and is bundled into the Big Minified Piles Of Resources we ship.
   // Here we sub the former with the latter.
-  var ix = all[resource].indexOf('/shared/templates.js');
+  var ix = all[resource].indexOf('/common/js/templates.js');
   if (ix != -1) all[resource].splice(ix, 1, '/build/templates.js');
 
   cc.enqueue({
diff --git a/tests/static-resource-test.js b/tests/static-resource-test.js
index ceda594e43ea9f697b56507f2b909bf9f66286f8..66a83a9ba88fb4ef3428474f635e8acfc5ac9142 100755
--- a/tests/static-resource-test.js
+++ b/tests/static-resource-test.js
@@ -23,7 +23,7 @@ suite.addBatch({
       var res = resources.resources;
       assert.ok(files['/production/dialog.css'].length >= 3);
       // Get ride of non-localized asset bundles
-      ['/production/communication_iframe.js', '/production/include.js', '/production/dialog.css', '/production/browserid.css', '/production/ie8_main.css', '/production/ie8_dialog.css', '/production/relay.js'].forEach(
+      ['/production/communication_iframe.js', '/production/include.js', '/production/dialog.css', '/production/browserid.css', '/production/ie8_main.css', '/production/ie8_dialog.css', '/production/relay.js', '/production/html5shim.js'].forEach(
         function (nonLocaleAsset) {
           delete res[nonLocaleAsset];
           delete files[nonLocaleAsset];