diff --git a/resources/static/dialog/controllers/authenticate_controller.js b/resources/static/dialog/controllers/authenticate_controller.js index 84632f5f3008fe152f92d6c27a29a3b22899e047..21354b2b3ed27b4c5abe357b22da1eb0e1bff1e6 100644 --- a/resources/static/dialog/controllers/authenticate_controller.js +++ b/resources/static/dialog/controllers/authenticate_controller.js @@ -42,6 +42,7 @@ user = bid.User, errors = bid.Errors, validation = bid.Validation, + tooltip = bid.Tooltip, lastEmail = ""; function checkEmail(el, event) { @@ -50,9 +51,7 @@ cancelEvent(event); - if (!validation.email(email)) { - return; - } + if (!validation.email(email)) return; user.isEmailRegistered(email, function onComplete(registered) { if (registered) { @@ -70,19 +69,16 @@ cancelEvent(event); - if (!validation.email(email)) { - return; - } + if (!validation.email(email)) return; - user.createUser(email, function(keypair) { - if (keypair) { + user.createUser(email, function(staged) { + if (staged) { self.close("user_staged", { - email: email, - keypair: keypair + email: email }); } else { - // XXX can't register this email address. + tooltip.showTooltip("#could_not_add"); } }, self.getErrorDialog(errors.createUser)); } @@ -94,9 +90,7 @@ cancelEvent(event); - if (!validation.emailAndPassword(email, pass)) { - return; - } + if (!validation.emailAndPassword(email, pass)) return; user.authenticate(email, pass, function onComplete(authenticated) { @@ -131,16 +125,12 @@ } function cancelEvent(event) { - if (event) { - event.preventDefault(); - } + if (event) event.preventDefault(); } function enterEmailState(el, event) { - if (event && event.which === 13) { - // Enter key, do nothing - return; - } + // Enter key, do nothing + if (event && event.which === 13) return; if (!el.is(":disabled")) { this.submit = checkEmail; @@ -185,6 +175,10 @@ init: function(el, options) { options = options || {}; + if (options.user) { + user = options.user; + } + this._super(el, { bodyTemplate: "authenticate.ejs", bodyVars: { @@ -196,9 +190,7 @@ this.submit = checkEmail; // If we already have an email address, check if it is valid, if so, show // password. - if (options.email) { - this.submit(); - } + if (options.email) this.submit(); }, "#email keyup": function(el, event) { diff --git a/resources/static/dialog/qunit.html b/resources/static/dialog/qunit.html index 7e8189f829a5ff161198b6c2aa4251a536757786..93eb8cb2141e60e3e86587d8db256987223d81f4 100644 --- a/resources/static/dialog/qunit.html +++ b/resources/static/dialog/qunit.html @@ -32,7 +32,7 @@ <div class="contents"></div> </div> - <span id="email"></span> + <input id="email" /> <span id="cannotconfirm" class="error">Cannot confirm</span> <span id="cannotcommunicate" class="error">Cannot communicate</span> <span id="siteinfo" class="error"><span class="website"></span></span> diff --git a/resources/static/dialog/resources/network.js b/resources/static/dialog/resources/network.js index 855a349baea328f8d9c06042e66e6d2c249fcc9e..ea1f53b312e0e9c1c9c4a3a7c6351386996c71c1 100644 --- a/resources/static/dialog/resources/network.js +++ b/resources/static/dialog/resources/network.js @@ -59,6 +59,7 @@ BrowserID.Network = (function() { info = info || {}; var network = info.network = info.network || {}; + network.status = jqXHR && jqXHR.status; network.textStatus = textStatus; network.errorThrown = errorThrown; @@ -252,7 +253,13 @@ BrowserID.Network = (function() { success: function(status) { if (onSuccess) onSuccess(status.success); }, - error: onFailure + error: function(info) { + // 403 is throttling. + if(info.network.status === 403) { + if (onSuccess) onSuccess(false); + } + else if (onFailure) onFailure(info); + } }); }, @@ -410,7 +417,13 @@ BrowserID.Network = (function() { success: function(status) { if (onSuccess) onSuccess(status.success); }, - error: onFailure + error: function(info) { + // 403 is throttling. + if(info.network.status === 403) { + if (onSuccess) onSuccess(false); + } + else if (onFailure) onFailure(info); + } }); }, diff --git a/resources/static/dialog/resources/user.js b/resources/static/dialog/resources/user.js index 3fde2a9a9eb795b03e40e2685abd70266e544417..f78fadefde32261d645b0b1d25c5653880e7910b 100644 --- a/resources/static/dialog/resources/user.js +++ b/resources/static/dialog/resources/user.js @@ -236,11 +236,7 @@ BrowserID.User = (function() { // remember this for later storage.setStagedOnBehalfOf(self.getHostname()); - network.createUser(email, origin, function(created) { - if (onSuccess) { - onSuccess(created); - } - }, onFailure); + network.createUser(email, origin, onSuccess, onFailure); }, /** @@ -484,13 +480,10 @@ BrowserID.User = (function() { addEmail: function(email, onSuccess, onFailure) { var self = this; network.addEmail(email, origin, function(added) { - if (added) { - storage.setStagedOnBehalfOf(self.getHostname()); - // we no longer send the keypair, since we will certify it later. - if (onSuccess) { - onSuccess(added); - } - } + if (added) storage.setStagedOnBehalfOf(self.getHostname()); + + // we no longer send the keypair, since we will certify it later. + if (onSuccess) onSuccess(added); }, onFailure); }, diff --git a/resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js b/resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..0a40cf088dd586bcd81dfdbcf7ec65c98a7da1c2 --- /dev/null +++ b/resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js @@ -0,0 +1,94 @@ +/*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/controllers/page_controller", "/dialog/controllers/authenticate_controller", function() { + "use strict"; + + var controller, + el = $("body"), + storage = BrowserID.Storage, + emailRegistered = false, + userCreated = true; + + var userMock = { + getHostname: function() { return "host"; }, + isEmailRegistered: function(email, onSuccess, onFailure) { + onSuccess(emailRegistered); + }, + + createUser: function(email, onSuccess, onFailure) { + onSuccess(userCreated); + } + }; + + function reset() { + el = $("#controller_head"); + el.find("#formWrap .contents").html(""); + el.find("#wait .contents").html(""); + el.find("#error .contents").html(""); + + emailRegistered = false; + userCreated = true; + + OpenAjax.hub.unsubscribe("user_staged"); + } + + module("controllers/authenticate_controller", { + setup: function() { + reset(); + storage.clear(); + controller = el.authenticate({ user: userMock }).controller(); + }, + + teardown: function() { + if (controller) { + controller.destroy(); + } + reset(); + storage.clear(); + } + }); + + test("setting email address prefills address field", function() { + controller.destroy(); + $("#email").val(""); + controller = el.authenticate({ user: userMock, email: "testuser@testuser.com" }).controller(); + equal($("#email").val(), "testuser@testuser.com", "email prefilled"); + }); + + +}); + diff --git a/resources/static/dialog/test/qunit/qunit.js b/resources/static/dialog/test/qunit/qunit.js index ffecc86f2b4e903e5884105bdf51827953fa2a96..e97265eb156944baff0a3f3522101a329c3570a0 100644 --- a/resources/static/dialog/test/qunit/qunit.js +++ b/resources/static/dialog/test/qunit/qunit.js @@ -30,4 +30,5 @@ steal("/dialog/resources/browserid.js", .then("controllers/page_controller_unit_test") .then("controllers/pickemail_controller_unit_test") .then("controllers/dialog_controller_unit_test") + .then("controllers/authenticate_controller_unit_test") diff --git a/resources/static/dialog/test/qunit/resources/network_unit_test.js b/resources/static/dialog/test/qunit/resources/network_unit_test.js index d0a8bda50b4f48e16ad8b42ec83244a85a76984d..029e8d76cbe40e146a988cd19fed8f77ac8231fb 100644 --- a/resources/static/dialog/test/qunit/resources/network_unit_test.js +++ b/resources/static/dialog/test/qunit/resources/network_unit_test.js @@ -126,6 +126,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func // We are going to test for XHR failures for session_context using // call to serverTime. We are going to use the flag contextAjaxError "get /wsapi/session_context ajaxError": contextInfo, + "get /wsapi/session_context throttle": contextInfo, "get /wsapi/session_context contextAjaxError": undefined, "post /wsapi/authenticate_user valid": { success: true }, "post /wsapi/authenticate_user invalid": { success: false }, @@ -135,6 +136,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func "post /wsapi/complete_email_addition ajaxError": undefined, "post /wsapi/stage_user valid": { success: true }, "post /wsapi/stage_user invalid": { success: false }, + "post /wsapi/stage_user throttle": 403, "post /wsapi/stage_user ajaxError": undefined, "get /wsapi/user_creation_status?email=address notcreated": undefined, // undefined because server returns 400 error "get /wsapi/user_creation_status?email=address pending": { status: "pending" }, @@ -156,6 +158,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func "post /wsapi/account_cancel ajaxError": undefined, "post /wsapi/stage_email valid": { success: true }, "post /wsapi/stage_email invalid": { success: false }, + "post /wsapi/stage_email throttle": 403, "post /wsapi/stage_email ajaxError": undefined, "get /wsapi/email_addition_status?email=address notcreated": undefined, // undefined because server returns 400 error "get /wsapi/email_addition_status?email=address pending": { status: "pending" }, @@ -189,7 +192,8 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func var resName = req.type + " " + req.url + " " + xhr.resultType; var result = xhr.results[resName]; - if(result) { + var type = typeof result; + if(!(type == "number" || type == "undefined")) { if(obj.success) { obj.success(result); } @@ -197,7 +201,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func else if (obj.error) { // Invalid result - either invalid URL, invalid GET/POST or // invalid resultType - obj.error({}, "errorStatus", "errorThrown"); + obj.error({ status: result || 400 }, "errorStatus", "errorThrown"); } } } @@ -373,6 +377,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("createUser throttled", function() { + xhr.useResult("throttle"); + + network.createUser("validuser", "origin", function onSuccess(added) { + equal(added, false, "throttled email returns onSuccess but with false as the value"); + wrappedStart(); + }, function onFailure() { + ok(false); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("createUser with XHR failure", function() { notificationCheck(network.createUser, "validuser", "origin"); }); @@ -548,6 +566,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("addEmail throttled", function() { + xhr.useResult("throttle"); + + network.addEmail("address", "origin", function onSuccess(added) { + equal(added, false, "throttled email returns onSuccess but with false as the value"); + wrappedStart(); + }, function onFailure() { + ok(false); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("addEmail with XHR failure", function() { notificationCheck(network.addEmail, "address", "origin"); }); 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 48e7a4b705930a18aa72f16d7206ddec37c739c0..7ef2dfd486e92752218cea80e5911dbc93705840 100644 --- a/resources/static/dialog/test/qunit/resources/user_unit_test.js +++ b/resources/static/dialog/test/qunit/resources/user_unit_test.js @@ -61,7 +61,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio var netStub = { reset: function() { - credentialsValid = syncValid = true; + credentialsValid = emailAdded = userAdded = syncValid = true; unknownEmails = []; keyRefresh = []; userEmails = {"testuser@testuser.com": {}}; @@ -93,7 +93,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }, addEmail: function(email, origin, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(true); + xhrFailure ? onFailure() : onSuccess(emailAdded); }, checkEmailRegistration: function(email, onSuccess, onFailure) { @@ -145,7 +145,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }, createUser: function(email, origin, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(true); + xhrFailure ? onFailure() : onSuccess(userAdded); }, setPassword: function(password, onSuccess) { @@ -206,7 +206,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio */ } - module("user", { + module("resources/user", { setup: function() { lib.setNetwork(netStub); lib.clearStoredEmailKeypairs(); @@ -284,6 +284,16 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); + test("createUser with user creation refused", function() { + userAdded = false + lib.createUser("testuser@testuser.com", function(status) { + equal(status, false, "user creation refused"); + start(); + }, failure("createUser failure")); + + stop(); + }); + test("createUser with XHR failure", function() { xhrFailure = true; @@ -597,7 +607,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio var identities = lib.getStoredEmailKeypairs(); equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation."); - equal(storage.getStagedOnBehalfOf(), lib.getHostname(), "initiatingOrigin is stored"); start(); @@ -606,6 +615,23 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); + test("addEmail with addition refused", function() { + emailAdded = false; + + lib.addEmail("testemail@testemail.com", function(added) { + equal(added, false, "user addition was refused"); + + var identities = lib.getStoredEmailKeypairs(); + equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation."); + + equal(typeof storage.getStagedOnBehalfOf(), "undefined", "initiatingOrigin is not stored"); + + start(); + }, failure("addEmail failure")); + + stop(); + }); + test("addEmail with XHR failure", function() { xhrFailure = true; lib.addEmail("testemail@testemail.com", function(added) { diff --git a/resources/static/dialog/views/authenticate.ejs b/resources/static/dialog/views/authenticate.ejs index ae0c180a6e5837c3111364ea6d1ca9d2947f8c08..4a4edfa17269a734af146e8488b05b6b9c696cd4 100644 --- a/resources/static/dialog/views/authenticate.ejs +++ b/resources/static/dialog/views/authenticate.ejs @@ -13,6 +13,14 @@ <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="cannotStage" 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> </li> <li id="hint_section" class="start"> diff --git a/resources/static/dialog/views/pickemail.ejs b/resources/static/dialog/views/pickemail.ejs index 8e0b1669133bb225f96c535f662c0e9f72a0c53b..c7255d16f4841d164fb47699cf18ed0febcc8c8c 100644 --- a/resources/static/dialog/views/pickemail.ejs +++ b/resources/static/dialog/views/pickemail.ejs @@ -40,7 +40,7 @@ </div> <div id="could_not_add" class="tooltip" for="newEmail"> - This email address could not be added. + 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="already_taken" class="tooltip" for="newEmail">