diff --git a/resources/static/common/js/network.js b/resources/static/common/js/network.js index 2a38f461eb319abe90817655a95a737b1d839ae9..5864671e9f86c1ed5109be63dd0461c7c88ec1ce 100644 --- a/resources/static/common/js/network.js +++ b/resources/static/common/js/network.js @@ -83,14 +83,10 @@ BrowserID.Network = (function() { } } - function stageEmailForVerification(email, password, origin, wsapiName, onComplete, onFailure) { + function stageAddressForVerification(data, wsapiName, onComplete, onFailure) { post({ url: wsapiName, - data: { - email: email, - pass: password, - site : origin - }, + data: data, success: function(status) { complete(onComplete, status.success); }, @@ -235,7 +231,12 @@ BrowserID.Network = (function() { * @param {function} [onFailure] - Called on XHR failure. */ createUser: function(email, password, origin, onComplete, onFailure) { - stageEmailForVerification(email, password, origin, "/wsapi/stage_user", onComplete, onFailure); + var postData = { + email: email, + pass: password, + site : origin + }; + stageAddressForVerification(postData, "/wsapi/stage_user", onComplete, onFailure); }, /** @@ -308,16 +309,17 @@ BrowserID.Network = (function() { }, /** - * Complete user reset password - * @method completeUserResetPassword - * @param {string} token - token to register for. + * Call with a token to prove an email address ownership. + * @method completeEmailRegistration + * @param {string} token - token proving email ownership. * @param {string} password - * @param {function} [onComplete] - Called when complete. + * @param {function} [onComplete] - Callback to call when complete. Called + * with one boolean parameter that specifies the validity of the token. * @param {function} [onFailure] - Called on XHR failure. */ - completeUserResetPassword: function(token, password, onComplete, onFailure) { + completeEmailRegistration: function(token, password, onComplete, onFailure) { post({ - url: "/wsapi/complete_reset", + url: "/wsapi/complete_email_addition", data: { token: token, pass: password @@ -330,17 +332,34 @@ BrowserID.Network = (function() { }, /** - * Call with a token to prove an email address ownership. - * @method completeEmailRegistration - * @param {string} token - token proving email ownership. + * Request a password reset for the given email address. + * @method requestPasswordReset + * @param {string} email * @param {string} password - * @param {function} [onComplete] - Callback to call when complete. Called - * with one boolean parameter that specifies the validity of the token. + * @param {string} origin + * @param {function} [onComplete] - Callback to call when complete. * @param {function} [onFailure] - Called on XHR failure. */ - completeEmailRegistration: function(token, password, onComplete, onFailure) { + requestPasswordReset: function(email, password, origin, onComplete, onFailure) { + var postData = { + email: email, + pass: password, + site : origin + }; + stageAddressForVerification(postData, "/wsapi/stage_reset", onComplete, onFailure); + }, + + /** + * Complete email reset password + * @method completePasswordReset + * @param {string} token - token to register for. + * @param {string} password + * @param {function} [onComplete] - Called when complete. + * @param {function} [onFailure] - Called on XHR failure. + */ + completePasswordReset: function(token, password, onComplete, onFailure) { post({ - url: "/wsapi/complete_email_addition", + url: "/wsapi/complete_reset", data: { token: token, pass: password @@ -353,19 +372,49 @@ BrowserID.Network = (function() { }, /** - * Complete email reset password - * @method completeEmailResetPassword + * Check the registration status of a password reset + * @method checkPasswordReset + * @param {function} [onsuccess] - called when complete. + * @param {function} [onfailure] - called on xhr failure. + */ + checkPasswordReset: function(email, onComplete, onFailure) { + get({ + // XXX the URL is going to have to change + url: "/wsapi/email_addition_status?email=" + encodeURIComponent(email), + success: function(status, textStatus, jqXHR) { + complete(onComplete, status.status); + }, + error: onFailure + }); + }, + + /** + * Stage an email reverification. + * @method requestEmailReverify + * @param {string} email + * @param {string} origin - site user is trying to sign in to. + * @param {function} [onComplete] - Callback to call when complete. + * @param {function} [onFailure] - Called on XHR failure. + */ + requestEmailReverify: function(email, origin, onComplete, onFailure) { + var postData = { + email: email, + site : origin + }; + stageAddressForVerification(postData, "/wsapi/stage_reverify", onComplete, onFailure); + }, + + /** + * Complete email reverification + * @method completeEmailReverify * @param {string} token - token to register for. * @param {string} password * @param {function} [onComplete] - Called when complete. * @param {function} [onFailure] - Called on XHR failure. */ - /** - * BEGIN may not be needed - */ - completeEmailResetPassword: function(token, password, onComplete, onFailure) { + completeEmailReverify: function(token, password, onComplete, onFailure) { post({ - url: "/wsapi/complete_reset", + url: "/wsapi/complete_reverify", data: { token: token, pass: password @@ -376,24 +425,25 @@ BrowserID.Network = (function() { error: onFailure }); }, - /** - * END may not be needed - */ - /** - * Request a password reset for the given email address. - * @method requestPasswordReset - * @param {string} email - * @param {string} password - * @param {string} origin - * @param {function} [onComplete] - Callback to call when complete. - * @param {function} [onFailure] - Called on XHR failure. + * Check the registration status of an email reverification + * @method checkEmailReverify + * @param {function} [onsuccess] - called when complete. + * @param {function} [onfailure] - called on xhr failure. */ - requestPasswordReset: function(email, password, origin, onComplete, onFailure) { - stageEmailForVerification(email, password, origin, "/wsapi/stage_reset", onComplete, onFailure); + checkEmailReverify: function(email, onComplete, onFailure) { + get({ + // XXX the URL is going to have to change + url: "/wsapi/email_addition_status?email=" + encodeURIComponent(email), + success: function(status, textStatus, jqXHR) { + complete(onComplete, status.status); + }, + error: onFailure + }); }, + /** * Set the password of the current user. * @method setPassword @@ -504,7 +554,12 @@ BrowserID.Network = (function() { * @param {function} [onFailure] - called on xhr failure. */ addSecondaryEmail: function(email, password, origin, onComplete, onFailure) { - stageEmailForVerification(email, password, origin, "/wsapi/stage_email", onComplete, onFailure); + var postData = { + email: email, + pass: password, + site : origin + }; + stageAddressForVerification(postData, "/wsapi/stage_email", onComplete, onFailure); }, /** diff --git a/resources/static/common/js/user.js b/resources/static/common/js/user.js index 9d0cb8150c6d56961eb2bb58b621d5c777b7d8a8..d36d94ea66236862842204edebde5b1e6eb4c490 100644 --- a/resources/static/common/js/user.js +++ b/resources/static/common/js/user.js @@ -220,7 +220,20 @@ BrowserID.User = (function() { }); } - function verifyAddress(token, password, networkFuncName, onComplete, onFailure) { + function stageAddressVerificationResponse(onComplete, staged) { + var status = { success: staged }; + + if (!staged) status.reason = "throttle"; + // Used on the main site when the user verifies - once + // verification is complete, the user is redirected back to the + // RP and logged in. + var site = User.getReturnTo(); + if (staged && site) storage.setReturnTo(site); + + complete(onComplete, status); + } + + function completeAddressVerification(token, password, networkFuncName, onComplete, onFailure) { User.tokenInfo(token, function(info) { var invalidInfo = { valid: false }; if (info) { @@ -334,6 +347,7 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on error. */ createSecondaryUser: function(email, password, onComplete, onFailure) { + // set - XXX Use stageAddressVerificationResponse to handle the response. network.createUser(email, password, origin, function(created) { // Used on the main site when the user verifies - once verification // is complete, the user is redirected back to the RP and logged in. @@ -509,9 +523,7 @@ BrowserID.User = (function() { * @param {function} [onSuccess] - Called to give status updates. * @param {function} [onFailure] - Called on error. */ - waitForUserValidation: function(email, onSuccess, onFailure) { - registrationPoll(network.checkUserRegistration, email, onSuccess, onFailure); - }, + waitForUserValidation: registrationPoll.curry(network.checkUserRegistration), /** * Cancel the waitForUserValidation poll @@ -550,7 +562,7 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on error. */ verifyUser: function(token, password, onComplete, onFailure) { - verifyAddress(token, password, "completeUserRegistration", onComplete, onFailure); + completeAddressVerification(token, password, "completeUserRegistration", onComplete, onFailure); }, /** @@ -604,18 +616,8 @@ BrowserID.User = (function() { requestPasswordReset: function(email, password, onComplete, onFailure) { User.isEmailRegistered(email, function(registered) { if (registered) { - network.requestPasswordReset(email, password, origin, function(reset) { - var status = { success: reset }; - - if (!reset) status.reason = "throttle"; - // Used on the main site when the user verifies - once - // verification is complete, the user is redirected back to the - // RP and logged in. - var site = User.getReturnTo(); - if (reset && site) storage.setReturnTo(site); - - complete(onComplete, status); - }, onFailure); + network.requestPasswordReset(email, password, origin, + stageAddressVerificationResponse.curry(onComplete), onFailure); } else if (onComplete) { onComplete({ success: false, reason: "invalid_user" }); @@ -634,9 +636,66 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on error. */ verifyPasswordReset: function(token, password, onComplete, onFailure) { - verifyAddress(token, password, "completeUserResetPassword", onComplete, onFailure); + completeAddressVerification(token, password, "completePasswordReset", onComplete, onFailure); }, + /** + * Wait for the password reset to complete + * @method waitForPasswordResetComplete + * @param {string} email - email address to check. + * @param {function} [onSuccess] - Called to give status updates. + * @param {function} [onFailure] - Called on error. + */ + waitForPasswordResetComplete: registrationPoll.curry(network.checkPasswordReset), + + /** + * Cancel the waitForPasswordResetComplete poll + * @method cancelWaitForPasswordResetComplete + */ + cancelWaitForPasswordResetComplete: cancelRegistrationPoll, + + /** + * Request the reverification of an unverified email address + * @method requestEmailReverify + * @param {string} email + * @param {function} [onComplete] + * @param {function} [onFailure] + */ + requestEmailReverify: function(email, onComplete, onFailure) { + var idInfo = storage.getEmail(email); + if (idInfo && idInfo.verified) { + // this email is already verified, cannot be reverified. + complete(onComplete, { success: false, reason: "verified_email" }); + } + else if (idInfo && !idInfo.verified) { + // this address is unverified, try to reverify it. + network.requestEmailReverify(email, origin, + stageAddressVerificationResponse.curry(onComplete), onFailure); + } + else { + // user does not own this address. + complete(onComplete, { success: false, reason: "invalid_email" }); + } + }, + + completeEmailReverify: function(token, password, onComplete, onFailure) { + completeAddressVerification(token, password, "completeEmailReverify", onComplete, onFailure); + }, + + /** + * Wait for the email reverification to complete + * @method waitForEmailReverifyComplete + * @param {string} email - email address to check. + * @param {function} [onSuccess] - Called to give status updates. + * @param {function} [onFailure] - Called on error. + */ + waitForEmailReverifyComplete: registrationPoll.curry(network.checkEmailReverify), + + /** + * Cancel the waitForEmailReverifyComplete poll + * @method cancelWaitForEmailReverifyComplete + */ + cancelWaitForEmailReverifyComplete: cancelRegistrationPoll, /** * Cancel the current user's account. Remove last traces of their @@ -932,9 +991,7 @@ BrowserID.User = (function() { * @param {function} [onSuccess] - Called to give status updates. * @param {function} [onFailure] - Called on error. */ - waitForEmailValidation: function(email, onSuccess, onFailure) { - registrationPoll(network.checkEmailRegistration, email, onSuccess, onFailure); - }, + waitForEmailValidation: registrationPoll.curry(network.checkEmailRegistration), /** * Cancel the waitForEmailValidation poll @@ -955,7 +1012,7 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on error. */ verifyEmail: function(token, password, onComplete, onFailure) { - verifyAddress(token, password, "completeEmailRegistration", onComplete, onFailure); + completeAddressVerification(token, password, "completeEmailRegistration", onComplete, onFailure); }, /** diff --git a/resources/static/dialog/js/misc/helpers.js b/resources/static/dialog/js/misc/helpers.js index ce0f58a472e1a65dfb689a64d3eaec5bf1a0f390..d35b68a0ee9d97237278f4bcdcf3db6bab8d7f25 100644 --- a/resources/static/dialog/js/misc/helpers.js +++ b/resources/static/dialog/js/misc/helpers.js @@ -89,7 +89,20 @@ var self=this; user.requestPasswordReset(email, password, function(status) { if (status.success) { - self.publish("password_reset", { email: email }); + self.publish("password_reset_staged", { email: email }); + } + else { + tooltip.showTooltip("#could_not_add"); + } + complete(callback, status.success); + }, self.getErrorDialog(errors.requestPasswordReset, callback)); + } + + function reverifyEmail(email, callback) { + var self=this; + user.requestEmailReverify(email, function(status) { + if (status.success) { + self.publish("reverify_email_staged", { email: email }); } else { tooltip.showTooltip("#could_not_add"); @@ -149,6 +162,7 @@ addEmail: addEmail, addSecondaryEmail: addSecondaryEmail, resetPassword: resetPassword, + reverifyEmail: reverifyEmail, cancelEvent: helpers.cancelEvent, animateClose: animateClose, showRPTosPP: showRPTosPP diff --git a/resources/static/dialog/js/misc/state.js b/resources/static/dialog/js/misc/state.js index e4f342531aa735ca82a9336648f2fc7bcea81900..cc52fa840d0195b964d7c0d4a752949b403f7edf 100644 --- a/resources/static/dialog/js/misc/state.js +++ b/resources/static/dialog/js/misc/state.js @@ -152,7 +152,7 @@ BrowserID.State = (function() { } else if(self.resetPasswordEmail) { self.resetPasswordEmail = null; - startAction(false, "doResetPassword", info); + startAction(false, "doStageResetPassword", info); } }); @@ -172,6 +172,11 @@ BrowserID.State = (function() { redirectToState("email_chosen", { email: self.stagedEmail} ); }); + handleState("staged_address_confirmed", function() { + self.email = self.stagedEmail; + redirectToState("email_chosen", { email: self.stagedEmail} ); + }); + handleState("primary_user", function(msg, info) { addPrimaryUser = !!info.add; email = info.email; @@ -300,7 +305,7 @@ BrowserID.State = (function() { else if (!idInfo.verified) { // user selected an unverified secondary email, kick them over to the // verify screen. - redirectToState("verify_unverified_email", info); + redirectToState("stage_reverify_email", info); } else { // Address is verified, check the authentication, if the user is not @@ -329,8 +334,21 @@ BrowserID.State = (function() { } }); - handleState("verify_unverified_email", function(msg, info) { + handleState("stage_reverify_email", function(msg, info) { + // A user has selected an email that has not been verified after + // a password reset. Stage the email again to be re-verified. + var actionInfo = { + email: info.email, + siteName: self.siteName + }; + startAction("doStageReverifyEmail", actionInfo); + }); + handleState("reverify_email_staged", function(msg, info) { + // The unverified email has been staged, now the user has to confirm + // ownership of the address. Send them off to the "verify your address" + // screen. + startAction("doConfirmReverifyEmail"); }); handleState("email_valid_and_ready", function(msg, info) { @@ -384,15 +402,34 @@ BrowserID.State = (function() { startAction(false, "doForgotPassword", info); }); - handleState("password_reset", function(msg, info) { - // password_reset says the user has confirmed that they want to - // reset their password. doResetPassword will attempt to invoke + handleState("stage_reset_password", function(msg, info) { + // reset_password says the user has confirmed that they want to + // reset their password. doStageResetPassword will attempt to invoke // the reset_password wsapi. If the wsapi call is successful, - // the user will be shown the "go verify your account" message. - info.password_reset = true; - redirectToState("user_staged", info); + // the password_reset_staged message will be triggered and the user will + // be shown the "go verify your account" message. + + // We have to save the staged email address here for when the user + // verifies their account and user_confirmed is called. + self.stagedEmail = info.email; + + var actionInfo = { + email: info.email, + password: info.password + }; + startAction(false, "doStageResetPassword", actionInfo); }); + handleState("password_reset_staged", function(msg, info) { + var actionInfo = { + email: info.email, + siteName: self.siteName + }; + + startAction("doConfirmResetPassword", actionInfo); + }); + + handleState("assertion_generated", function(msg, info) { self.success = true; if (info.assertion !== null) { @@ -417,19 +454,6 @@ BrowserID.State = (function() { redirectToState("email_chosen", info); }); - handleState("reset_password", function(msg, info) { - info = info || {}; - // reset_password says the user has confirmed that they want to - // reset their password. doResetPassword will attempt to invoke - // the create_user wsapi. If the wsapi call is successful, - // the user will be shown the "go verify your account" message. - - // We have to save the staged email address here for when the user - // verifies their account and user_confirmed is called. - self.stagedEmail = info.email; - startAction(false, "doResetPassword", info); - }); - handleState("add_email", function(msg, info) { // add_email indicates the user wishes to add an email to the account, // the add_email screen must be displayed. After the user enters the diff --git a/resources/static/dialog/js/modules/actions.js b/resources/static/dialog/js/modules/actions.js index aec8e96a38ca1abfa54e69e683aa2bd7f2134c99..711f21bc9951364f21b160f11686f105c8b2c7e7 100644 --- a/resources/static/dialog/js/modules/actions.js +++ b/resources/static/dialog/js/modules/actions.js @@ -85,6 +85,10 @@ BrowserID.Modules.Actions = (function() { dialogHelpers.addSecondaryEmail.call(this, info.email, info.password, info.ready); }, + doConfirmEmail: function(info) { + startRegCheckService.call(this, info, "waitForEmailValidation", "email_confirmed"); + }, + doAuthenticate: function(info) { startService("authenticate", info); }, @@ -97,12 +101,21 @@ BrowserID.Modules.Actions = (function() { startService("set_password", _.extend(info, { password_reset: true })); }, - doResetPassword: function(info) { + doStageResetPassword: function(info) { dialogHelpers.resetPassword.call(this, info.email, info.password, info.ready); }, - doConfirmEmail: function(info) { - startRegCheckService.call(this, info, "waitForEmailValidation", "email_confirmed"); + doConfirmResetPassword: function(info) { + startRegCheckService.call(this, info, "waitForPasswordResetComplete", "staged_address_confirmed", info.password || undefined); + + }, + + doStageReverifyEmail: function(info) { + dialogHelpers.reverifyEmail.call(this, info.email, info.ready); + }, + + doConfirmReverifyEmail: function(info) { + startRegCheckService.call(this, info, "waitForEmailReverifyComplete", "staged_address_confirmed", info.password || undefined); }, doAssertionGenerated: function(info) { diff --git a/resources/static/test/cases/common/js/network.js b/resources/static/test/cases/common/js/network.js index 68fc546a8a2b5587902b48f34ee91e5db5aa011c..2b2db59e6d2c57e4489c11d4fa543fd29cf3f7fb 100644 --- a/resources/static/test/cases/common/js/network.js +++ b/resources/static/test/cases/common/js/network.js @@ -17,7 +17,7 @@ var network = BrowserID.Network; - module("shared/network", { + module("common/js/network", { setup: function() { testHelpers.setup(); }, @@ -26,6 +26,15 @@ } }); + function testVerificationPending(funcName) { + transport.useResult("pending"); + + network[funcName]("registered@testuser.com", function(status) { + equal(status, "pending"); + start(); + }, testHelpers.unexpectedFailure); + } + asyncTest("authenticate with valid user", function() { network.authenticate(TEST_EMAIL, "testuser", function onSuccess(authenticated) { @@ -192,32 +201,6 @@ failureCheck(network.completeEmailRegistration, "goodtoken", "password"); }); - asyncTest("completeEmailResetPassword valid", function() { - network.completeEmailResetPassword("goodtoken", "password", function onSuccess(proven) { - equal(proven, true, "good token proved"); - start(); - }, testHelpers.unexpectedXHRFailure); - }); - - asyncTest("completeEmailResetPassword with valid token, bad password", function() { - transport.useResult("badPassword"); - network.completeEmailResetPassword("token", "password", - testHelpers.unexpectedSuccess, - testHelpers.expectedXHRFailure); - }); - - asyncTest("completeEmailResetPassword with invalid token", function() { - transport.useResult("invalid"); - network.completeEmailResetPassword("badtoken", "password", function onSuccess(proven) { - equal(proven, false, "bad token could not be proved"); - start(); - }, testHelpers.unexpectedXHRFailure); - }); - - asyncTest("completeEmailResetPassword with XHR failure", function() { - failureCheck(network.completeEmailResetPassword, "goodtoken", "password"); - }); - asyncTest("createUser with valid user", function() { network.createUser("validuser", "password", "origin", function onSuccess(created) { ok(created); @@ -332,40 +315,6 @@ failureCheck(network.completeUserRegistration, "token", "password"); }); - asyncTest("completeUserResetPassword with valid token, no password required", function() { - network.completeUserResetPassword("token", undefined, function(registered) { - ok(registered); - start(); - }, testHelpers.unexpectedFailure); - }); - - asyncTest("completeUserResetPassword with valid token, bad password", function() { - transport.useResult("badPassword"); - network.completeUserResetPassword("token", "password", - testHelpers.unexpectedSuccess, - testHelpers.expectedXHRFailure); - }); - - asyncTest("completeUserResetPassword with valid token, password required", function() { - network.completeUserResetPassword("token", "password", function(registered) { - ok(registered); - start(); - }, testHelpers.unexpectedFailure); - }); - - asyncTest("completeUserResetPassword with invalid token", function() { - transport.useResult("invalid"); - - network.completeUserResetPassword("token", "password", function(registered) { - equal(registered, false); - start(); - }, testHelpers.unexpectedFailure); - }); - - asyncTest("completeUserResetPassword with XHR failure", function() { - failureCheck(network.completeUserResetPassword, "token", "password"); - }); - asyncTest("cancelUser valid", function() { network.cancelUser(function() { @@ -437,12 +386,7 @@ }); asyncTest("checkEmailRegistration pending", function() { - transport.useResult("pending"); - - network.checkEmailRegistration("registered@testuser.com", function(status) { - equal(status, "pending"); - start(); - }, testHelpers.unexpectedFailure); + testVerificationPending("checkEmailRegistration"); }); asyncTest("checkEmailRegistration complete", function() { @@ -548,6 +492,100 @@ failureCheck(network.requestPasswordReset, TEST_EMAIL, "password", "origin"); }); + asyncTest("completePasswordReset with valid token, no password required", function() { + network.completePasswordReset("token", undefined, function(registered) { + ok(registered); + start(); + }, testHelpers.unexpectedFailure); + }); + + asyncTest("completePasswordReset with valid token, bad password", function() { + transport.useResult("badPassword"); + network.completePasswordReset("token", "password", + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure); + }); + + asyncTest("completePasswordReset with valid token, password required", function() { + network.completePasswordReset("token", "password", function(registered) { + ok(registered); + start(); + }, testHelpers.unexpectedFailure); + }); + + asyncTest("completePasswordReset with invalid token", function() { + transport.useResult("invalid"); + + network.completePasswordReset("token", "password", function(registered) { + equal(registered, false); + start(); + }, testHelpers.unexpectedFailure); + }); + + asyncTest("completePasswordReset with XHR failure", function() { + failureCheck(network.completePasswordReset, "token", "password"); + }); + + + asyncTest("checkPasswordReset pending", function() { + testVerificationPending("checkPasswordReset"); + }); + + + + + asyncTest("requestEmailReverify - true status", function() { + network.requestEmailReverify(TEST_EMAIL, "origin", function onSuccess(status) { + equal(status, true, "password reset request success"); + start(); + }, testHelpers.unexpectedFailure); + }); + + asyncTest("requestEmailReverify with XHR failure", function() { + failureCheck(network.requestEmailReverify, TEST_EMAIL, "origin"); + }); + + asyncTest("completeEmailReverify with valid token, no password required", function() { + network.completeEmailReverify("token", undefined, function(registered) { + ok(registered); + start(); + }, testHelpers.unexpectedFailure); + }); + + asyncTest("completeEmailReverify with valid token, bad password", function() { + transport.useResult("badPassword"); + network.completeEmailReverify("token", "password", + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure); + }); + + asyncTest("completeEmailReverify with valid token, password required", function() { + network.completeEmailReverify("token", "password", function(registered) { + ok(registered); + start(); + }, testHelpers.unexpectedFailure); + }); + + asyncTest("completeEmailReverify with invalid token", function() { + transport.useResult("invalid"); + + network.completeEmailReverify("token", "password", function(registered) { + equal(registered, false); + start(); + }, testHelpers.unexpectedFailure); + }); + + asyncTest("completeEmailReverify with XHR failure", function() { + failureCheck(network.completeEmailReverify, "token", "password"); + }); + + asyncTest("checkEmailReverify pending", function() { + testVerificationPending("checkEmailReverify"); + }); + + + + asyncTest("setPassword happy case expects true status", function() { network.setPassword("password", function onComplete(status) { equal(status, true, "correct status"); diff --git a/resources/static/test/cases/common/js/user.js b/resources/static/test/cases/common/js/user.js index 0a4b1d6b80cb5e81c55cc41641b9aa61f2c071b3..ce212f120bc5d56c6f0360347c295f5022ea554c 100644 --- a/resources/static/test/cases/common/js/user.js +++ b/resources/static/test/cases/common/js/user.js @@ -65,7 +65,7 @@ var jwcrypto = require("./lib/jwcrypto"); }); } - module("shared/user", { + module("common/js/user", { setup: function() { testHelpers.setup(); }, @@ -558,6 +558,100 @@ var jwcrypto = require("./lib/jwcrypto"); ); }); + asyncTest("requestEmailReverify with owned verified email - false status", function() { + storage.addSecondaryEmail(TEST_EMAIL, { verified: true }); + + var returnTo = "http://samplerp.org"; + lib.setReturnTo(returnTo); + lib.requestEmailReverify(TEST_EMAIL, function(status) { + testObjectValuesEqual(status, { + success: false, + reason: "verified_email" + }); + + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("requestEmailReverify with owned unverified email - false status", function() { + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); + + var returnTo = "http://samplerp.org"; + lib.setReturnTo(returnTo); + lib.requestEmailReverify(TEST_EMAIL, function(status) { + equal(status.success, true, "password reset for known user"); + equal(storage.getReturnTo(), returnTo, "RP URL is stored for verification"); + + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("requestEmailReverify with unowned email - false status, invalid_user", function() { + lib.requestEmailReverify(TEST_EMAIL, function(status) { + testObjectValuesEqual(status, { + success: false, + reason: "invalid_email" + }); + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("requestEmailReverify owned email with throttle - false status, throttle", function() { + xhr.useResult("throttle"); + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); + + lib.requestEmailReverify(TEST_EMAIL, function(status) { + testObjectValuesEqual(status, { + success: false, + reason: "throttle" + }); + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("requestEmailReverify with XHR failure", function() { + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); + failureCheck(lib.requestEmailReverify, TEST_EMAIL); + }); + + asyncTest("completeEmailReverify with a good token", function() { + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); + storage.setReturnTo(testOrigin); + + lib.completeEmailReverify("token", "password", function onSuccess(info) { + testObjectValuesEqual(info, { + valid: true, + email: TEST_EMAIL, + returnTo: testOrigin, + }); + + equal(storage.getReturnTo(), "", "initiating origin was removed"); + equal(storage.getEmail(TEST_EMAIL).verified, true, "email now marked as verified"); + + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("completeEmailReverify with a bad token", function() { + xhr.useResult("invalid"); + + lib.completeEmailReverify("token", "password", function onSuccess(info) { + equal(info.valid, false, "bad token calls onSuccess with a false validity"); + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("completeEmailReverify with an XHR failure", function() { + xhr.useResult("ajaxError"); + + lib.completeEmailReverify( + "token", + "password", + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); + }); + asyncTest("authenticate with valid credentials, also syncs email with server", function() { lib.authenticate(TEST_EMAIL, "testuser", function(authenticated) { diff --git a/resources/static/test/cases/dialog/js/misc/state.js b/resources/static/test/cases/dialog/js/misc/state.js index 95ac670d42a642db6b2d0c5ae627c71947f9d6e4..86f919450f623b4f3b68f6d2abbba48d4a28528b 100644 --- a/resources/static/test/cases/dialog/js/misc/state.js +++ b/resources/static/test/cases/dialog/js/misc/state.js @@ -42,6 +42,31 @@ } } + function testAddressStaged(startMessage, expectedAction) { + // password_reset_staged indicates the user has verified that they want to reset + // their password. + mediator.publish(startMessage, { + email: TEST_EMAIL + }); + equal(actions.info[expectedAction].email, TEST_EMAIL, expectedAction + " with the correct email"); + + // At this point the user should be displayed the "go confirm your address" + // screen. + + // user_confirmed means the user has confirmed their email and the dialog + // has received the "complete" message from /wsapi/user_creation_status. + try { + mediator.publish("staged_address_confirmed"); + } catch(e) { + // Exception is expected because as part of the user confirmation + // process, before user_confirmed is called, email addresses are synced. + // Addresses are not synced in this test. + equal(e.toString(), "invalid email", "expected failure"); + } + + } + + function createMachine() { machine = bid.State.create(); actions = new ActionsMock(); @@ -61,7 +86,7 @@ }); } - module("resources/state", { + module("dialog/js/misc/state", { setup: function() { testHelpers.setup(); createMachine(); @@ -128,11 +153,11 @@ equal(actions.info.doStageEmail.email, TEST_EMAIL, "correct email sent to doStageEmail"); }); - test("password_set for reset password - call doResetPassword with correct email", function() { + test("password_set for reset password - call doStageResetPassword with correct email", function() { mediator.publish("forgot_password", { email: TEST_EMAIL }); mediator.publish("password_set"); - equal(actions.info.doResetPassword.email, TEST_EMAIL, "correct email sent to doResetPassword"); + equal(actions.info.doStageResetPassword.email, TEST_EMAIL, "correct email sent to doStageResetPassword"); }); test("start - RPInfo always started", function() { @@ -146,9 +171,7 @@ }); test("user_staged - call doConfirmUser", function() { - mediator.publish("user_staged", { email: TEST_EMAIL }); - - equal(actions.info.doConfirmUser.email, TEST_EMAIL, "waiting for email confirmation for testuser@testuser.com"); + testAddressStaged("user_staged", "doConfirmUser"); }); test("user_staged with required email - call doConfirmUser with required = true", function() { @@ -176,9 +199,7 @@ }); test("email_staged - call doConfirmEmail", function() { - mediator.publish("email_staged", { email: TEST_EMAIL }); - - equal(actions.info.doConfirmEmail.required, false, "doConfirmEmail called without required flag"); + testAddressStaged("email_staged", "doConfirmEmail"); }); test("email_staged with required email - call doConfirmEmail with required = true", function() { @@ -281,42 +302,11 @@ testActionStarted("doForgotPassword", { email: TEST_EMAIL, requiredEmail: true }); }); - test("password_reset to user_confirmed - call doUserStaged then doEmailConfirmed", function() { - // password_reset indicates the user has verified that they want to reset - // their password. - mediator.publish("password_reset", { - email: TEST_EMAIL - }); - equal(actions.info.doConfirmUser.email, TEST_EMAIL, "doConfirmUser with the correct email"); - - // At this point the user should be displayed the "go confirm your address" - // screen. - - // user_confirmed means the user has confirmed their email and the dialog - // has received the "complete" message from /wsapi/user_creation_status. - try { - mediator.publish("user_confirmed"); - } catch(e) { - // Exception is expected because as part of the user confirmation - // process, before user_confirmed is called, email addresses are synced. - // Addresses are not synced in this test. - equal(e.toString(), "invalid email", "expected failure"); - } + test("password_reset_staged to staged_address_confirmed - call doConfirmResetPassword then doEmailConfirmed", function() { + testAddressStaged("password_reset_staged", "doConfirmResetPassword"); }); - test("cancel password_reset flow - go two steps back", function() { - // we want to skip the "verify" screen of reset password and instead go two - // screens back. Do do this, we are simulating the steps necessary to get - // to the password_reset flow. - mediator.publish("authenticate"); - mediator.publish("forgot_password", undefined, { email: TEST_EMAIL }); - mediator.publish("password_reset"); - actions.info.doAuthenticate = {}; - mediator.publish("cancel_state"); - equal(actions.info.doAuthenticate.email, TEST_EMAIL, "authenticate called with the correct email"); - }); - asyncTest("assertion_generated with null assertion - redirect to pick_email", function() { mediator.subscribe("pick_email", function() { ok(true, "redirect to pick_email"); @@ -462,12 +452,12 @@ }); }); - function testUnverifiedEmailChosen(auth_level) { + function testReverifyEmailChosen(auth_level) { storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); xhr.setContextInfo("auth_level", auth_level); - mediator.subscribe("verify_unverified_email", function(msg, info) { - equal(info.email, TEST_EMAIL, "correctly redirected to verify_unverified_email with correct email"); + mediator.subscribe("stage_reverify_email", function(msg, info) { + equal(info.email, TEST_EMAIL, "correctly redirected to stage_reverify_email with correct email"); start(); }); @@ -476,12 +466,12 @@ }); } - asyncTest("email_chosen with unverified secondary email, user authenticated to secondary - redirect to verify_unverified_email", function() { - testUnverifiedEmailChosen("password"); + asyncTest("email_chosen with unverified secondary email, user authenticated to secondary - redirect to stage_reverify_email", function() { + testReverifyEmailChosen("password"); }); - asyncTest("email_chosen with unverified secondary email, user authenticated to primary - redirect to verify_unverified_email", function() { - testUnverifiedEmailChosen("assertion"); + asyncTest("email_chosen with unverified secondary email, user authenticated to primary - redirect to stage_reverify_email", function() { + testReverifyEmailChosen("assertion"); }); test("email_chosen with primary email - call doProvisionPrimaryUser", function() { @@ -565,8 +555,14 @@ }); }); - asyncTest("verify_unverified_email", function() { - start(); + test("stage_reverify_email - call doStageReverifyEmail", function() { + mediator.publish("start", { siteName: "Unit Test Site" }); + mediator.publish("stage_reverify_email", { email: TEST_EMAIL }); + testActionStarted("doStageReverifyEmail", { email: TEST_EMAIL, siteName: "Unit Test Site" }); + }); + + test("unverified_email_staged - call doConfirmReverifyEmail", function() { + }); asyncTest("window_unload - set the final KPIs", function() { diff --git a/resources/static/test/cases/dialog/js/modules/actions.js b/resources/static/test/cases/dialog/js/modules/actions.js index 3f98fc81b8d968879c1c34738a00b4eab5500111..15f76f52ceebe272cb6d7dc7c5a144da05579ee5 100644 --- a/resources/static/test/cases/dialog/js/modules/actions.js +++ b/resources/static/test/cases/dialog/js/modules/actions.js @@ -8,6 +8,7 @@ var bid = BrowserID, user = bid.User, + storage = bid.Storage, controller, el, testHelpers = bid.TestHelpers, @@ -34,7 +35,29 @@ }); } - module("controllers/actions", { + function testStageAddress(actionName, expectedMessage) { + createController({ + ready: function() { + var message, + email; + + testHelpers.register(expectedMessage, function(msg, info) { + message = msg; + email = info.email; + }); + + controller[actionName]({ email: TEST_EMAIL, password: "password", ready: function(status) { + equal(status, true, "correct status"); + equal(message, expectedMessage, "correct message triggered"); + equal(email, TEST_EMAIL, "address successfully staged"); + start(); + }}); + } + }); + } + + + module("dialog/js/modules/actions", { setup: function() { testHelpers.setup(); }, @@ -74,39 +97,50 @@ "primary_user_provisioned"); }); + asyncTest("doStageUser with successful creation - trigger user_staged", function() { + testStageAddress("doStageUser", "user_staged"); + }); + asyncTest("doConfirmUser - start the check_registration service", function() { testActionStartsModule("doConfirmUser", {email: TEST_EMAIL, siteName: "Unit Test Site"}, "check_registration"); }); + asyncTest("doStageEmail with successful staging - trigger email_staged", function() { + testStageAddress("doStageEmail", "email_staged"); + }); + asyncTest("doConfirmEmail - start the check_registration service", function() { testActionStartsModule("doConfirmEmail", {email: TEST_EMAIL, siteName: "Unit Test Site"}, "check_registration"); }); - asyncTest("doGenerateAssertion - start the generate_assertion service", function() { - testActionStartsModule('doGenerateAssertion', { email: TEST_EMAIL }, "generate_assertion"); + asyncTest("doForgotPassword - call the set_password controller with reset_password true", function() { + testActionStartsModule('doForgotPassword', { email: TEST_EMAIL }, "set_password"); }); - asyncTest("doStageUser with successful creation - trigger user_staged", function() { - createController({ - ready: function() { - var email; - testHelpers.register("user_staged", function(msg, info) { - email = info.email; - }); + asyncTest("doStageResetPassword - trigger password_reset_staged", function() { + testStageAddress("doStageResetPassword", "password_reset_staged"); + }); - controller.doStageUser({ email: TEST_EMAIL, password: "password", ready: function(status) { - equal(status, true, "correct status"); - equal(email, TEST_EMAIL, "user successfully staged"); - start(); - }}); - } - }); + asyncTest("doConfirmResetPassword - start the check_registration service", function() { + testActionStartsModule("doConfirmResetPassword", {email: TEST_EMAIL, siteName: "Unit Test Site"}, + "check_registration"); }); - asyncTest("doForgotPassword - call the set_password controller with reset_password true", function() { - testActionStartsModule('doForgotPassword', { email: TEST_EMAIL }, "set_password"); + asyncTest("doStageReverifyEmail - trigger reverify_email_staged", function() { + + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); + testStageAddress("doStageReverifyEmail", "reverify_email_staged"); + }); + + asyncTest("doConfirmReverifyEmail - start the check_registration service", function() { + testActionStartsModule("doConfirmReverifyEmail", {email: TEST_EMAIL, siteName: "Unit Test Site"}, + "check_registration"); + }); + + asyncTest("doGenerateAssertion - start the generate_assertion service", function() { + testActionStartsModule('doGenerateAssertion', { email: TEST_EMAIL }, "generate_assertion"); }); asyncTest("doRPInfo - start the rp_info service", function() { diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js index b01a6ac766c77407be9a085e90d61f8e53a226a2..5b458fbebb9cb7468a4d616d7adb789db9c0ede9 100644 --- a/resources/static/test/mocks/xhr.js +++ b/resources/static/test/mocks/xhr.js @@ -74,6 +74,17 @@ BrowserID.Mocks.xhr = (function() { "post /wsapi/complete_reset invalid": { success: false }, "post /wsapi/complete_reset ajaxError": undefined, + "post /wsapi/stage_reverify unknown_secondary": { success: true }, + "post /wsapi/stage_reverify valid": { success: true }, + "post /wsapi/stage_reverify invalid": { success: false }, + "post /wsapi/stage_reverify throttle": 429, + "post /wsapi/stage_reverify ajaxError": undefined, + + "post /wsapi/complete_reverify valid": { success: true }, + "post /wsapi/complete_reverify badPassword": 401, + "post /wsapi/complete_reverify invalid": { success: false }, + "post /wsapi/complete_reverify ajaxError": undefined, + "get /wsapi/user_creation_status?email=registered%40testuser.com pending": { status: "pending" }, "get /wsapi/user_creation_status?email=registered%40testuser.com complete": { status: "complete", userid: 4 }, "get /wsapi/user_creation_status?email=registered%40testuser.com mustAuth": { status: "mustAuth" }, @@ -89,6 +100,9 @@ BrowserID.Mocks.xhr = (function() { "get /wsapi/have_email?email=registered%40testuser.com valid": { email_known: true }, "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 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 }, "post /wsapi/remove_email valid": { success: true },