diff --git a/resources/static/dialog/controllers/required_email.js b/resources/static/dialog/controllers/required_email.js index 1715578d451c8eb465ff64d560c9975db8803c2c..205c1df7650415dfcbfe2e91e1cad0a6a8aa4dd6 100644 --- a/resources/static/dialog/controllers/required_email.js +++ b/resources/static/dialog/controllers/required_email.js @@ -127,6 +127,12 @@ BrowserID.Modules.RequiredEmail = (function() { }); ready(); } + else if(emailInfo && emailInfo.cert) { + // primary user with valid cert, user can sign in normally. + primaryInfo = emailInfo; + showTemplate({ signin: true, primary: true }); + ready(); + } else { // At this point, there are several possibilities: // 1) Authenticated primary user who has valid cert. @@ -136,9 +142,7 @@ BrowserID.Modules.RequiredEmail = (function() { user.addressInfo(email, function(info) { if(info.type === "primary") primaryInfo = info; - if (info.authed || (emailInfo && emailInfo.cert)) { - // primary user with valid cert, user can sign in normally. - // OR + if (info.authed) { // this is a primary user who is authenticated with their IdP. // We know the user has control of this address, give them // a chance to hit "sign in" before we kick them off to the diff --git a/resources/static/dialog/resources/state_machine.js b/resources/static/dialog/resources/state_machine.js index 258a5e5ddedcaae47f8fc4d34eb34eed75e1a421..76717c5068e974b54091248130f413ff4b49d9e9 100644 --- a/resources/static/dialog/resources/state_machine.js +++ b/resources/static/dialog/resources/state_machine.js @@ -143,7 +143,13 @@ // We don't want to put the provisioning step on the stack, instead when // a user cancels this step, they should go back to the step before the // provisioning. - startState(false, "doProvisionPrimaryUser", info); + var idInfo = storage.getEmail(email); + if(idInfo && idInfo.cert) { + mediator.publish("primary_user_ready", info); + } + else { + startState(false, "doProvisionPrimaryUser", info); + } }); subscribe("primary_user_provisioned", function(msg, info) { diff --git a/resources/static/pages/page_helpers.js b/resources/static/pages/page_helpers.js index 06d29ce4c31426bc02d9b5bcb4801d9d4888408c..7121f70ed998b9ac35e7ffed7d4969b998afc2ff 100644 --- a/resources/static/pages/page_helpers.js +++ b/resources/static/pages/page_helpers.js @@ -9,6 +9,7 @@ BrowserID.PageHelpers = (function() { var win = window, locStorage = win.localStorage, bid = BrowserID, + user = bid.User, helpers = bid.Helpers, dom = bid.DOM, errorDisplay = bid.ErrorDisplay, @@ -137,7 +138,16 @@ BrowserID.PageHelpers = (function() { relay_url: "https://browserid.org/relay", window_features: "width=700,height=375", params: url - }, callback); + }, function(error, result) { + // We have to force a reset of the primary caches because the user's + // authentication status may be incorrect. + // XXX a better solution here would be to change the authentication + // status of the user inside of the cache. + if(!error) { + user.resetCaches(); + } + callback && callback(error, result); + }); } return { diff --git a/resources/static/pages/signin.js b/resources/static/pages/signin.js index fdf47f34ad689de484c0a0a705f49b0526d8d14e..27633981de20533c67c13fd6bf90c66aecd8a159 100644 --- a/resources/static/pages/signin.js +++ b/resources/static/pages/signin.js @@ -37,7 +37,8 @@ BrowserID.signIn = (function() { }, pageHelpers.getFailure(errors.authenticateWithAssertion, callback)); } else { - $("#primary_no_login").fadeIn(); + $("#primary_no_login").fadeIn(250); + setTimeout(complete.curry(callback), 250); } }, pageHelpers.getFailure(errors.provisioningPrimary, callback)); } diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js index d0d7420eb0c4918ad0a566bc24a09dcb5c42702e..268cd7f5afe2ba3758f7041b28f1770986e31831 100644 --- a/resources/static/shared/user.js +++ b/resources/static/shared/user.js @@ -13,7 +13,8 @@ BrowserID.User = (function() { storage = bid.Storage, User, pollTimeout, provisioning = bid.Provisioning, - addressCache = {}; + addressCache = {}, + primaryAuthCache = {}; function prepareDeps() { if (!jwk) { @@ -205,7 +206,12 @@ BrowserID.User = (function() { reset: function() { provisioning = BrowserID.Provisioning; + User.resetCaches(); + }, + + resetCaches: function() { addressCache = {}; + primaryAuthCache = {}; }, /** @@ -389,6 +395,31 @@ BrowserID.User = (function() { * @param {function} [onFailure] - called on failure */ primaryUserAuthenticationInfo: function(email, info, onComplete, onFailure) { + var idInfo = storage.getEmail(email), + self=this; + + primaryAuthCache = primaryAuthCache || {}; + + function complete(info) { + primaryAuthCache[email] = info; + onComplete && _.defer(onComplete.curry(info)); + } + + if(primaryAuthCache[email]) { + // If we have the info in our cache, we most definitely do not have to + // ask for it. + complete(primaryAuthCache[email]); + return; + } + else if(idInfo && idInfo.cert) { + // If we already have the info in storage, we know the user has a valid + // cert with their IdP, we say they are authenticated and pass back the + // appropriate info. + var userInfo = _.extend({authenticated: true}, idInfo, info); + complete(userInfo); + return; + } + provisioning( { email: email, url: info.prov }, function(keypair, cert) { @@ -398,14 +429,14 @@ BrowserID.User = (function() { authenticated: true }, info); - onComplete(userInfo); + complete(userInfo); }, function(error) { if (error.code === "primaryError" && error.msg === "user is not authenticated as target user") { var userInfo = _.extend({ authenticated: false }, info); - onComplete(userInfo); + complete(userInfo); } else { onFailure(info); diff --git a/resources/static/test/qunit/mocks/winchan.js b/resources/static/test/qunit/mocks/winchan.js index 7ba3c211520e69c1f10def2579cde4213d749b57..43ecf8dc51c519fdf173298db27290ec23ac2d78 100644 --- a/resources/static/test/qunit/mocks/winchan.js +++ b/resources/static/test/qunit/mocks/winchan.js @@ -12,6 +12,7 @@ BrowserID.Mocks.WinChan = (function() { open: function(params, callback) { this.params = params; this.oncomplete = callback; + callback && callback(null, "yar"); }, onOpen: function() { diff --git a/resources/static/test/qunit/pages/signin_unit_test.js b/resources/static/test/qunit/pages/signin_unit_test.js index 560c95e889b475593a932bff3eace8e280607bd2..d03a0855648e59e9c7b9edc430155a612ea10f6d 100644 --- a/resources/static/test/qunit/pages/signin_unit_test.js +++ b/resources/static/test/qunit/pages/signin_unit_test.js @@ -179,6 +179,9 @@ controller.emailSubmit(function() { controller.authWithPrimary(function() { provisioning.setStatus(provisioning.AUTHENTICATED); + // Before primaryAuthComplete is called, we reset the user caches to + // force re-fetching of what could have been stale user data. + user.resetCaches(); controller.primaryAuthComplete(null, "yar", function() { network.checkAuth(function(status) { diff --git a/resources/static/test/qunit/pages/signup_unit_test.js b/resources/static/test/qunit/pages/signup_unit_test.js index 37422f5f660a1e992681e9239cbbbec11e6c4fac..6fe113b2b6ad8e6c5f08a01da9a3d7bc1733a6f7 100644 --- a/resources/static/test/qunit/pages/signup_unit_test.js +++ b/resources/static/test/qunit/pages/signup_unit_test.js @@ -7,6 +7,7 @@ "use strict"; var bid = BrowserID, + user = bid.User, network = bid.Network, xhr = bid.Mocks.xhr, WinChanMock = bid.Mocks.WinChan, @@ -174,6 +175,10 @@ bid.signUp.authWithPrimary(function() { // In real life the user would now be authenticated. provisioning.setStatus(provisioning.AUTHENTICATED); + + // Before primaryAuthComplete is called, we reset the user caches to + // force re-fetching of what could have been stale user data. + user.resetCaches(); bid.signUp.primaryAuthComplete(null, "success", function(status) { equal(status, true, "correct status"); equal($("#congrats:visible").length, 1, "success notification is visible"); diff --git a/resources/static/test/qunit/resources/state_machine_unit_test.js b/resources/static/test/qunit/resources/state_machine_unit_test.js index 8ee06c79ffd9dd616dcdf995d2317bf2fa7b1725..19609c205465fc9e2ec16f06c47db5e378558923 100644 --- a/resources/static/test/qunit/resources/state_machine_unit_test.js +++ b/resources/static/test/qunit/resources/state_machine_unit_test.js @@ -81,7 +81,13 @@ ok(actions.called.doEmailConfirmed, "user was confirmed"); }); - test("primary_user calls doProvisionPrimaryUser", function() { + test("primary_user with already provisioned primary user calls doEmailChosen", function() { + storage.addEmail("testuser@testuser.com", { type: "primary", cert: "cert" }); + mediator.publish("primary_user", { email: "testuser@testuser.com" }); + ok(actions.called.doEmailChosen, "doEmailChosen called"); + }); + + test("primary_user with unprovisioned primary user doProvisionPrimaryUser", function() { mediator.publish("primary_user", { email: "testuser@testuser.com" }); ok(actions.called.doProvisionPrimaryUser, "doPrimaryUserProvisioned called"); });