diff --git a/resources/static/common/js/user.js b/resources/static/common/js/user.js index c5433499a1e4f4a4ae765dc7d0a8ddb6f7fbfaeb..24e2f621fcb4b6d87f8acfe41763cfc86e573f5c 100644 --- a/resources/static/common/js/user.js +++ b/resources/static/common/js/user.js @@ -629,14 +629,19 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on XHR failure. */ requestPasswordReset: function(email, password, onComplete, onFailure) { - User.isEmailRegistered(email, function(registered) { - if (registered) { + User.addressInfo(email, function(info) { + // user is not known. Can't request a password reset. + if (!info.known) { + complete(onComplete, { success: false, reason: "invalid_user" }); + } + // user is trying to reset the password of a primary address. + else if (info.type === "primary") { + complete(onComplete, { success: false, reason: "primary_address" }); + } + else { network.requestPasswordReset(email, password, origin, handleStageAddressVerifictionResponse.curry(onComplete), onFailure); } - else if (onComplete) { - onComplete({ success: false, reason: "invalid_user" }); - } }, onFailure); }, @@ -949,10 +954,13 @@ BrowserID.User = (function() { network.addressInfo(email, function(info) { info.email = email; if(info.type === "primary") { - User.isUserAuthenticatedToPrimary(email, info, function(authed) { - info.authed = authed; - info.idpName = getIdPName(info); - complete(info); + User.isEmailRegistered(email, function(registered) { + User.isUserAuthenticatedToPrimary(email, info, function(authed) { + info.known = registered; + info.authed = authed; + info.idpName = getIdPName(info); + complete(info); + }, onFailure); }, onFailure); } else { diff --git a/resources/static/pages/js/forgot.js b/resources/static/pages/js/forgot.js index c8e2da96d4d6d6fb2f4939c3b5bb42852dbffee2..300e2a95f8a480895616eda46a3efac22f4d861e 100644 --- a/resources/static/pages/js/forgot.js +++ b/resources/static/pages/js/forgot.js @@ -17,8 +17,7 @@ BrowserID.forgot = (function() { tooltip = bid.Tooltip; function submit(oncomplete) { - // GET RID OF THIS HIDE CRAP AND USE CSS! - $(".notifications .notification").hide(); + dom.hide(".notification"); var email = helpers.getAndValidateEmail("#email"), pass = dom.getInner("#password"), @@ -31,41 +30,86 @@ BrowserID.forgot = (function() { pageHelpers.emailSent("waitForPasswordResetComplete", email, oncomplete); } else { - var tooltipEl = info.reason === "throttle" ? "#could_not_add" : "#not_registered"; - tooltip.showTooltip(tooltipEl); + var tooltipEls = { + throttle: "#could_not_add", + invalid_user: "#not_registered", + primary_address: "#primary_address" + }; + + var tooltipEl = tooltipEls[info.reason]; + if (tooltipEl) { + tooltip.showTooltip(tooltipEl); + } complete(oncomplete); } }, pageHelpers.getFailure(bid.Errors.requestPasswordReset, oncomplete)); } else { complete(oncomplete); } - }; + } function back(oncomplete) { pageHelpers.cancelEmailSent(oncomplete); } - function init() { - $("form input[autofocus]").focus(); + function redirectIfNeeded(doc, ready) { + // email addresses are stored if the user is coming from the signin or + // signup page. If no email address is stored, the user browsed here + // directly. If the user browsed here directly, kick them back to the + // sign in page. + var email = pageHelpers.getStoredEmail(); + if (!email) { + doc.location.href = "/signin"; + complete(ready); + return; + } - pageHelpers.setupEmail(); + // We know an email address was stored, now check if it is registered. If + // it is not registered, kick the user over to the signup page. If it is + // registered, but a primary, kick them over to the signin page. + user.addressInfo(email, function(info) { + if (!info.known) { + doc.location.href = "/signup"; + } + else if(info.type === "primary") { + doc.location.href="/signin"; + } - dom.bindEvent("form", "submit", cancelEvent(submit)); - dom.bindEvent("#back", "click", cancelEvent(back)); + complete(ready); + }); } - // BEGIN TESTING API - function reset() { - dom.unbindEvent("form", "submit"); - dom.unbindEvent("#back", "click"); - } + var Module = bid.Modules.PageModule.extend({ + start: function(options) { + options = options || {}; + + var self=this, + doc = options.document || document; + + // Check whether a redirection needs to happen before showing the rest of + // the content. + redirectIfNeeded(doc, function() { + dom.focus("form input[autofocus]"); + + pageHelpers.setupEmail(); + + self.bind("form", "submit", cancelEvent(submit)); + self.click("#back", back); + + Module.sc.init.call(self, options); + + complete(options.ready); + }); + } - init.submit = submit; - init.reset = reset; - init.back = back; - // END TESTING API + // BEGIN TESTING API + , + submit: submit, + back: back + // END TESTING API + }); - return init; + return Module; }()); diff --git a/resources/static/pages/js/start.js b/resources/static/pages/js/start.js index 740b1f0bd3e0692a64b529430697ab004b92ebab..03f66f0ee36292d52850ada737e7881ea5ae0db3 100644 --- a/resources/static/pages/js/start.js +++ b/resources/static/pages/js/start.js @@ -139,7 +139,8 @@ $(function() { module.start({}); } else if (path === "/forgot") { - bid.forgot(); + var module = bid.forgot.create(); + module.start({}); } // START TRANSITION CODE // add_email_address has been renamed to confirm. Once all outstanding diff --git a/resources/static/test/cases/common/js/user.js b/resources/static/test/cases/common/js/user.js index 46f7a2376315a1cc18af304fd5f0d8b429cd48b0..94dd1b05677c3cbf96c1c3e7489d004223d80b7c 100644 --- a/resources/static/test/cases/common/js/user.js +++ b/resources/static/test/cases/common/js/user.js @@ -1291,31 +1291,57 @@ ); }); - asyncTest("addressInfo with primary authenticated user", function() { + asyncTest("addressInfo with unknown primary authenticated user", function() { + xhr.useResult("primary"); + provisioning.setStatus(provisioning.AUTHENTICATED); + lib.addressInfo( + "unregistered@testuser.com", + function(info) { + testObjectValuesEqual(info, { + type: "primary", + email: "unregistered@testuser.com", + authed: true, + idpName: "testuser.com", + known: false + }); + start(); + }, + testHelpers.unexpectedFailure + ); + }); + + asyncTest("addressInfo with known primary authenticated user", function() { xhr.useResult("primary"); provisioning.setStatus(provisioning.AUTHENTICATED); lib.addressInfo( "registered@testuser.com", function(info) { - equal(info.type, "primary", "correct type"); - equal(info.email, "registered@testuser.com", "correct email"); - equal(info.authed, true, "user is authenticated with IdP"); - equal(info.idpName, "testuser.com", "unknown IdP, use email host portion for name"); + testObjectValuesEqual(info, { + type: "primary", + email: "registered@testuser.com", + authed: true, + idpName: "testuser.com", + known: true + }); start(); }, testHelpers.unexpectedFailure ); }); - asyncTest("addressInfo with primary unauthenticated user", function() { + asyncTest("addressInfo with known primary unauthenticated user", function() { xhr.useResult("primary"); provisioning.setStatus(provisioning.NOT_AUTHENTICATED); lib.addressInfo( "registered@testuser.com", function(info) { - equal(info.type, "primary", "correct type"); - equal(info.email, "registered@testuser.com", "correct email"); - equal(info.authed, false, "user is not authenticated with IdP"); + testObjectValuesEqual(info, { + type: "primary", + email: "registered@testuser.com", + authed: false, + idpName: "testuser.com", + known: true + }); start(); }, testHelpers.unexpectedFailure diff --git a/resources/static/test/cases/pages/js/forgot.js b/resources/static/test/cases/pages/js/forgot.js index d6179186f4f6b1eb5cf421e2ad56e4d6ec12e09b..d8252629c0280be490d288bc580fd10f6ef6afb2 100644 --- a/resources/static/test/cases/pages/js/forgot.js +++ b/resources/static/test/cases/pages/js/forgot.js @@ -10,23 +10,36 @@ network = bid.Network, user = bid.User, testHelpers = bid.TestHelpers, - xhr = bid.Mocks.xhr; + pageHelpers = bid.PageHelpers, + xhr = bid.Mocks.xhr, + WindowMock = bid.Mocks.WindowMock, + controller, + docMock; + + function createController(options) { + options = options || {}; + + docMock = new WindowMock().document; + options.document = docMock; + + controller = bid.forgot.create(); + controller.start(options); + } module("pages/js/forgot", { setup: function() { testHelpers.setup(); bid.Renderer.render("#page_head", "site/forgot", {}); - bid.forgot(); + createController(); }, teardown: function() { testHelpers.teardown(); - bid.forgot.reset(); } }); function testEmailNotSent(config) { config = config || {}; - bid.forgot.submit(function() { + controller.submit(function() { equal($(".emailsent").is(":visible"), false, "email not sent"); if(config.checkTooltip !== false) testHelpers.testTooltipVisible(); if (config.ready) config.ready(); @@ -34,7 +47,38 @@ }); } - asyncTest("requestPasswordReset with invalid email", function() { + test("start with no stored email - redirect to /signin", function() { + equal(docMock.location.href, "/signin", "page redirected to signin if no email stored"); + }); + + asyncTest("start with stored primary email - redirect to /signin", function() { + xhr.useResult("primary"); + pageHelpers.setStoredEmail("testuser@testuser.com"); + createController({ + ready: function() { + equal(docMock.location.href, "/signin", "page redirected to signin if primary email stored"); + start(); + } + }); + }); + + asyncTest("start with stored unknown secondary email - redirect to /signup", function() { + pageHelpers.setStoredEmail("unregistered@testuser.com"); + createController({ + ready: function() { + equal(docMock.location.href, "/signup", "page redirected to signup if unknown secondary email stored"); + start(); + } + }); + }); + + test("start with stored known secondary email - no redirection", function() { + pageHelpers.setStoredEmail("testuser@testuser.com"); + createController(); + equal(docMock.location.href, document.location.href, "no page redirection if known secondary is stored"); + }); + + asyncTest("submit with invalid email", function() { $("#email").val("invalid"); $("#password,#vpassword").val("password"); @@ -43,62 +87,62 @@ testEmailNotSent(); }); - asyncTest("requestPasswordReset with known email, happy case - show email sent notice", function() { + asyncTest("submit with known secondary email, happy case - show email sent notice", function() { $("#email").val("registered@testuser.com"); $("#password,#vpassword").val("password"); - bid.forgot.submit(function() { + controller.submit(function() { ok($(".emailsent").is(":visible"), "email sent successfully"); start(); }); }); - asyncTest("requestPasswordReset with known email with leading/trailing whitespace - show email sent notice", function() { + asyncTest("submit with known secondary email with leading/trailing whitespace - show email sent notice", function() { $("#email").val(" registered@testuser.com "); $("#password,#vpassword").val("password"); - bid.forgot.submit(function() { + controller.submit(function() { ok($(".emailsent").is(":visible"), "email sent successfully"); start(); }); }); - asyncTest("requestPasswordReset with missing password", function() { + asyncTest("submit with missing password", function() { $("#email").val("unregistered@testuser.com"); $("#vpassword").val("password"); testEmailNotSent(); }); - asyncTest("requestPasswordReset with too short of a password", function() { + asyncTest("submit with too short of a password", function() { $("#email").val("unregistered@testuser.com"); $("#password,#vpassword").val(testHelpers.generateString(bid.PASSWORD_MIN_LENGTH - 1)); testEmailNotSent(); }); - asyncTest("requestPasswordReset with too long of a password", function() { + asyncTest("submit with too long of a password", function() { $("#email").val("unregistered@testuser.com"); $("#password,#vpassword").val(testHelpers.generateString(bid.PASSWORD_MAX_LENGTH + 1)); testEmailNotSent(); }); - asyncTest("requestPasswordReset with missing vpassword", function() { + asyncTest("submit with missing vpassword", function() { $("#email").val("unregistered@testuser.com"); $("#password").val("password"); testEmailNotSent(); }); - asyncTest("requestPasswordReset with unknown email", function() { + asyncTest("submit with unknown secondary email", function() { $("#email").val("unregistered@testuser.com"); $("#password,#vpassword").val("password"); testEmailNotSent(); }); - asyncTest("requestPasswordReset with throttling", function() { + asyncTest("submit with throttling", function() { $("#email").val("registered@testuser.com"); $("#password,#vpassword").val("password"); @@ -106,7 +150,7 @@ testEmailNotSent(); }); - asyncTest("requestPasswordReset with XHR Error", function() { + asyncTest("submit with XHR Error", function() { $("#email").val("testuser@testuser.com"); $("#password,#vpassword").val("password"); diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js index 1a23e87b0644c0b73c6e8140268008886d19a011..c5abae2d3c003c8baf1e451e33ac918ee3575799 100644 --- a/resources/static/test/mocks/xhr.js +++ b/resources/static/test/mocks/xhr.js @@ -108,10 +108,12 @@ BrowserID.Mocks.xhr = (function() { "get /wsapi/have_email?email=registered%40testuser.com throttle": { email_known: true }, "get /wsapi/have_email?email=registered%40testuser.com ajaxError": undefined, "get /wsapi/have_email?email=testuser%40testuser.com valid": { email_known: true }, + "get /wsapi/have_email?email=testuser%40testuser.com primary": { email_known: true }, "get /wsapi/have_email?email=testuser%40testuser.com throttle": { email_known: true }, "get /wsapi/have_email?email=testuser%40testuser.com ajaxError": undefined, "get /wsapi/have_email?email=unregistered%40testuser.com valid": { email_known: false }, "get /wsapi/have_email?email=unregistered%40testuser.com primary": { email_known: false }, + "get /wsapi/have_email?email=registered%40testuser.com primary": { email_known: true }, "post /wsapi/remove_email valid": { success: true }, "post /wsapi/remove_email invalid": { success: false }, "post /wsapi/remove_email multiple": { success: true }, @@ -150,12 +152,17 @@ BrowserID.Mocks.xhr = (function() { "get /wsapi/address_info?email=unregistered%40testuser.com throttle": { type: "secondary", known: false }, "get /wsapi/address_info?email=unregistered%40testuser.com valid": { type: "secondary", known: false }, "get /wsapi/address_info?email=unregistered%40testuser.com unknown_secondary": { type: "secondary", known: false }, + "get /wsapi/address_info?email=unregistered%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, + + "get /wsapi/address_info?email=registered%40testuser.com valid": { type: "secondary", known: true }, "get /wsapi/address_info?email=registered%40testuser.com known_secondary": { type: "secondary", known: true }, + "get /wsapi/address_info?email=registered%40testuser.com throttle": { type: "secondary", known: true }, "get /wsapi/address_info?email=registered%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, - "get /wsapi/address_info?email=unregistered%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, - "get /wsapi/address_info?email=testuser%40testuser.com unknown_secondary": { type: "secondary", known: false }, - "get /wsapi/address_info?email=testuser%40testuser.com known_secondary": { type: "secondary", known: true }, "get /wsapi/address_info?email=registered%40testuser.com mustAuth": { type: "secondary", known: true }, + + "get /wsapi/address_info?email=testuser%40testuser.com valid": { type: "secondary", known: true }, + "get /wsapi/address_info?email=testuser%40testuser.com known_secondary": { type: "secondary", known: true }, + "get /wsapi/address_info?email=testuser%40testuser.com unknown_secondary": { type: "secondary", known: false }, "get /wsapi/address_info?email=testuser%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, "get /wsapi/address_info?email=testuser%40testuser.com ajaxError": undefined, "post /wsapi/add_email_with_assertion invalid": { success: false }, diff --git a/resources/views/forgot.ejs b/resources/views/forgot.ejs index 7e76d76e2966c898768b7685a462331b1f9c4603..2dff1e3ef8e73a26a990ada9552ab10bda361535 100644 --- a/resources/views/forgot.ejs +++ b/resources/views/forgot.ejs @@ -24,7 +24,7 @@ <ul class="inputs forminputs"> <li> <label for="email"><%- gettext('Email Address') %></label> - <input id="email" autofocus required placeholder="<%- gettext('Your Email') %>" type="email" autocapitalize="off" autocorrect="off" spellcheck="false" maxlength="254" /> + <input id="email" required placeholder="<%- gettext('Your Email') %>" type="email" autocapitalize="off" autocorrect="off" spellcheck="false" maxlength="254" disabled /> <div id="email_format" class="tooltip" for="email"> <%- gettext('This field must be an email address.') %> @@ -41,11 +41,15 @@ <div id="not_registered" class="tooltip" for="email"> <%- gettext('Non existent user!') %> </div> + + <div id="primary_address" class="tooltip" for="email"> + <%- gettext('Cannot reset the password of that address.') %> + </div> </li> <li> <label for="password"><%= format(gettext("Create a new password to use with %s."), ["Persona"]) %></label> - <input id="password" placeholder="<%- gettext('Password') %>" type="password" maxlength="80" /> + <input id="password" placeholder="<%- gettext('Password') %>" type="password" maxlength="80" autofocus /> <div id="password_required" class="tooltip" for="password"> <%- gettext('Password is required.') %>