diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js index 0cc46aab5a3dadfbe852c2ddf458b2550815fc89..b64dcbf422e5a04a4ecfadbc109b859116f6d266 100644 --- a/resources/static/dialog/controllers/dialog.js +++ b/resources/static/dialog/controllers/dialog.js @@ -84,16 +84,19 @@ BrowserID.Modules.Dialog = (function() { if (win.location.hash == "#NATIVE" || win.location.hash == "#INTERNAL") { // don't do winchan, let it be. return; - } + } try { - // WinChan.onOpen(function(origin, args, cb) { - self.get(origin, args.params, function(r) { - cb(r); - }, function (e) { - cb(null); - }); + // XXX this is called whenever the primary provisioning iframe gets + // added. If there are no args, then do not do self.get. + if(args) { + self.get(origin, args.params, function(r) { + cb(r); + }, function (e) { + cb(null); + }); + } }); } catch (e) { self.renderError("error", { diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js index 4449bc51858fa427a4545521683c5e46efcf56b5..28d45ca23144a02566e23ce0b3f4096347528790 100644 --- a/resources/static/dialog/resources/helpers.js +++ b/resources/static/dialog/resources/helpers.js @@ -96,7 +96,7 @@ } var self=this; - user.createUser(email, function(status) { + user.createUser(email, function(status, info) { switch(status) { case "secondary.already_added": // XXX how to handle this - createUser should not be called on @@ -125,7 +125,8 @@ break; case "primary.verify": self.close("primary_verify_user", { - email: email + email: email, + auth_url: info.auth }); complete(true); break; diff --git a/resources/static/pages/signup.js b/resources/static/pages/signup.js index a70e72fc2b558cf97f595e7b8fb060e43e0e5a51..0613649254d78d265dd70e627e5db3cfd5b44bdb 100644 --- a/resources/static/pages/signup.js +++ b/resources/static/pages/signup.js @@ -46,12 +46,25 @@ BrowserID.signUp = (function() { errors = bid.Errors, tooltip = BrowserID.Tooltip, ANIMATION_SPEED = 250, - storedEmail = pageHelpers; + storedEmail = pageHelpers, + win = window, + verifyEmail, + verifyURL; function showNotice(selector) { $(selector).fadeIn(ANIMATION_SPEED); } + function verifyWithPrimary(oncomplete) { + if(!(verifyEmail && verifyURL)) { + throw "cannot verify with primary without an email address and URL" + } + + var url = verifyURL + "?email=" + encodeURIComponent(verifyEmail); + win.open(url, "_moz_primary_verification", "width: 500px, height: 500px"); + oncomplete && oncomplete(); + } + function submit(oncomplete) { var email = helpers.getAndValidateEmail("#email"); @@ -60,7 +73,7 @@ BrowserID.signUp = (function() { } if (email) { - user.createUser(email, function onComplete(status) { + user.createUser(email, function onComplete(status, info) { switch(status) { case "secondary.already_added": $('#registeredEmail').html(email); @@ -81,8 +94,10 @@ BrowserID.signUp = (function() { pageHelpers.replaceInputsWithNotice("#congrats", complete.bind(null, true)); break; case "primary.verify": - // XXX What do we do here? - complete(false); + verifyEmail = email; + verifyURL = info.auth; + dom.setInner("#primary_email", email); + pageHelpers.replaceInputsWithNotice("#primary_verify", complete.bind(null, false)); break; case "primary.could_not_add": // XXX Can this happen? @@ -108,6 +123,10 @@ BrowserID.signUp = (function() { function init(config) { config = config || {}; + if(config.window) { + win = config.window; + } + $("form input[autofocus]").focus(); pageHelpers.setupEmail(); @@ -115,6 +134,7 @@ BrowserID.signUp = (function() { dom.bindEvent("#email", "keyup", onEmailKeyUp); dom.bindEvent("form", "submit", cancelEvent(submit)); dom.bindEvent("#back", "click", cancelEvent(back)); + dom.bindEvent("#verifyWithPrimary", "click", cancelEvent(verifyWithPrimary)); } // BEGIN TESTING API @@ -122,11 +142,15 @@ BrowserID.signUp = (function() { dom.unbindEvent("#email", "keyup"); dom.unbindEvent("form", "submit"); dom.unbindEvent("#back", "click"); + dom.unbindEvent("#verifyWithPrimary", "click"); + win = window; + verifyEmail = verifyURL = null; } init.submit = submit; init.reset = reset; init.back = back; + init.verifyWithPrimary = verifyWithPrimary; // END TESTING API return init; diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js index 16c0f60b82cedfe6118b6b9370f1129fb0ac9e84..f253372cdd281e14e17a53fd10ab7834fb373779 100644 --- a/resources/static/shared/network.js +++ b/resources/static/shared/network.js @@ -149,6 +149,24 @@ BrowserID.Network = (function() { csrf_token = server_time = auth_status = undef; } + function handleAuthenticationResponse(onComplete, onFailure, status) { + if (onComplete) { + try { + var authenticated = status.success; + + if (typeof authenticated !== 'boolean') throw status; + + // at this point we know the authentication status of the + // session, let's set it to perhaps save a network request + // (to fetch session context). + auth_status = authenticated; + if (onComplete) onComplete(authenticated); + } catch (e) { + onFailure("unexpected server response: " + e); + } + } + } + // Not really part of the Network API, but related to networking $(document).bind("offline", function() { mediator.publish("offline"); @@ -170,33 +188,39 @@ BrowserID.Network = (function() { * @method authenticate * @param {string} email - address to authenticate * @param {string} password - password. - * @param {function} [onSuccess] - callback to call for success + * @param {function} [onComplete] - callback to call when complete. Called + * with status parameter - true if authenticated, false otw. * @param {function} [onFailure] - called on XHR failure */ - authenticate: function(email, password, onSuccess, onFailure) { + authenticate: function(email, password, onComplete, onFailure) { post({ url: "/wsapi/authenticate_user", data: { email: email, pass: password }, - success: function(status, textStatus, jqXHR) { - if (onSuccess) { - try { - var authenticated = status.success; - - if (typeof authenticated !== 'boolean') throw status; - - // at this point we know the authentication status of the - // session, let's set it to perhaps save a network request - // (to fetch session context). - auth_status = authenticated; - if (onSuccess) onSuccess(authenticated); - } catch (e) { - onFailure("unexpected server response: " + e); - } - } + success: handleAuthenticationResponse.bind(null, onComplete, onFailure), + error: onFailure + }); + }, + + /** + * Authenticate with a primary generated assertion + * @method authenticateWithAssertion + * @param {string} email - address to authenticate + * @param {string} assertion + * @param {function} [onComplete] - callback to call when complete. Called + * with status parameter - true if authenticated, false otw. + * @param {function} [onFailure] - called on XHR failure + */ + authenticateWithAssertion: function(email, assertion, onComplete, onFailure) { + post({ + url: "/wsapi/auth_with_assertion", + data: { + email: email, + assertion: assertion }, + success: handleAuthenticationResponse.bind(null, onComplete, onFailure), error: onFailure }); }, @@ -204,15 +228,15 @@ BrowserID.Network = (function() { /** * Check whether a user is currently logged in. * @method checkAuth - * @param {function} [onSuccess] - Success callback, called with one + * @param {function} [onComplete] - called with one * boolean parameter, whether the user is authenticated. * @param {function} [onFailure] - called on XHR failure. */ - checkAuth: function(onSuccess, onFailure) { + checkAuth: function(onComplete, onFailure) { withContext(function() { try { if (typeof auth_status !== 'boolean') throw "can't get authentication status!"; - if (onSuccess) onSuccess(auth_status); + if (onComplete) onComplete(auth_status); } catch(e) { if (onFailure) onFailure(e.toString()); } @@ -222,10 +246,10 @@ BrowserID.Network = (function() { /** * Log the authenticated user out * @method logout - * @param {function} [onSuccess] - called on completion + * @param {function} [onComplete] - called on completion * @param {function} [onFailure] - Called on XHR failure. */ - logout: function(onSuccess, onFailure) { + logout: function(onComplete, onFailure) { post({ url: "/wsapi/logout", success: function() { @@ -235,7 +259,7 @@ BrowserID.Network = (function() { // FIXME: we should return a confirmation that the // user was successfully logged out. auth_status = false; - if (onSuccess) onSuccess(); + if (onComplete) onComplete(); }, error: onFailure }); @@ -246,10 +270,10 @@ BrowserID.Network = (function() { * @method createUser * @param {string} email - Email address to prepare. * @param {string} origin - site user is trying to sign in to. - * @param {function} [onSuccess] - Callback to call when complete. + * @param {function} [onComplete] - Callback to call when complete. * @param {function} [onFailure] - Called on XHR failure. */ - createUser: function(email, origin, onSuccess, onFailure) { + createUser: function(email, origin, onComplete, onFailure) { post({ url: "/wsapi/stage_user", data: { @@ -257,12 +281,12 @@ BrowserID.Network = (function() { site : origin }, success: function(status) { - if (onSuccess) onSuccess(status.success); + if (onComplete) onComplete(status.success); }, error: function(info) { // 403 is throttling. if (info.network.status === 403) { - if (onSuccess) onSuccess(false); + if (onComplete) onComplete(false); } else if (onFailure) onFailure(info); } @@ -277,11 +301,11 @@ BrowserID.Network = (function() { * TODO: think about whether this requires the right cookie * I think so (BA). */ - emailForVerificationToken: function(token, onSuccess, onFailure) { + emailForVerificationToken: function(token, onComplete, onFailure) { get({ url : "/wsapi/email_for_token?token=" + encodeURIComponent(token), success: function(data) { - if (onSuccess) onSuccess(data.email); + if (onComplete) onComplete(data.email); }, error: onFailure }); @@ -290,14 +314,14 @@ BrowserID.Network = (function() { /** * Check the current user"s registration status * @method checkUserRegistration - * @param {function} [onSuccess] - Called when complete. + * @param {function} [onComplete] - Called when complete. * @param {function} [onFailure] - Called on XHR failure. */ - checkUserRegistration: function(email, onSuccess, onFailure) { + checkUserRegistration: function(email, onComplete, onFailure) { get({ url: "/wsapi/user_creation_status?email=" + encodeURIComponent(email), success: function(status, textStatus, jqXHR) { - if (onSuccess) onSuccess(status.status); + if (onComplete) onComplete(status.status); }, error: onFailure }); @@ -308,10 +332,10 @@ BrowserID.Network = (function() { * @method completeUserRegistration * @param {string} token - token to register for. * @param {string} password - password to register for account. - * @param {function} [onSuccess] - Called when complete. + * @param {function} [onComplete] - Called when complete. * @param {function} [onFailure] - Called on XHR failure. */ - completeUserRegistration: function(token, password, onSuccess, onFailure) { + completeUserRegistration: function(token, password, onComplete, onFailure) { post({ url: "/wsapi/complete_user_creation", data: { @@ -319,7 +343,7 @@ BrowserID.Network = (function() { pass: password }, success: function(status, textStatus, jqXHR) { - if (onSuccess) onSuccess(status.success); + if (onComplete) onComplete(status.success); }, error: onFailure }); @@ -329,18 +353,18 @@ BrowserID.Network = (function() { * Call with a token to prove an email address ownership. * @method completeEmailRegistration * @param {string} token - token proving email ownership. - * @param {function} [onSuccess] - Callback to call when complete. Called + * @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. */ - completeEmailRegistration: function(token, onSuccess, onFailure) { + completeEmailRegistration: function(token, onComplete, onFailure) { post({ url: "/wsapi/complete_email_addition", data: { token: token }, success: function(status, textStatus, jqXHR) { - if (onSuccess) onSuccess(status.success); + if (onComplete) onComplete(status.success); }, error: onFailure }); @@ -350,12 +374,12 @@ BrowserID.Network = (function() { * 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. * @param {function} [onFailure] - Called on XHR failure. */ - requestPasswordReset: function(email, origin, onSuccess, onFailure) { + requestPasswordReset: function(email, origin, onComplete, onFailure) { if (email) { - Network.createUser(email, origin, onSuccess, onFailure); + Network.createUser(email, origin, onComplete, onFailure); } else { // TODO: if no email is provided, then what? throw "no email provided to password reset"; @@ -366,12 +390,12 @@ BrowserID.Network = (function() { * Update the password of the current user. This is for a password reseT * @method resetPassword * @param {string} password - new password. - * @param {function} [onSuccess] - Callback to call when complete. + * @param {function} [onComplete] - Callback to call when complete. * @param {function} [onFailure] - Called on XHR failure. */ - resetPassword: function(password, onSuccess, onFailure) { + resetPassword: function(password, onComplete, onFailure) { // XXX fill this in. - if (onSuccess) onSuccess(); + if (onComplete) onComplete(); }, /** @@ -390,8 +414,8 @@ BrowserID.Network = (function() { oldpass: oldPassword, newpass: newPassword }, - success: function(response) { - if (onComplete) onComplete(response.success); + success: function(status) { + if (onComplete) onComplete(status.success); }, error: onFailure }); @@ -401,13 +425,13 @@ BrowserID.Network = (function() { /** * Cancel the current user"s account. * @method cancelUser - * @param {function} [onSuccess] - called whenever complete. + * @param {function} [onComplete] - called whenever complete. * @param {function} [onFailure] - Called on XHR failure. */ - cancelUser: function(onSuccess, onFailure) { + cancelUser: function(onComplete, onFailure) { post({ url: "/wsapi/account_cancel", - success: onSuccess, + success: onComplete, error: onFailure }); }, @@ -417,10 +441,10 @@ BrowserID.Network = (function() { * @method addEmail * @param {string} email - Email address to add. * @param {string} origin - site user is trying to sign in to. - * @param {function} [onsuccess] - called when complete. - * @param {function} [onfailure] - called on xhr failure. + * @param {function} [onComplete] - called when complete. + * @param {function} [onFailure] - called on xhr failure. */ - addEmail: function(email, origin, onSuccess, onFailure) { + addEmail: function(email, origin, onComplete, onFailure) { post({ url: "/wsapi/stage_email", data: { @@ -428,12 +452,12 @@ BrowserID.Network = (function() { site: origin }, success: function(response) { - if (onSuccess) onSuccess(response.success); + if (onComplete) onComplete(response.success); }, error: function(info) { // 403 is throttling. if (info.network.status === 403) { - if (onSuccess) onSuccess(false); + if (onComplete) onComplete(false); } else if (onFailure) onFailure(info); } @@ -447,11 +471,11 @@ BrowserID.Network = (function() { * @param {function} [onsuccess] - called when complete. * @param {function} [onfailure] - called on xhr failure. */ - checkEmailRegistration: function(email, onSuccess, onFailure) { + checkEmailRegistration: function(email, onComplete, onFailure) { get({ url: "/wsapi/email_addition_status?email=" + encodeURIComponent(email), success: function(status, textStatus, jqXHR) { - if (onSuccess) onSuccess(status.status); + if (onComplete) onComplete(status.status); }, error: onFailure }); @@ -461,16 +485,16 @@ BrowserID.Network = (function() { * Check whether the email is already registered. * @method emailRegistered * @param {string} email - Email address to check. - * @param {function} [onSuccess] - Called with one boolean parameter when + * @param {function} [onComplete] - Called with one boolean parameter when * complete. Parameter is true if `email` is already registered, false * otw. * @param {function} [onFailure] - Called on XHR failure. */ - emailRegistered: function(email, onSuccess, onFailure) { + emailRegistered: function(email, onComplete, onFailure) { get({ url: "/wsapi/have_email?email=" + encodeURIComponent(email), success: function(data, textStatus, xhr) { - if (onSuccess) onSuccess(data.email_known); + if (onComplete) onComplete(data.email_known); }, error: onFailure }); @@ -503,17 +527,17 @@ BrowserID.Network = (function() { * Remove an email address from the current user. * @method removeEmail * @param {string} email - Email address to remove. - * @param {function} [onSuccess] - Called whenever complete. + * @param {function} [onComplete] - Called whenever complete. * @param {function} [onFailure] - Called on XHR failure. */ - removeEmail: function(email, onSuccess, onFailure) { + removeEmail: function(email, onComplete, onFailure) { post({ url: "/wsapi/remove_email", data: { email: email }, success: function(status, textStatus, jqXHR) { - if (onSuccess) onSuccess(status.success); + if (onComplete) onComplete(status.success); }, error: onFailure }); @@ -523,14 +547,14 @@ BrowserID.Network = (function() { * Certify the public key for the email address. * @method certKey */ - certKey: function(email, pubkey, onSuccess, onFailure) { + certKey: function(email, pubkey, onComplete, onFailure) { post({ url: "/wsapi/cert_key", data: { email: email, pubkey: pubkey.serialize() }, - success: onSuccess, + success: onComplete, error: onFailure }); }, @@ -539,10 +563,10 @@ BrowserID.Network = (function() { * List emails * @method listEmails */ - listEmails: function(onSuccess, onFailure) { + listEmails: function(onComplete, onFailure) { get({ url: "/wsapi/list_emails", - success: onSuccess, + success: onComplete, error: onFailure }); }, @@ -557,12 +581,12 @@ BrowserID.Network = (function() { * * @method serverTime */ - serverTime: function(onSuccess, onFailure) { + serverTime: function(onComplete, onFailure) { withContext(function() { try { if (!server_time) throw "can't get server time!"; var offset = (new Date()).getTime() - server_time.local; - if (onSuccess) onSuccess(new Date(offset + server_time.remote)); + if (onComplete) onComplete(new Date(offset + server_time.remote)); } catch(e) { if (onFailure) onFailure(e.toString()); } @@ -578,11 +602,11 @@ BrowserID.Network = (function() { * * @method domainKeyCreationTime */ - domainKeyCreationTime: function(onSuccess, onFailure) { + domainKeyCreationTime: function(onComplete, onFailure) { withContext(function() { try { if (!domain_key_creation_time) throw "can't get domain key creation time!"; - if (onSuccess) onSuccess(new Date(domain_key_creation_time)); + if (onComplete) onComplete(new Date(domain_key_creation_time)); } catch(e) { if (onFailure) onFailure(e.toString()); } diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js index 46a24d10a0bdefd87604d8a6966f88e17cb43a6f..10011b41daff13f5bfd30147b746b963593b2fad 100644 --- a/resources/static/shared/user.js +++ b/resources/static/shared/user.js @@ -296,9 +296,22 @@ BrowserID.User = (function() { }, /** - * Status: - * "already_added", "verify_secondary", "secondary_could_not_add", "verify_primary", - * "primary_verified" + * Create a user. Works for both primaries and secondaries. + * @method createUser + * @param {string} email + * @param {function} onComplete - function to call on complettion. Called + * with two parameters - status and info. + * Status can be: + * secondary.already_added + * secondary.verify + * secondary.could_not_add + * primary.already_added + * primary.verified + * primary.verify + * primary.could_not_add + * + * info is passed on primary.verify and contains the info necessary to + * verify the user with the IdP */ createUser: function(email, onComplete, onFailure) { var self=this; @@ -324,13 +337,24 @@ BrowserID.User = (function() { provisioning({ email: email, url: info.prov - }, function(r) { - onComplete("primary.verified"); - }, function(info) { - // XXX When do we have to redirect off to the authentication page? - // Would a code like this come in on the failure mode? - if(info.code === "MUST_AUTHENTICATE") { - onComplete("primary.verify"); + }, function(keypair, cert) { + persistEmailKeypair(email, "primary", keypair, cert, function() { + User.getAssertion(email, function(assertion) { + if(assertion) { + network.authenticateWithAssertion(email, assertion, function(status) { + var message = status ? "primary.verified" : "primary.could_not_add"; + onComplete(message); + }, onFailure); + } + else { + // XXX perhaps these failure modes should call onFailure instead. + onComplete("primary.could_not_add"); + } + }, onFailure); + }, onFailure); + }, function(error) { + if(error.code === "primaryError" && error.msg === "user is not authenticated as target user") { + onComplete("primary.verify", info); } else { onFailure(info); diff --git a/resources/static/test/index.html b/resources/static/test/index.html index d209296ed3065a4c1e2606a7f3b4cddb1d9a16c8..9a990769264f68e87b75949ca961509d33681b81 100644 --- a/resources/static/test/index.html +++ b/resources/static/test/index.html @@ -65,6 +65,7 @@ <li class="notification emailsent">Email Sent</li> <li class="notification doh">doh</li> <li class="notification" id="congrats">Congratulations!</li> + <li class="notification" id="primary_verify"><span id="primary_email"></span></li> </ul> <ul id="emailList"> diff --git a/resources/static/test/qunit/mocks/provisioning.js b/resources/static/test/qunit/mocks/provisioning.js index 70a8760e627c3de8546e748ee46cde2b4accd39f..b83bde1b4a7f0e6d8a52b445a78042f0626b5ac7 100644 --- a/resources/static/test/qunit/mocks/provisioning.js +++ b/resources/static/test/qunit/mocks/provisioning.js @@ -35,18 +35,45 @@ * * ***** END LICENSE BLOCK ***** */ BrowserID.Mocks.Provisioning = (function() { + + "use strict"; + + var keypair, + // this cert is meaningless, but it has the right format + cert = "eyJhbGciOiJSUzEyOCJ9.eyJpc3MiOiJpc3N1ZXIuY29tIiwiZXhwIjoxMzE2Njk1MzY3NzA3LCJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IlJTIiwibiI6IjU2MDYzMDI4MDcwNDMyOTgyMzIyMDg3NDE4MTc2ODc2NzQ4MDcyMDM1NDgyODk4MzM0ODExMzY4NDA4NTI1NTk2MTk4MjUyNTE5MjY3MTA4MTMyNjA0MTk4MDA0NzkyODQ5MDc3ODY4OTUxOTA2MTcwODEyNTQwNzEzOTgyOTU0NjUzODEwNTM5OTQ5Mzg0NzEyNzczMzkwMjAwNzkxOTQ5NTY1OTAzNDM5NTIxNDI0OTA5NTc2ODMyNDE4ODkwODE5MjA0MzU0NzI5MjE3MjA3MzYwMTA1OTA2MDM5MDIzMjk5NTYxMzc0MDk4OTQyNzg5OTk2NzgwMTAyMDczMDcxNzYwODUyODQxMDY4OTg5ODYwNDAzNDMxNzM3NDgwMTgyNzI1ODUzODk5NzMzNzA2MDY5IiwiZSI6IjY1NTM3In0sInByaW5jaXBhbCI6eyJlbWFpbCI6InRlc3R1c2VyQHRlc3R1c2VyLmNvbSJ9fQ.aVIO470S_DkcaddQgFUXciGwq2F_MTdYOJtVnEYShni7I6mqBwK3fkdWShPEgLFWUSlVUtcy61FkDnq2G-6ikSx1fUZY7iBeSCOKYlh6Kj9v43JX-uhctRSB2pI17g09EUtvmb845EHUJuoowdBLmLa4DSTdZE-h4xUQ9MsY7Ik", + failure, + jwk = require("./jwk"), + status; + function Provisioning(info, onsuccess, onfailure) { - if(Provisioning.failure) onfailure(Provisioning.failure); - else onsuccess(); + if(status === Provisioning.AUTHENTICATED) { + onsuccess(keypair, cert); + } + else onfailure(failure); } - Provisioning.setSuccess = function(status) { - Provisioning.status = status; + Provisioning.setStatus = function(newStatus) { + failure = null; + + if(newStatus === Provisioning.NOT_AUTHENTICATED) { + failure = { + code: "primaryError", + msg: "user is not authenticated as target user" + }; + } + else if(newStatus === Provisioning.AUTHENTICATED) { + keypair = keypair || jwk.KeyPair.generate("DS", 256); + } + + status = newStatus; }; + Provisioning.NOT_AUTHENTICATED = "not_authenticated"; + Provisioning.AUTHENTICATED = "authenticated"; + Provisioning.setFailure = function(status) { - Provisioning.failure = status; - } + failure = status; + }; return Provisioning; }()); diff --git a/resources/static/test/qunit/mocks/xhr.js b/resources/static/test/qunit/mocks/xhr.js index e2f48b980ff484c45934263f4f03b531e89cbc7f..bc309a62750f292feb944fc8f4b55145b52fbb67 100644 --- a/resources/static/test/qunit/mocks/xhr.js +++ b/resources/static/test/qunit/mocks/xhr.js @@ -64,6 +64,10 @@ BrowserID.Mocks.xhr = (function() { "post /wsapi/authenticate_user valid": { success: true }, "post /wsapi/authenticate_user invalid": { success: false }, "post /wsapi/authenticate_user ajaxError": undefined, + "post /wsapi/auth_with_assertion primary": { success: true }, + "post /wsapi/auth_with_assertion valid": { success: true }, + "post /wsapi/auth_with_assertion invalid": { success: false }, + "post /wsapi/auth_with_assertion ajaxError": undefined, "post /wsapi/cert_key valid": random_cert, "post /wsapi/cert_key invalid": undefined, "post /wsapi/cert_key ajaxError": undefined, @@ -118,10 +122,10 @@ 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 unknown_secondary": { type: "secondary", known: false }, "get /wsapi/address_info?email=registered%40testuser.com known_secondary": { type: "secondary", known: true }, - "get /wsapi/address_info?email=unregistered%40testuser.com primary": { type: "primary", auth: "", prov: "" }, + "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=testuser%40testuser.com primary": { type: "primary", auth: "", prov: "" }, + "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 }, diff --git a/resources/static/test/qunit/pages/signup_unit_test.js b/resources/static/test/qunit/pages/signup_unit_test.js index b55022f0ad9df19b5fab4bbe3041618b34997a46..08efadc86917f5688d6953a92a610d8bee239d0f 100644 --- a/resources/static/test/qunit/pages/signup_unit_test.js +++ b/resources/static/test/qunit/pages/signup_unit_test.js @@ -40,14 +40,31 @@ var bid = BrowserID, network = bid.Network, xhr = bid.Mocks.xhr, - testOrigin = "http://browserid.org", testHelpers = bid.TestHelpers, - provisioning = bid.Mocks.Provisioning; + provisioning = bid.Mocks.Provisioning, + win; + + function DocumentMock() { + this.location = document.location; + } + + function WindowMock() { + this.document = new DocumentMock(); + } + WindowMock.prototype = { + open: function(url, name, options) { + this.open_url = url; + } + }; module("pages/signup", { setup: function() { testHelpers.setup(); - bid.signUp(); + + win = new WindowMock(); + bid.signUp({ + window: win + }); }, teardown: function() { testHelpers.teardown(); @@ -133,7 +150,6 @@ asyncTest("signup with primary email address, provisioning failure - expect error screen", function() { xhr.useResult("primary"); - $("#email").val("unregistered@testuser.com"); provisioning.setFailure({ code: "internal", @@ -149,33 +165,38 @@ asyncTest("signup with primary email address, user verified by primary - print success message", function() { xhr.useResult("primary"); - $("#email").val("unregistered@testuser.com"); - - provisioning.setSuccess(true); + provisioning.setStatus(provisioning.AUTHENTICATED); bid.signUp.submit(function(status) { equal(status, true, "primary addition success - true status"); - equal($(".notification:visible").length, 1, "success notification is visible"); + equal($("#congrats:visible").length, 1, "success notification is visible"); start(); }); }); - // XXX what do we expect here? - asyncTest("signup with primary email address, user must verify with primary - ", function() { + asyncTest("signup with primary email address, user must verify with primary", function() { xhr.useResult("primary"); - $("#email").val("unregistered@testuser.com"); - provisioning.setFailure({ - code: "MUST_AUTHENTICATE", - msg: "Wahhooo!!" - }); - bid.signUp.submit(function(status) { + equal($("#primary_verify:visible").length, 1, "success notification is visible"); + equal($("#primary_email").text(), "unregistered@testuser.com", "correct email shown"); equal(status, false, "user must authenticate, some action needed."); start(); }); }); + asyncTest("verifyWithPrimary opens new tab", function() { + xhr.useResult("primary"); + $("#email").val("unregistered@testuser.com"); + + bid.signUp.submit(function(status) { + bid.signUp.verifyWithPrimary(function() { + equal(win.open_url, "https://auth_url?email=unregistered%40testuser.com", "user directed to authentication URL"); + start(); + }); + }); + }); + }()); diff --git a/resources/static/test/qunit/resources/helpers_unit_test.js b/resources/static/test/qunit/resources/helpers_unit_test.js index 66613847707e62012465aa9686671ff3980a5edc..62e6e486a55d6755468d87286f928164ba70962a 100644 --- a/resources/static/test/qunit/resources/helpers_unit_test.js +++ b/resources/static/test/qunit/resources/helpers_unit_test.js @@ -48,7 +48,7 @@ provisioning = bid.Mocks.Provisioning, closeCB, errorCB, - expectedError = testHelpers.expectXHRFailure, + expectedError = testHelpers.expectedXHRFailure, badError = testHelpers.unexpectedXHRFailure; var controllerMock = { @@ -182,7 +182,7 @@ closeCB = expectedClose("primary_user_verified", "email", "unregistered@testuser.com"); xhr.useResult("primary"); - provisioning.setSuccess(true); + provisioning.setStatus(provisioning.AUTHENTICATED); dialogHelpers.createUser.call(controllerMock, "unregistered@testuser.com", function(staged) { equal(staged, true, "user was staged"); @@ -192,11 +192,7 @@ asyncTest("createUser with unknown primary, user must verify with IdP - expect 'primary_verify_user' message", function() { closeCB = expectedClose("primary_verify_user", "email", "unregistered@testuser.com"); - xhr.useResult("primary"); - provisioning.setFailure({ - code: "MUST_AUTHENTICATE" - }); dialogHelpers.createUser.call(controllerMock, "unregistered@testuser.com", function(staged) { equal(staged, true, "user was staged"); diff --git a/resources/static/test/qunit/shared/network_unit_test.js b/resources/static/test/qunit/shared/network_unit_test.js index dc7d44725e217b5756ff8f2265194e7f39f46ab3..b89ec7b5a4d0767552108e9188e78c1a384043f7 100644 --- a/resources/static/test/qunit/shared/network_unit_test.js +++ b/resources/static/test/qunit/shared/network_unit_test.js @@ -40,7 +40,8 @@ var bid = BrowserID, mediator = bid.Mediator, xhr = bid.Mocks.xhr, - testHelpers = bid.TestHelpers; + testHelpers = bid.TestHelpers, + TEST_EMAIL = "testuser@testuser.com"; function notificationCheck(cb) { // Take the original arguments, take off the function. Add any additional @@ -112,7 +113,7 @@ asyncTest("authenticate with valid user", function() { - network.authenticate("testuser@testuser.com", "testuser", function onSuccess(authenticated) { + network.authenticate(TEST_EMAIL, "testuser", function onSuccess(authenticated) { equal(authenticated, true, "valid authentication"); start(); }, function onFailure() { @@ -123,7 +124,7 @@ asyncTest("authenticate with invalid user", function() { xhr.useResult("invalid"); - network.authenticate("testuser@testuser.com", "invalid", function onSuccess(authenticated) { + network.authenticate(TEST_EMAIL, "invalid", function onSuccess(authenticated) { equal(authenticated, false, "invalid authentication"); start(); }, function onFailure() { @@ -133,11 +134,38 @@ }); asyncTest("authenticate with XHR failure, checking whether application is notified", function() { - notificationCheck(network.authenticate, "testuser@testuser.com", "ajaxError"); + notificationCheck(network.authenticate, TEST_EMAIL, "ajaxError"); }); asyncTest("authenticate with XHR failure after context already setup", function() { - failureCheck(network.authenticate, "testuser@testuser.com", "ajaxError"); + failureCheck(network.authenticate, TEST_EMAIL, "ajaxError"); + }); + + asyncTest("authenticateWithAssertion with valid email/assertioni, returns true status", function() { + network.authenticateWithAssertion(TEST_EMAIL, "test_assertion", function(status) { + equal(status, true, "user authenticated, status set to true"); + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("authenticateWithAssertion with invalid email/assertion", function() { + xhr.useResult("invalid"); + + network.authenticateWithAssertion(TEST_EMAIL, "test_assertion", function(status) { + equal(status, false, "user not authenticated, status set to false"); + start(); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("authenticateWithAssertion with XHR error", function() { + xhr.useResult("ajaxError"); + + network.authenticateWithAssertion( + TEST_EMAIL, + "test_assertion", + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); }); @@ -146,10 +174,7 @@ network.checkAuth(function onSuccess(authenticated) { equal(authenticated, true, "we have an authentication"); start(); - }, function onFailure() { - ok(false, "checkAuth failure"); - start(); - }); + }, testHelpers.unexpectedXHRFailure); }); asyncTest("checkAuth with invalid authentication", function() { @@ -159,10 +184,7 @@ network.checkAuth(function onSuccess(authenticated) { equal(authenticated, false, "we are not authenticated"); start(); - }, function onFailure() { - ok(false, "checkAuth failure"); - start(); - }); + }, testHelpers.unexpectedXHRFailure); }); @@ -630,7 +652,7 @@ asyncTest("addressInfo with unknown secondary email", function() { xhr.useResult("unknown_secondary"); - network.addressInfo("testuser@testuser.com", function onComplete(data) { + network.addressInfo(TEST_EMAIL, function onComplete(data) { equal(data.type, "secondary", "type is secondary"); equal(data.known, false, "address is unknown to BrowserID"); start(); @@ -640,7 +662,7 @@ asyncTest("addressInfo with known seconday email", function() { xhr.useResult("known_secondary"); - network.addressInfo("testuser@testuser.com", function onComplete(data) { + network.addressInfo(TEST_EMAIL, function onComplete(data) { equal(data.type, "secondary", "type is secondary"); equal(data.known, true, "address is known to BrowserID"); start(); @@ -650,7 +672,7 @@ asyncTest("addressInfo with primary email", function() { xhr.useResult("primary"); - network.addressInfo("testuser@testuser.com", function onComplete(data) { + network.addressInfo(TEST_EMAIL, function onComplete(data) { equal(data.type, "primary", "type is primary"); ok("auth" in data, "auth field exists"); ok("prov" in data, "prov field exists"); @@ -660,7 +682,7 @@ asyncTest("addressInfo with XHR error", function() { xhr.useResult("ajaxError"); - failureCheck(network.addressInfo, "testuser@testuser.com"); + failureCheck(network.addressInfo, TEST_EMAIL); }); asyncTest("changePassword happy case, expect complete callback with true status", function() { @@ -696,4 +718,5 @@ start(); }); }); + }()); diff --git a/resources/static/test/qunit/shared/user_unit_test.js b/resources/static/test/qunit/shared/user_unit_test.js index 997c7239960f445f3d66f4f7789125de7a629f9b..eb0e5c3c3726c9bb79aa169238d5a9af7f4eb682 100644 --- a/resources/static/test/qunit/shared/user_unit_test.js +++ b/resources/static/test/qunit/shared/user_unit_test.js @@ -155,35 +155,32 @@ var jwcert = require("./jwcert"); equal(0, count, "after clearing, there are no identities"); }); - /* - asyncTest("createUser", function() { - lib.createUser("testuser@testuser.com", function(status) { + asyncTest("createSecondaryUser", function() { + lib.createSecondaryUser("testuser@testuser.com", function(status) { ok(status, "user created"); start(); - }, failure("createUser failure")); + }, testHelpers.unexpectedXHRFailure); }); - asyncTest("createUser with user creation refused", function() { + asyncTest("createSecondaryUser with user creation refused", function() { xhr.useResult("throttle"); - lib.createUser("testuser@testuser.com", function(status) { + lib.createSecondaryUser("testuser@testuser.com", function(status) { equal(status, false, "user creation refused"); start(); - }, failure("createUser failure")); + }, testHelpers.unexpectedXHRFailure); }); - asyncTest("createUser with XHR failure", function() { + asyncTest("createSecondaryUser with XHR failure", function() { xhr.useResult("ajaxError"); - lib.createUser("testuser@testuser.com", function(status) { - ok(false, "xhr failure should never succeed"); - start(); - }, function() { - ok(true, "xhr failure should always be a failure"); - start(); - }); + lib.createSecondaryUser( + "testuser@testuser.com", + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); }); -*/ + asyncTest("createUser with unknown secondary happy case - expect 'secondary.verify'", function() { xhr.useResult("unknown_secondary"); @@ -207,28 +204,26 @@ var jwcert = require("./jwcert"); lib.createUser("unregistered@testuser.com", testHelpers.unexpectedSuccess, - testHelpers.expectXHRFailure + testHelpers.expectedXHRFailure ); }); asyncTest("createUser with primary, user verified with primary - expect 'primary.verified'", function() { xhr.useResult("primary"); - provisioning.setSuccess(true); + provisioning.setStatus(provisioning.AUTHENTICATED); lib.createUser("unregistered@testuser.com", function(status) { equal(status, "primary.verified", "primary user is already verified, correct status"); - start(); + network.checkAuth(function(authenticated) { + equal(authenticated, true, "after provisioning user, user should be automatically authenticated to BrowserID"); + start(); + }); }, testHelpers.unexpectedXHRFailure); }); asyncTest("createUser with primary, user must authenticate with primary - expect 'primary.verify'", function() { xhr.useResult("primary"); - provisioning.setFailure({ - code: "MUST_AUTHENTICATE", - msg: "Wahhooo!!" - }); - lib.createUser("unregistered@testuser.com", function(status) { equal(status, "primary.verify", "primary must verify with primary, correct status"); start(); @@ -244,7 +239,7 @@ var jwcert = require("./jwcert"); lib.createUser("unregistered@testuser.com", testHelpers.unexpectedSuccess, - testHelpers.expectXHRFailure + testHelpers.expectedXHRFailure ); }); @@ -339,9 +334,7 @@ var jwcert = require("./jwcert"); xhr.useResult("invalid"); lib.verifyUser("token", "password", function onSuccess(info) { - equal(info.valid, false, "bad token calls onSuccess with a false validity"); - start(); }, failure("verifyUser failure")); }); diff --git a/resources/static/test/qunit/testHelpers/helpers.js b/resources/static/test/qunit/testHelpers/helpers.js index 61890240ceafa51f74c34371e1cf8bbd859d0c04..4e27eaae5451c9f02da19802a361156c53c7733b 100644 --- a/resources/static/test/qunit/testHelpers/helpers.js +++ b/resources/static/test/qunit/testHelpers/helpers.js @@ -55,8 +55,7 @@ screens.wait.hide(); screens.error.hide(); tooltip.reset(); - provisioning.setSuccess(false); - provisioning.setFailure(false); + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); user.init({ provisioning: provisioning }); @@ -74,8 +73,7 @@ screens.wait.hide(); screens.error.hide(); tooltip.reset(); - provisioning.setSuccess(false); - provisioning.setFailure(false); + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); user.reset(); }, @@ -92,7 +90,7 @@ start(); }, - expectXHRFailure: function() { + expectedXHRFailure: function() { ok(true, "expected XHR failure"); start(); }, diff --git a/resources/views/signup.ejs b/resources/views/signup.ejs index 738b16335b3f6128d9bd1f2545403b624ad03c49..9f7b2b9ec17fdf3b55a0bf174c3a70784c954596 100644 --- a/resources/views/signup.ejs +++ b/resources/views/signup.ejs @@ -36,6 +36,17 @@ </p> </p> </li> + + <li class="notification" id="primary_verify"> + <p> + To verify that you own <strong id="primary_email">address</strong>, you must + sign in with your provider. A new window will be opened. + </p> + + <p> + <button id="verifyWithPrimary">Verify</button> + </p> + </li> </ul> <ul class="inputs forminputs">