diff --git a/resources/static/common/js/network.js b/resources/static/common/js/network.js index e571992c60049b0fce982f451decef332309ca94..2a38f461eb319abe90817655a95a737b1d839ae9 100644 --- a/resources/static/common/js/network.js +++ b/resources/static/common/js/network.js @@ -83,6 +83,28 @@ BrowserID.Network = (function() { } } + function stageEmailForVerification(email, password, origin, wsapiName, onComplete, onFailure) { + post({ + url: wsapiName, + data: { + email: email, + pass: password, + site : origin + }, + success: function(status) { + complete(onComplete, status.success); + }, + error: function(info) { + // 429 is throttling. + if (info.network.status === 429) { + complete(onComplete, false); + } + else complete(onFailure, info); + } + }); + } + + var Network = { /** * Initialize - Clear all context info. Used for testing. @@ -213,24 +235,7 @@ BrowserID.Network = (function() { * @param {function} [onFailure] - Called on XHR failure. */ createUser: function(email, password, origin, onComplete, onFailure) { - post({ - url: "/wsapi/stage_user", - data: { - email: email, - pass: password, - site : origin - }, - success: function(status) { - complete(onComplete, status.success); - }, - error: function(info) { - // 429 is throttling. - if (info.network.status === 429) { - complete(onComplete, false); - } - else complete(onFailure, info); - } - }); + stageEmailForVerification(email, password, origin, "/wsapi/stage_user", onComplete, onFailure); }, /** @@ -312,7 +317,7 @@ BrowserID.Network = (function() { */ completeUserResetPassword: function(token, password, onComplete, onFailure) { post({ - url: "/wsapi/complete_user_reset_password", + url: "/wsapi/complete_reset", data: { token: token, pass: password @@ -360,7 +365,7 @@ BrowserID.Network = (function() { */ completeEmailResetPassword: function(token, password, onComplete, onFailure) { post({ - url: "/wsapi/complete_email_reset_password", + url: "/wsapi/complete_reset", data: { token: token, pass: password @@ -386,12 +391,7 @@ BrowserID.Network = (function() { * @param {function} [onFailure] - Called on XHR failure. */ requestPasswordReset: function(email, password, origin, onComplete, onFailure) { - if (email) { - Network.createUser(email, password, origin, onComplete, onFailure); - } else { - // TODO: if no email is provided, then what? - throw "no email provided to password reset"; - } + stageEmailForVerification(email, password, origin, "/wsapi/stage_reset", onComplete, onFailure); }, /** @@ -504,27 +504,9 @@ BrowserID.Network = (function() { * @param {function} [onFailure] - called on xhr failure. */ addSecondaryEmail: function(email, password, origin, onComplete, onFailure) { - post({ - url: "/wsapi/stage_email", - data: { - email: email, - pass: password, - site: origin - }, - success: function(response) { - complete(onComplete, response.success); - }, - error: function(info) { - // 429 is throttling. - if (info.network.status === 429) { - complete(onComplete, false); - } - else complete(onFailure, info); - } - }); + stageEmailForVerification(email, password, origin, "/wsapi/stage_email", onComplete, onFailure); }, - /** * Check the registration status of an email * @method checkEmailRegistration diff --git a/resources/static/common/js/user.js b/resources/static/common/js/user.js index 6c04fe3c6e7dce51c29b0f55b45bb0289b5158d9..9d0cb8150c6d56961eb2bb58b621d5c777b7d8a8 100644 --- a/resources/static/common/js/user.js +++ b/resources/static/common/js/user.js @@ -206,30 +206,40 @@ BrowserID.User = (function() { /** * Persist an email address without a keypair * @method persistEmail - * @param {string} email - Email address to persist. - * @param {string} type - Is the email a 'primary' or a 'secondary' address? - * @param {function} [onComplete] - Called on successful completion. - * @param {function} [onFailure] - Called on error. + * @param {object} options - options to save + * @param {string} options.email - Email address to persist. + * @param {string} options.type - Is the email a 'primary' or a 'secondary' address? + * @param {string} options.verified - If the email is 'secondary', is it verified? */ - function persistEmail(email, type, onComplete, onFailure) { - checkEmailType(type); - storage.addEmail(email, { + function persistEmail(options) { + checkEmailType(options.type); + storage.addEmail(options.email, { created: new Date(), - type: type + type: options.type, + verified: options.verified }); - - if (onComplete) onComplete(true); } - function verifyAddress(token, password, callName, onComplete, onFailure) { + function verifyAddress(token, password, networkFuncName, onComplete, onFailure) { User.tokenInfo(token, function(info) { var invalidInfo = { valid: false }; if (info) { - network[callName](token, password, function (valid) { + network[networkFuncName](token, password, function (valid) { var result = invalidInfo; - if(valid) { + if (valid) { result = _.extend({ valid: valid }, info); + var email = info.email, + idInfo = storage.getEmail(email); + + // Now that the address is verified, its verified bit has to be + // updated as well or else the user will be forced to verify the + // address again. + if (idInfo) { + idInfo.verified = true; + storage.addEmail(email, idInfo); + } + storage.setReturnTo(""); } @@ -680,29 +690,39 @@ BrowserID.User = (function() { var emails_to_add = _.difference(server_emails, client_emails); var emails_to_remove = _.difference(client_emails, server_emails); + var emails_to_update = _.intersection(client_emails, server_emails); // remove emails _.each(emails_to_remove, function(email) { storage.removeEmail(email); }); - // keygen for new emails - // asynchronous - function addNextEmail() { - if (!emails_to_add || !emails_to_add.length) { - onComplete(); - return; - } + // these are new emails + _.each(emails_to_add, function(email) { + var emailInfo = emails[email]; + + persistEmail({ + email: email, + type: emailInfo.type || "secondary", + verified: emailInfo.verified + }); + }); - var email = emails_to_add.shift(); + // update the type and verified status of stored emails + _.each(emails_to_update, function(email) { + var emailInfo = emails[email], + storedEmailInfo = storage.getEmail(email); - // extract the email type from the server response, if it - // doesn't exist, assume secondary - var type = emails[email].type || "secondary"; - persistEmail(email, type, addNextEmail, onFailure); - } + _.extend(storedEmailInfo, { + type: emailInfo.type, + verified: emailInfo.verified + }); + + storage.addEmail(email, storedEmailInfo); + }); + + complete(onComplete); - addNextEmail(); }, onFailure); }); }, diff --git a/resources/static/test/cases/common/js/user.js b/resources/static/test/cases/common/js/user.js index 293f9168f733db8d0274e0f139da32ddc035a28d..0a4b1d6b80cb5e81c55cc41641b9aa61f2c071b3 100644 --- a/resources/static/test/cases/common/js/user.js +++ b/resources/static/test/cases/common/js/user.js @@ -412,13 +412,17 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("verifyUser with a good token", function() { storage.setReturnTo(testOrigin); + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); lib.verifyUser("token", "password", function onSuccess(info) { - ok(info.valid, "token was valid"); - equal(info.email, TEST_EMAIL, "email part of info"); - equal(info.returnTo, testOrigin, "returnTo in 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 marked as verified"); start(); }, testHelpers.unexpectedXHRFailure); @@ -517,10 +521,10 @@ var jwcrypto = require("./lib/jwcrypto"); }); asyncTest("verifyPasswordReset with a good token", function() { + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); storage.setReturnTo(testOrigin); lib.verifyPasswordReset("token", "password", function onSuccess(info) { - testObjectValuesEqual(info, { valid: true, email: TEST_EMAIL, @@ -528,6 +532,7 @@ var jwcrypto = require("./lib/jwcrypto"); }); equal(storage.getReturnTo(), "", "initiating origin was removed"); + equal(storage.getEmail(TEST_EMAIL).verified, true, "email now marked as verified"); start(); }, testHelpers.unexpectedXHRFailure); @@ -830,12 +835,15 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("verifyEmail with a good token - callback with email, returnTo, valid", function() { storage.setReturnTo(testOrigin); + storage.addSecondaryEmail(TEST_EMAIL, { verified: false }); lib.verifyEmail("token", "password", function onSuccess(info) { - - ok(info.valid, "token was valid"); - equal(info.email, TEST_EMAIL, "email part of info"); - equal(info.returnTo, testOrigin, "returnTo in info"); + testObjectValuesEqual(info, { + valid: true, + email: TEST_EMAIL, + returnTo: testOrigin + }); equal(storage.getReturnTo(), "", "initiating returnTo was removed"); + equal(storage.getEmail(TEST_EMAIL).verified, true, "email now marked as verified"); start(); }, testHelpers.unexpectedXHRFailure); @@ -982,12 +990,16 @@ var jwcrypto = require("./lib/jwcrypto"); }, testHelpers.unexpectedXHRFailure); }); - asyncTest("syncEmails with one to refresh", function() { - storage.addEmail(TEST_EMAIL, {pub: pubkey, cert: random_cert}); + asyncTest("syncEmails with one to update", function() { + // verified is set to false here, the mock for list_emails has verified + // set to true. If emails are being updated, verified will be set to true + // whenever syncEmails is complete. + storage.addEmail(TEST_EMAIL, {pub: pubkey, cert: random_cert, verified: false}); lib.syncEmails(function onSuccess() { var identities = lib.getStoredEmailKeypairs(); ok(TEST_EMAIL in identities, "refreshed key is synced"); + equal(identities[TEST_EMAIL].verified, true, "verified was correctly updated"); start(); }, testHelpers.unexpectedXHRFailure); }); diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js index b48d6c8e7506cc445f574690fffa84162dd1f85b..b01a6ac766c77407be9a085e90d61f8e53a226a2 100644 --- a/resources/static/test/mocks/xhr.js +++ b/resources/static/test/mocks/xhr.js @@ -57,17 +57,23 @@ BrowserID.Mocks.xhr = (function() { "post /wsapi/complete_email_addition badPassword": 401, "post /wsapi/complete_email_addition invalid": { success: false }, "post /wsapi/complete_email_addition ajaxError": undefined, - /** BEGIN May not be needed **/ - "post /wsapi/complete_email_reset_password valid": { success: true }, - "post /wsapi/complete_email_reset_password badPassword": 401, - "post /wsapi/complete_email_reset_password invalid": { success: false }, - "post /wsapi/complete_email_reset_password ajaxError": undefined, - /** END May not be needed **/ "post /wsapi/stage_user unknown_secondary": { success: true }, "post /wsapi/stage_user valid": { success: true }, "post /wsapi/stage_user invalid": { success: false }, "post /wsapi/stage_user throttle": 429, "post /wsapi/stage_user ajaxError": undefined, + + "post /wsapi/stage_reset unknown_secondary": { success: true }, + "post /wsapi/stage_reset valid": { success: true }, + "post /wsapi/stage_reset invalid": { success: false }, + "post /wsapi/stage_reset throttle": 429, + "post /wsapi/stage_reset ajaxError": undefined, + + "post /wsapi/complete_reset valid": { success: true }, + "post /wsapi/complete_reset badPassword": 401, + "post /wsapi/complete_reset invalid": { success: false }, + "post /wsapi/complete_reset 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" }, @@ -77,10 +83,6 @@ BrowserID.Mocks.xhr = (function() { "post /wsapi/complete_user_creation badPassword": 401, "post /wsapi/complete_user_creation invalid": { success: false }, "post /wsapi/complete_user_creation ajaxError": undefined, - "post /wsapi/complete_user_reset_password valid": { success: true }, - "post /wsapi/complete_user_reset_password badPassword": 401, - "post /wsapi/complete_user_reset_password invalid": { success: false }, - "post /wsapi/complete_user_reset_password ajaxError": undefined, "post /wsapi/logout valid": { success: true }, "post /wsapi/logout not_authenticated": 400, "post /wsapi/logout ajaxError": 401,