diff --git a/bin/browserid b/bin/browserid index d4fcfc3c749f7d8f8dceab1186c26ffadedcee1b..e1db01419e9a46588e9280758af75b5fdf11cfe9 100755 --- a/bin/browserid +++ b/bin/browserid @@ -8,7 +8,7 @@ const fs = require('fs'), path = require('path'), url = require('url'), -http = require('http'); +http = require('http'), urlparse = require('urlparse'), express = require('express'); @@ -62,7 +62,7 @@ app.use(i18n.abide({ var statsd_config = config.get('statsd'); if (statsd_config && statsd_config.enabled) { - logger_statsd = require("connect-logger-statsd"); + var logger_statsd = require("connect-logger-statsd"); app.use(logger_statsd({ host: statsd_config.hostname || "localhost", port: statsd_config.port || 8125, @@ -157,7 +157,7 @@ db.open(config.get('database'), function (error) { // shut down express gracefully on SIGINT shutdown.handleTerminationSignals(app, function(readyForShutdownCB) { require('../lib/bcrypt.js').shutdown(); - db.close(readyForShutdownCB) + db.close(readyForShutdownCB); }); var bindTo = config.get('bind_to'); 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/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/dialog/js/modules/dialog.js b/resources/static/dialog/js/modules/dialog.js index 7e40c8c531c50ae78e90a79a06045908ef0a2b49..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, @@ -155,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... @@ -192,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 + ")"; - 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 + ")"; + 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 = 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/verify_primary_user.js b/resources/static/dialog/js/modules/verify_primary_user.js index c92fdb25219fcb4f666c1ff5e3493e41b9f2c7c5..8a35914e2665c6a07d5814f439da440e5699a752 100644 --- a/resources/static/dialog/js/modules/verify_primary_user.js +++ b/resources/static/dialog/js/modules/verify_primary_user.js @@ -20,15 +20,14 @@ BrowserID.Modules.VerifyPrimaryUser = (function() { 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); 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/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/test/cases/dialog/js/modules/dialog.js b/resources/static/test/cases/dialog/js/modules/dialog.js index e7a75af818d6174a4737a623b8c99df74ca15066..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,64 +208,6 @@ }); }); - 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 termsOfService & valid privacyPolicy - print error screen", function() { createController({ 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 bde1c0d47b4396c0433077e6796ed4d4b6ad568b..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 @@ -61,8 +61,7 @@ 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, @@ -83,13 +82,13 @@ 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, @@ -105,7 +104,7 @@ 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/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/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];