diff --git a/resources/static/dialog/qunit.html b/resources/static/dialog/qunit.html index 8a9f00c078ad7564ce39f6dd6c55ead608502bc6..0236807217744451277741940817a7f083dcc48c 100644 --- a/resources/static/dialog/qunit.html +++ b/resources/static/dialog/qunit.html @@ -49,6 +49,11 @@ This is a long tooltip. This should remain on the screen for about 5 seconds. </div> + <ul class="notifications"> + <li class="notification emailsent">Email Sent</li> + <li class="notification doh">doh</li> + </ul> + <script type="text/html" id="templateTooltip"> <div class="tooltip"> {{ contents }} diff --git a/resources/static/dialog/resources/user.js b/resources/static/dialog/resources/user.js index f78fadefde32261d645b0b1d25c5653880e7910b..64d4ae72fc52a75b70d4053e138d9fa70141c2c8 100644 --- a/resources/static/dialog/resources/user.js +++ b/resources/static/dialog/resources/user.js @@ -283,36 +283,54 @@ BrowserID.User = (function() { * Set the password of the current user. * @method setPassword * @param {string} password - password to set - * @param {function} [onSuccess] - Called on successful completion. + * @param {function} [onComplete] - Called on successful completion. * @param {function} [onFailure] - Called on error. */ - setPassword: function(password, onSuccess, onFailure) { - network.setPassword(password, onSuccess, onFailure); + setPassword: function(password, onComplete, onFailure) { + network.setPassword(password, onComplete, onFailure); }, /** * Request a password reset for the given email address. * @method requestPasswordReset * @param {string} email - email address to reset password for. - * @param {function} [onSuccess] - Callback to call when complete. + * @param {function} [onComplete] - Callback to call when complete, called + * with a single object, info. + * info.status {boolean} - true or false whether request was successful. + * info.reason {string} - if status false, reason of failure. * @param {function} [onFailure] - Called on XHR failure. */ - requestPasswordReset: function(email, onSuccess, onFailure) { - network.requestPasswordReset(email, origin, onSuccess, onFailure); + requestPasswordReset: function(email, onComplete, onFailure) { + this.isEmailRegistered(email, function(registered) { + if (registered) { + network.requestPasswordReset(email, origin, function(reset) { + var status = { + success: reset + }; + + if(!reset) status.reason = "throttle"; + + if (onComplete) onComplete(status); + }, onFailure); + } + else if (onComplete) { + onComplete({ success: false, reason: "invalid_user" }); + } + }, onFailure); }, /** * Cancel the current user's account. Remove last traces of their * identity. * @method cancelUser - * @param {function} [onSuccess] - Called whenever complete. + * @param {function} [onComplete] - Called whenever complete. * @param {function} [onFailure] - called on error. */ - cancelUser: function(onSuccess, onFailure) { + cancelUser: function(onComplete, onFailure) { network.cancelUser(function() { setAuthenticationStatus(false); - if (onSuccess) { - onSuccess(); + if (onComplete) { + onComplete(); } }, onFailure); diff --git a/resources/static/dialog/test/qunit/pages/forgot_unit_test.js b/resources/static/dialog/test/qunit/pages/forgot_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..d9afb17b14c42cb90173d963237cb5514646d51b --- /dev/null +++ b/resources/static/dialog/test/qunit/pages/forgot_unit_test.js @@ -0,0 +1,135 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery").then("/dialog/resources/network", "/dialog/resources/user", "/js/pages/forgot", function() { + "use strict"; + + var bid = BrowserID, + network = bid.Network, + user = bid.User, + xhrError = false, + CHECK_DELAY = 500; + + var netMock = { + requestPasswordReset: function(email, origin, onComplete, onFailure) { + xhrError ? onFailure() : onComplete(email === "registered@testuser.com"); + }, + + emailRegistered: function(email, onComplete, onFailure) { + xhrError ? onFailure() : onComplete(email === "registered@testuser.com"); + } + }; + + module("pages/forgot", { + setup: function() { + user.setNetwork(netMock); + $(".error").stop().hide(); + xhrError = false; + bid.forgot(); + }, + teardown: function() { + user.setNetwork(network); + $(".error").stop().hide(); + $(".website").text(""); + bid.forgot.reset(); + } + }); + + test("requestPasswordReset with invalid email", function() { + $("#email").val("invalid"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with known email", function() { + $("#email").val("registered@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + ok($(".emailsent").is(":visible"), "email sent successfully"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with unknown email", function() { + $("#email").val("unregistered@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with throttling", function() { + $("#email").val("throttled@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with XHR Error", function() { + xhrError = true; + + $("#email").val("testuser@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + equal($(".doh").is(":visible"), true, "XHR error message is displayed"); + start(); + }, CHECK_DELAY); + + stop(); + + }); + +}); diff --git a/resources/static/dialog/test/qunit/qunit.js b/resources/static/dialog/test/qunit/qunit.js index 093568db7df89e758c8ecb875c6cf0b872acd0d4..a9c3269bcc65824a35015099d0f540069a3efef3 100644 --- a/resources/static/dialog/test/qunit/qunit.js +++ b/resources/static/dialog/test/qunit/qunit.js @@ -22,6 +22,7 @@ steal("/dialog/resources/browserid.js", .then("include_unit_test") .then("relay/relay_unit_test") .then("pages/add_email_address_test") + .then("pages/forgot_unit_test") .then("resources/tooltip_unit_test") .then("resources/channel_unit_test") .then("resources/browser-support_unit_test") diff --git a/resources/static/dialog/test/qunit/resources/user_unit_test.js b/resources/static/dialog/test/qunit/resources/user_unit_test.js index 7ef2dfd486e92752218cea80e5911dbc93705840..568d7d6aa73f93d9a23912d3a754d07c0970d293 100644 --- a/resources/static/dialog/test/qunit/resources/user_unit_test.js +++ b/resources/static/dialog/test/qunit/resources/user_unit_test.js @@ -57,6 +57,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio emailCheckCount = 0, registrationResponse, xhrFailure = false, + throttle = false, validToken = true; var netStub = { @@ -67,6 +68,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio userEmails = {"testuser@testuser.com": {}}; registrationResponse = "complete"; xhrFailure = false; + throttle = false; }, checkUserRegistration: function(email, onSuccess, onFailure) { @@ -153,7 +155,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }, requestPasswordReset: function(email, origin, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(true); + xhrFailure ? onFailure() : onSuccess(!throttle); }, cancelUser: function(onSuccess, onFailure) { @@ -431,10 +433,52 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); - test("requestPasswordReset", function() { - lib.requestPasswordReset("address", function(reset) { - // XXX fill this in. - ok(true); + test("requestPasswordReset with known email", function() { + lib.requestPasswordReset("registered", function(status) { + equal(status.success, true, "password reset for known user"); + start(); + }, function() { + ok(false, "onFailure should not be called"); + start(); + }); + + stop(); + }); + + test("requestPasswordReset with unknown email", function() { + lib.requestPasswordReset("unregistered", function(status) { + equal(status.success, false, "password not reset for unknown user"); + equal(status.reason, "invalid_user", "invalid_user is the reason"); + start(); + }, function() { + ok(false, "onFailure should not be called"); + start(); + }); + + stop(); + }); + + test("requestPasswordReset with throttle", function() { + throttle = true; + lib.requestPasswordReset("registered", function(status) { + equal(status.success, false, "password not reset for throttle"); + equal(status.reason, "throttle", "password reset was throttled"); + start(); + }, function() { + ok(false, "onFailure should not be called"); + start(); + }); + + stop(); + }); + + test("requestPasswordReset with XHR failure", function() { + xhrFailure = true; + lib.requestPasswordReset("address", function(status) { + ok(false, "xhr failure should never succeed"); + start(); + }, function() { + ok(true, "xhr failure should always be a failure"); start(); }); diff --git a/resources/static/js/pages/forgot.js b/resources/static/js/pages/forgot.js index b329a1da7e10f9cf156e5d88c8b702cd0f64adea..32004aef4ed8602904b56fd38a62f18ff2abf114 100644 --- a/resources/static/js/pages/forgot.js +++ b/resources/static/js/pages/forgot.js @@ -38,38 +38,55 @@ BrowserID.forgot = (function() { "use strict"; var bid = BrowserID, - pageHelpers = bid.PageHelpers; + user = bid.User, + pageHelpers = bid.PageHelpers, + tooltip = bid.Tooltip; - return function() { - $("form input[autofocus]").focus(); - - pageHelpers.setupEmail(); + function submit(event) { + if (event) event.preventDefault(); - $("#signUpForm").bind("submit", function(event) { - event.preventDefault(); - $(".notifications .notification").hide(); + // GET RID OF THIS HIDE CRAP AND USE CSS! + $(".notifications .notification").hide(); - var email = $("#email").val(), - password = $("#password").val(), - vpassword = $("#vpassword").val(); + var email = $("#email").val(), + valid = bid.Validation.email(email); - if (password != vpassword) { - $(".notifications .notification.mismatchpassword").fadeIn(); - return false; - } - - pageHelpers.clearStoredEmail(); - bid.User.createUser(email, function onSuccess(keypair) { - $('#sent_to_email').html(email); - $('#forminputs').fadeOut(); - $(".notifications .notification.emailsent").fadeIn(); + if (valid) { + user.requestPasswordReset(email, function onSuccess(info) { + if (info.success) { + pageHelpers.clearStoredEmail(); + $('#sent_to_email').html(email); + $('#forminputs').fadeOut(); + $(".notifications .notification.emailsent").fadeIn(); + } + else { + var tooltipEl = info.reason === "throttle" ? "#could_not_add" : "#not_registered"; + tooltip.showTooltip(tooltipEl); + } }, function onFailure() { - // bad authentication $(".notifications .notification.doh").fadeIn(); }); - }); + } }; + function init() { + $("form input[autofocus]").focus(); + + pageHelpers.setupEmail(); + + $("#signUpForm").bind("submit", submit); + } + + function reset() { + $("#signUpForm").unbind("submit", submit); + } + + + var forgot = init; + forgot.submit = submit; + forgot.reset = reset; + + return forgot; }()); diff --git a/resources/static/js/pages/verify_email_address.js b/resources/static/js/pages/verify_email_address.js index 07642d077dc1639d25939d9e7510d3f6abc8a578..6c97f534739ff52494eaab9f051991526c3891ac 100644 --- a/resources/static/js/pages/verify_email_address.js +++ b/resources/static/js/pages/verify_email_address.js @@ -37,8 +37,7 @@ (function() { "use strict"; - var bid = BrowserID, - tooltip = bid.Tooltip; + var bid = BrowserID; function showError(el) { $(el).fadeIn(250); diff --git a/resources/views/forgot.ejs b/resources/views/forgot.ejs index 3e36ada4c89b785dcbbffb198fa1f9c8065de9e7..763420c42f8d2d04d8ee660550bb85a42fd0a2ab 100644 --- a/resources/views/forgot.ejs +++ b/resources/views/forgot.ejs @@ -1,7 +1,7 @@ <div id="vAlign" class="display_always"> <div id="signUpFormWrap"> <!-- XXX this form submits to nowhere --> - <form id="signUpForm" class="cf authform"> + <form id="signUpForm" class="cf authform" novalidate> <h1 class="serif">Forgot Password</h1> <div class="notifications"> <div class="notification error doh">Doh! Something went wrong :-( </div> @@ -13,7 +13,25 @@ <li> <label class="serif" for="email">Email Address</label> <input class="sans" id="email" autofocus required placeholder="Your Email" type="email" autocapitalize="off" autocorrect="off" maxlength="254" /> + + <div id="email_format" class="tooltip" for="email"> + This field must be an email address. + </div> + + <div id="email_required" class="tooltip" for="email"> + The email field is required. + </div> + + <div id="could_not_add" class="tooltip" for="email"> + We just sent an email to that address! If you really want to send another, wait a minute or two and try again. + </div> + + <div id="not_registered" class="tooltip" for="email"> + Non existent user! + </div> </li> + + </ul> <div class="submit cf"> <div class="remember cf">