diff --git a/example/rp/index.html b/example/rp/index.html index 5f0b24bdcae337ead4254e40073d00b9b1c69995..19ddc0533b343d33ef00de82318cc0f11ddf8655 100644 --- a/example/rp/index.html +++ b/example/rp/index.html @@ -73,6 +73,10 @@ pre { <input type="checkbox" id="siteLogo"> <label for="siteLogo">Supply Site Logo</label><br /> </li> + </li><li> + <input type="checkbox" id="returnTo"> + <label for="returnTo">Supply returnTo</label><br /> + </li> </li><li> <input type="text" id="requiredEmail" width="80"> <label for="requiredEmail">Require a specific email</label><br /> @@ -193,6 +197,7 @@ $(document).ready(function() { termsOfService: $('#termsOfService').attr('checked') ? "/TOS.html" : undefined, siteName: $('#siteName').attr('checked') ? "Persona Test Relying Party" : undefined, siteLogo: $('#siteLogo').attr('checked') ? "/i/logo.png" : undefined, + returnTo: $('#returnTo').attr('checked') ? "/postVerificationReturn.html" : undefined, requiredEmail: requiredEmail, oncancel: function() { loggit("oncancel"); diff --git a/example/rp/postVerificationReturn.html b/example/rp/postVerificationReturn.html new file mode 100644 index 0000000000000000000000000000000000000000..518c7abbcd7e3073215336abff5adfb523c97815 --- /dev/null +++ b/example/rp/postVerificationReturn.html @@ -0,0 +1,142 @@ +<!DOCTYPE html> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<html> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="initial-scale=1.0; maximum-scale=1.0; width=device-width;"> +<title> +Persona Relying Party Post Verification Return +</title> +<style type="text/css"> + +body { margin: auto; font: 13px/1.5 Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif; } +a:link, a:visited { font-style: italic; text-decoration: none; color: #008; } +a:hover { border-bottom: 2px solid black ; } +.title { font-size: 2em; font-weight: bold; text-align: center; margin: 1.5em auto 1.5em auto; } +.intro { font-size: 1.2em; } +.specify, .session { font-size: 1.1em; padding-top: 2em; } +body div { width: 600px; margin: auto; } + +pre { + font-family: 'lucida console', monaco, 'andale mono', 'bitstream vera sans mono', consolas, monospace; + border: 3px solid #666; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + padding: .5em; + margin: .5em; + color: #ccc; + background-color: #333; +/* white-space: pre;*/ + font-size: .9em; + word-wrap: break-word; +} + +.specify ul { padding-left: 0px; } +.specify li { list-style: none; } + +@media screen and (max-width: 640px) { + .intro, .output, .step { + width: 90%; + } +} + +</style> +</head> +<body> +<div class="title"> + Persona Relying Party Post Verification Return +</div> + +<div class="intro"> + This is part of a RP for testing, it is the returnTo for post-verification redirect. + <p> + <a href="/">Return to RP Test Page</a> + </p> +</div> + +<div class="loginEvents"> + <h2>logins</h2> + <pre> ... </pre> +</div> + + +<div class="readiness"> + <h2>readiness</h2> + <pre> ... </pre> +</div> + +</body> + +<script src="jquery-min.js"></script> +<script src="https://browserid.org/include.js"></script> +<script> + +try { + var storage = localStorage; +} +catch(e) { + // Fx with cookies disabled with blow up when trying to access localStorage. + storage = {}; +} + + +function loggit() { + try { + console.log.apply(console, arguments); + } catch(e) {} +} + +var serial = 1; + +// a function to check an assertion against the server +function checkAssertion(assertion) { + $.ajax({ + url: "/process_assertion", + type: "post", + dataType: "json", + data: { + assertion: assertion, + audience: window.location.protocol + "//" + window.location.host + }, + success: function(data, textStatus, jqXHR) { + var old = $(".loginEvents > pre").text() + "\n"; + $(".loginEvents > pre").text(old + JSON.stringify(data, null, 4)); + }, + error: function(jqXHR, textStatus, errorThrown) { + var resp = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : errorThrown; + $(".loginEvents > pre").text(resp); + } + }); +}; + +navigator.id.watch({ + loggedInEmail: (storage.loggedInUser === 'null') ? null : storage.loggedInUser, + onready: function () { + loggit("onready"); + var txt = serial++ + ' navigator.id ready at ' + (new Date).toString(); + $(".readiness > pre").text(txt); + + }, + onlogin: function (assertion) { + loggit("onlogin"); + var txt = serial++ + ' got assertion at ' + (new Date).toString(); + $(".loginEvents > pre").text(txt); + + checkAssertion(assertion); + + $(".specify button.assertion").removeAttr('disabled'); + }, + onlogout: function () { + loggit("onlogout"); + var txt = serial++ + ' logout callback invoked at ' + (new Date).toString(); + $(".logoutEvents > pre").text(txt); + } +}); + +</script> + +</html> diff --git a/lib/static_resources.js b/lib/static_resources.js index d86bfd740f699c1873d26f50b092a0b80ca5fac3..3e0cf15d6f00a382af7dcad03a94d47d8fcbdf09 100644 --- a/lib/static_resources.js +++ b/lib/static_resources.js @@ -22,6 +22,7 @@ var common_js = [ '/lib/bidbundle.js', '/lib/ejs.js', '/lib/micrajax.js', + '/lib/urlparse.js', '/shared/javascript-extensions.js', '/i18n/:locale/client.json', '/shared/gettext.js', @@ -75,8 +76,6 @@ var dialog_min_js = '/production/:locale/dialog.js'; var dialog_js = und.flatten([ common_js, [ - '/lib/urlparse.js', - '/shared/command.js', '/shared/history.js', '/shared/state_machine.js', diff --git a/resources/static/css/style.css b/resources/static/css/style.css index 4cecf7645473b802c065ad18a1a9f52e81cf91b8..35bfa524ab66efd353488ddd596061022bb3fc77 100644 --- a/resources/static/css/style.css +++ b/resources/static/css/style.css @@ -557,12 +557,16 @@ button.delete:active { } #congrats .siteinfo { - margin-top: 10px; + margin-top: 10px; } #congrats .website { - display: block; - text-align: center; + display: block; + text-align: center; +} + +#redirection { + text-align: center; } diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js index 8d9725d5120c29f38b095d7a1cb747e0706eeb5e..36e66dca60e3ccc6310246344cb029f22d637376 100644 --- a/resources/static/dialog/controllers/dialog.js +++ b/resources/static/dialog/controllers/dialog.js @@ -184,6 +184,14 @@ BrowserID.Modules.Dialog = (function() { params.siteName = _.escape(paramsFromRP.siteName); } + // returnTo is used for post verification redirection. Redirect back + // to the path specified by the RP. + if (paramsFromRP.returnTo) { + var returnTo = fixupAbsolutePath(origin_url, paramsFromRP.returnTo); + user.setReturnTo(returnTo); + } + + if (hash.indexOf("#CREATE_EMAIL=") === 0) { var email = hash.replace(/#CREATE_EMAIL=/, ""); if (!bid.verifyEmail(email)) diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js index fafc25ff8844ad40a38f15922161f90d34222c9e..e3b60e5ff29ed5e25d3be57dd2cbeebc1b41328a 100644 --- a/resources/static/include_js/include.js +++ b/resources/static/include_js/include.js @@ -1069,6 +1069,9 @@ // don't do duplicative work if (commChan) commChan.notify({ method: 'dialog_running' }); + // returnTo is used for post-email-verification redirect + if (!options.returnTo) options.returnTo = document.location.pathname; + w = WinChan.open({ url: ipServer + '/sign_in', relay_url: ipServer + '/relay', diff --git a/resources/static/pages/verify_secondary_address.js b/resources/static/pages/verify_secondary_address.js index e94297adf8fce4786b99f8ebcbfd80ac0985efa6..df4561ec37b6707e3401afafd4f7c07225ba283a 100644 --- a/resources/static/pages/verify_secondary_address.js +++ b/resources/static/pages/verify_secondary_address.js @@ -9,6 +9,7 @@ BrowserID.verifySecondaryAddress = (function() { var ANIMATION_TIME=250, bid = BrowserID, user = bid.User, + storage = bid.Storage, errors = bid.Errors, pageHelpers = bid.PageHelpers, dom = bid.DOM, @@ -20,7 +21,13 @@ BrowserID.verifySecondaryAddress = (function() { sc, needsPassword, mustAuth, - verifyFunction; + verifyFunction, + doc = document, + REDIRECT_SECONDS = 5, + secondsRemaining = REDIRECT_SECONDS, + email, + redirectTo, + redirectTimeout; // set in config if available, use REDIRECT_SECONDS otw. function showError(el, oncomplete) { dom.hide(".hint,#signUpForm"); @@ -30,26 +37,56 @@ BrowserID.verifySecondaryAddress = (function() { function showRegistrationInfo(info) { dom.setInner("#email", info.email); - if (info.origin) { - dom.setInner(".website", info.origin); + if (info.returnTo) { + dom.setInner(".website", info.returnTo); + updateRedirectTimeout(); dom.show(".siteinfo"); } } + function updateRedirectTimeout() { + if (secondsRemaining > 0) { + dom.setInner("#redirectTimeout", secondsRemaining); + + secondsRemaining--; + setTimeout(updateRedirectTimeout, 1000); + } + } + function submit(oncomplete) { var pass = dom.getInner("#password") || undefined, vpass = dom.getInner("#vpassword") || undefined, - valid = (!needsPassword || + inputValid = (!needsPassword || validation.passwordAndValidationPassword(pass, vpass)) && (!mustAuth || validation.password(pass)); - if (valid) { + if (inputValid) { user[verifyFunction](token, pass, function(info) { dom.addClass("body", "complete"); - var selector = info.valid ? "#congrats" : "#cannotcomplete"; - pageHelpers.replaceFormWithNotice(selector, complete.curry(oncomplete, info.valid)); + var verified = info.valid, + selector = verified ? "#congrats" : "#cannotcomplete"; + + pageHelpers.replaceFormWithNotice(selector, function() { + if (redirectTo && verified) { + + // set the loggedIn status for the site. This allows us to get + // a silent assertion without relying on the dialog to set the + // loggedIn status for the domain. This is useful when the user + // closes the dialog OR if redirection happens before the dialog + // has had a chance to finish its business. + storage.setLoggedIn(URLParse(redirectTo).originOnly(), email); + + setTimeout(function() { + doc.location.href = redirectTo; + complete(oncomplete, verified); + }, redirectTimeout); + } + else { + complete(oncomplete, verified); + } + }); }, function(info) { if (info.network && info.network.status === 401) { tooltip.showTooltip("#cannot_authenticate"); @@ -67,7 +104,9 @@ BrowserID.verifySecondaryAddress = (function() { function startVerification(oncomplete) { user.tokenInfo(token, function(info) { - if(info) { + if (info) { + redirectTo = info.returnTo; + email = info.email; showRegistrationInfo(info); needsPassword = info.needs_password; @@ -105,6 +144,12 @@ BrowserID.verifySecondaryAddress = (function() { token = options.token; verifyFunction = options.verifyFunction; + doc = options.document || document; + + redirectTimeout = options.redirectTimeout; + if (typeof redirectTimeout === "undefined") { + redirectTimeout = REDIRECT_SECONDS * 1000; + } startVerification(options.ready); diff --git a/resources/static/shared/storage.js b/resources/static/shared/storage.js index 1fa52161a9da6e0b65c7c0bad02e79dfe9682800..7b4e768ac2d2ab3d37e36365fed4b919fd3c039b 100644 --- a/resources/static/shared/storage.js +++ b/resources/static/shared/storage.js @@ -50,7 +50,6 @@ BrowserID.Storage = (function() { function clear() { storage.removeItem("emails"); storage.removeItem("tempKeypair"); - storage.removeItem("stagedOnBehalfOf"); storage.removeItem("siteInfo"); storage.removeItem("managePage"); } @@ -147,29 +146,50 @@ BrowserID.Storage = (function() { } } - function setStagedOnBehalfOf(origin) { - storage.stagedOnBehalfOf = JSON.stringify({ + function setReturnTo(returnToURL) { + storage.returnTo = JSON.stringify({ at: new Date().toString(), - origin: origin + url: returnToURL }); } - function getStagedOnBehalfOf() { - var origin; + function getReturnTo() { + var returnToURL; + // XXX - The transitional code is to make sure any emails that were staged using + // the old setStagedOnBehalfOf still work with the new API. This should be + // able to be removed by mid-July 2012. try { - var staged = JSON.parse(storage.stagedOnBehalfOf); + // BEGIN TRANSITIONAL CODE + if (storage.returnTo) { + // END TRANSITIONAL CODE + var staged = JSON.parse(storage.returnTo); + + if (staged) { + if ((new Date() - new Date(staged.at)) > (5 * 60 * 1000)) throw "stale"; + if (typeof(staged.url) !== 'string') throw "malformed"; + returnToURL = staged.url; + } + // BEGIN TRANSITIONAL CODE + } + else if(storage.stagedOnBehalfOf) { + var staged = JSON.parse(storage.stagedOnBehalfOf); - if (staged) { - if ((new Date() - new Date(staged.at)) > (5 * 60 * 1000)) throw "stale"; - if (typeof(staged.origin) !== 'string') throw "malformed"; - origin = staged.origin; + if (staged) { + if ((new Date() - new Date(staged.at)) > (5 * 60 * 1000)) throw "stale"; + if (typeof(staged.origin) !== 'string') throw "malformed"; + returnToURL = staged.origin; + } } + // END TRANSITIONAL CODE } catch (x) { + storage.removeItem("returnTo"); + // BEGIN TRANSITIONAL CODE storage.removeItem("stagedOnBehalfOf"); + // END TRANSITIONAL CODE } - return origin; + return returnToURL; } function siteSet(site, key, value) { @@ -570,7 +590,7 @@ BrowserID.Storage = (function() { clear: clear, storeTemporaryKeypair: storeTemporaryKeypair, retrieveTemporaryKeypair: retrieveTemporaryKeypair, - setStagedOnBehalfOf: setStagedOnBehalfOf, - getStagedOnBehalfOf: getStagedOnBehalfOf + setReturnTo: setReturnTo, + getReturnTo: getReturnTo }; }()); diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js index d966fe5d0f57c3a03421602d4df365f0ee417c85..ab493ae9f4870da8bc26472ff8a08f69b53b4095 100644 --- a/resources/static/shared/user.js +++ b/resources/static/shared/user.js @@ -113,7 +113,7 @@ BrowserID.User = (function() { // As soon as the registration comes back as complete, we should // ensure that the stagedOnBehalfOf is cleared so there is no stale // data. - storage.setStagedOnBehalfOf(""); + storage.setReturnTo(""); // To avoid too many address_info requests, returns from each // address_info request are cached. If the user is doing @@ -273,6 +273,14 @@ BrowserID.User = (function() { return origin.replace(/^.*:\/\//, "").replace(/:\d*$/, ""); }, + setReturnTo: function(returnTo) { + this.returnTo = returnTo; + }, + + getReturnTo: function() { + return this.returnTo; + }, + /** * Create a user account - this creates an user account that must be verified. * @method createSecondaryUser @@ -282,15 +290,13 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on error. */ createSecondaryUser: function(email, password, onComplete, onFailure) { - // Used on the main site when the user verifies - we try to show them - // what URL they came from. - - // XXX - this will have to be updated to either store both the hostname - // and the exact URL of the RP or just the URL of the RP and the origin - // is extracted from that. - storage.setStagedOnBehalfOf(User.getHostname()); - - network.createUser(email, password, origin, onComplete, onFailure); + 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. + var site = User.getReturnTo(); + if (created && site) storage.setReturnTo(site); + complete(onComplete, created); + }, onFailure); }, /** @@ -481,10 +487,10 @@ BrowserID.User = (function() { tokenInfo: function(token, onComplete, onFailure) { network.emailForVerificationToken(token, function (info) { if(info) { - info = _.extend(info, { origin: storage.getStagedOnBehalfOf() }); + info = _.extend(info, { returnTo: storage.getReturnTo() }); } - onComplete && onComplete(info); + complete(onComplete, info); }, onFailure); }, @@ -507,8 +513,8 @@ BrowserID.User = (function() { var result = invalidInfo; if(valid) { - result = _.extend({ valid: valid, origin: storage.getStagedOnBehalfOf() }, info); - storage.setStagedOnBehalfOf(""); + result = _.extend({ valid: valid, returnTo: storage.getReturnTo() }, info); + storage.setReturnTo(""); } complete(onComplete, result); @@ -571,13 +577,16 @@ BrowserID.User = (function() { User.isEmailRegistered(email, function(registered) { if (registered) { network.requestPasswordReset(email, password, origin, function(reset) { - var status = { - success: 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); - if (onComplete) onComplete(status); + complete(onComplete, status); }, onFailure); } else if (onComplete) { @@ -806,10 +815,13 @@ BrowserID.User = (function() { */ addEmail: function(email, password, onComplete, onFailure) { network.addSecondaryEmail(email, password, origin, function(added) { - if (added) storage.setStagedOnBehalfOf(User.getHostname()); + // 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 returnTo = User.getReturnTo(); + if (added && returnTo) storage.setReturnTo(returnTo); // we no longer send the keypair, since we will certify it later. - if (onComplete) onComplete(added); + complete(onComplete, added); }, onFailure); }, @@ -871,8 +883,8 @@ BrowserID.User = (function() { var result = invalidInfo; if(valid) { - result = _.extend({ valid: valid, origin: storage.getStagedOnBehalfOf() }, info); - storage.setStagedOnBehalfOf(""); + result = _.extend({ valid: valid, returnTo: storage.getReturnTo() }, info); + storage.setReturnTo(""); } complete(onComplete, result); diff --git a/resources/static/test/cases/controllers/dialog.js b/resources/static/test/cases/controllers/dialog.js index ed8aba8d28f0a540b76b9d7790ee3d1af438e1e1..aedd54d97475b720ed0affd26f640161948dfd04 100644 --- a/resources/static/test/cases/controllers/dialog.js +++ b/resources/static/test/cases/controllers/dialog.js @@ -15,6 +15,7 @@ testErrorNotVisible = testHelpers.testErrorNotVisible, screens = bid.Screens, xhr = bid.Mocks.xhr, + user = bid.User, HTTP_TEST_DOMAIN = "http://testdomain.org", HTTPS_TEST_DOMAIN = "https://testdomain.org", TESTEMAIL = "testuser@testuser.com", @@ -572,8 +573,6 @@ siteLogo: siteLogo }); - start(); - testHelpers.testObjectValuesEqual(startInfo, { siteLogo: encodeURI(HTTP_TEST_DOMAIN + siteLogo) }); @@ -582,10 +581,42 @@ start(); } }); - }); + asyncTest("get with returnTo with https - not allowed", function() { + createController({ + ready: function() { + var URL = HTTP_TEST_DOMAIN + "/path"; + + mediator.subscribe("start", function(msg, info) { + ok(false, "unexpected start"); + }); + + var retval = controller.get(HTTP_TEST_DOMAIN, { + returnTo: URL + }); + equal(retval, "must be an absolute path: (" + URL + ")", "expected error"); + testErrorVisible(); + start(); + } + }); + }); + + asyncTest("get with absolute path returnTo - allowed", function() { + createController({ + ready: function() { + mediator.subscribe("start", function(msg, info) { + equal(user.getReturnTo(), HTTPS_TEST_DOMAIN + "/path", "returnTo correctly set"); + start(); + }); + + var retval = controller.get(HTTPS_TEST_DOMAIN, { + returnTo: "/path" + }); + } + }); + }); }()); diff --git a/resources/static/test/cases/pages/verify_secondary_address.js b/resources/static/test/cases/pages/verify_secondary_address.js index 630667b19f554b2c634d860f3fb2fbc20ec1f687..0d74afe90af7dc39c853bc3f8860a11adfbac517 100644 --- a/resources/static/test/cases/pages/verify_secondary_address.js +++ b/resources/static/test/cases/pages/verify_secondary_address.js @@ -9,6 +9,7 @@ var bid = BrowserID, storage = bid.Storage, xhr = bid.Mocks.xhr, + WindowMock = bid.Mocks.WindowMock, dom = bid.DOM, testHelpers = bid.TestHelpers, testHasClass = testHelpers.testHasClass, @@ -17,7 +18,8 @@ config = { token: "token", verifyFunction: "verifyEmail" - }; + }, + doc; module("pages/verify_secondary_address", { setup: function() { @@ -33,6 +35,8 @@ function createController(options, callback) { controller = BrowserID.verifySecondaryAddress.create(); options = options || {}; + options.document = doc = new WindowMock().document; + options.redirectTimeout = 0; options.ready = callback; controller.start(options); } @@ -67,13 +71,16 @@ }); asyncTest("no password: start with good token and site", function() { - storage.setStagedOnBehalfOf("persona.org"); + var returnTo = "https://test.domain/path"; + storage.setReturnTo(returnTo); createController(config, function() { testEmail(); ok($(".siteinfo").is(":visible"), "siteinfo is visible when we say what it is"); - equal($(".website:nth(0)").text(), "persona.org", "origin is updated"); + equal($(".website:nth(0)").text(), returnTo, "website is updated"); testHasClass("body", "complete"); + equal(doc.location.href, returnTo, "redirection occurred to correct URL"); + equal(storage.getLoggedIn("https://test.domain"), "testuser@testuser.com", "logged in status set"); start(); }); }); diff --git a/resources/static/test/cases/shared/storage.js b/resources/static/test/cases/shared/storage.js index c9406c84a21ef077b4a150f956a3666a0eccc505..5a7048c00799dfa54ebd351407cf45199264aec9 100644 --- a/resources/static/test/cases/shared/storage.js +++ b/resources/static/test/cases/shared/storage.js @@ -164,12 +164,9 @@ // XXX needs a test }); - test("setStagedOnBehalfOf", function() { - // XXX needs a test - }); - - test("getStagedOnBehalfOf", function() { - // XXX needs a test + test("setReturnTo", function() { + storage.setReturnTo("http://some.domain/path"); + equal(storage.getReturnTo(), "http://some.domain/path", "setReturnTo/getReturnTo working as expected"); }); test("signInEmail.set/.get/.remove - set, get, and remove the signInEmail", function() { diff --git a/resources/static/test/cases/shared/user.js b/resources/static/test/cases/shared/user.js index 155883d5e53e4b320d3a3fcfc67a899e084b5248..a252cd357f1f73bc34d6c3bfed4755514813f2db 100644 --- a/resources/static/test/cases/shared/user.js +++ b/resources/static/test/cases/shared/user.js @@ -85,6 +85,12 @@ var jwcrypto = require("./lib/jwcrypto"); equal(hostname, "persona.org", "getHostname returns only the hostname"); }); + test("setReturnTo, getReturnTo", function() { + var returnTo = "http://samplerp.org"; + lib.setReturnTo(returnTo); + equal(lib.getReturnTo(), returnTo, "get/setReturnTo work as expected"); + }); + test("getStoredEmailKeypairs without key - return all identities", function() { var identities = lib.getStoredEmailKeypairs(); equal("object", typeof identities, "object returned"); @@ -292,27 +298,27 @@ var jwcrypto = require("./lib/jwcrypto"); }); asyncTest("waitForUserValidation with `complete` response", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); xhr.useResult("complete"); lib.waitForUserValidation("registered@testuser.com", function(status) { equal(status, "complete", "complete response expected"); - ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); + ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes"); start(); }, testHelpers.unexpectedXHRFailure); }); asyncTest("waitForUserValidation with `mustAuth` response", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); xhr.useResult("mustAuth"); lib.waitForUserValidation("registered@testuser.com", function(status) { equal(status, "mustAuth", "mustAuth response expected"); - ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); + ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes"); start(); }, testHelpers.unexpectedXHRFailure); }); @@ -320,12 +326,12 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("waitForUserValidation with `noRegistration` response", function() { xhr.useResult("noRegistration"); - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); lib.waitForUserValidation( "registered@testuser.com", testHelpers.unexpectedSuccess, function(status) { - ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared for noRegistration response"); + ok(storage.getReturnTo(), "staged on behalf of is not cleared for noRegistration response"); ok(status, "noRegistration", "noRegistration response causes failure"); start(); } @@ -334,12 +340,12 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("waitForUserValidation with XHR failure", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); lib.waitForUserValidation( "registered@testuser.com", testHelpers.unexpectedSuccess, function() { - ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared on XHR failure"); + ok(storage.getReturnTo(), "staged on behalf of is not cleared on XHR failure"); ok(true, "xhr failure should always be a failure"); start(); } @@ -349,7 +355,7 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("cancelUserValidation: ~1 second", function() { xhr.useResult("pending"); - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); // yes, we are neither expected succes nor failure because we are // cancelling the wait. lib.waitForUserValidation( @@ -360,25 +366,25 @@ var jwcrypto = require("./lib/jwcrypto"); setTimeout(function() { lib.cancelUserValidation(); - ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared when validation cancelled"); + ok(storage.getReturnTo(), "staged on behalf of is not cleared when validation cancelled"); start(); }, 500); }); - asyncTest("tokenInfo with a good token and origin info, expect origin in results", function() { - storage.setStagedOnBehalfOf(testOrigin); + asyncTest("tokenInfo with a good token and returnTo info, expect returnTo in results", function() { + storage.setReturnTo(testOrigin); lib.tokenInfo("token", function(info) { equal(info.email, TEST_EMAIL, "correct email"); - equal(info.origin, testOrigin, "correct origin"); + equal(info.returnTo, testOrigin, "correct returnTo"); start(); }, testHelpers.unexpectedXHRFailure); }); - asyncTest("tokenInfo with a bad token without site info, no site in results", function() { + asyncTest("tokenInfo with a bad token without returnTo info, no returnTo in results", function() { lib.tokenInfo("token", function(info) { equal(info.email, TEST_EMAIL, "correct email"); - equal(typeof info.origin, "undefined", "origin is undefined"); + equal(typeof info.returnTo, "undefined", "returnTo is undefined"); start(); }, testHelpers.unexpectedXHRFailure); }); @@ -388,14 +394,14 @@ var jwcrypto = require("./lib/jwcrypto"); }); asyncTest("verifyUser with a good token", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); lib.verifyUser("token", "password", function onSuccess(info) { ok(info.valid, "token was valid"); equal(info.email, TEST_EMAIL, "email part of info"); - equal(info.origin, testOrigin, "origin in info"); - equal(storage.getStagedOnBehalfOf(), "", "initiating origin was removed"); + equal(info.returnTo, testOrigin, "returnTo in info"); + equal(storage.getReturnTo(), "", "initiating origin was removed"); start(); }, testHelpers.unexpectedXHRFailure); @@ -461,8 +467,13 @@ var jwcrypto = require("./lib/jwcrypto"); }); asyncTest("requestPasswordReset with known email - true status", function() { + var returnTo = "http://samplerp.org"; + lib.setReturnTo(returnTo); + lib.requestPasswordReset("registered@testuser.com", "password", function(status) { equal(status.success, true, "password reset for known user"); + equal(storage.getReturnTo(), returnTo, "RP URL is stored for verification"); + start(); }, testHelpers.unexpectedXHRFailure); }); @@ -642,13 +653,16 @@ var jwcrypto = require("./lib/jwcrypto"); }); asyncTest("addEmail", function() { + var returnTo = "http://samplerp.org"; + lib.setReturnTo(returnTo); + lib.addEmail("testemail@testemail.com", "password", function(added) { ok(added, "user was added"); var identities = lib.getStoredEmailKeypairs(); - equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation."); + equal("testemail@testemail.com" in identities, false, "new email is not added until confirmation."); - equal(storage.getStagedOnBehalfOf(), lib.getHostname(), "initiatingOrigin is stored"); + equal(storage.getReturnTo(), returnTo, "RP URL is stored for verification"); start(); }, testHelpers.unexpectedXHRFailure); @@ -663,7 +677,7 @@ var jwcrypto = require("./lib/jwcrypto"); var identities = lib.getStoredEmailKeypairs(); equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation."); - equal(typeof storage.getStagedOnBehalfOf(), "undefined", "initiatingOrigin is not stored"); + equal(typeof storage.getReturnTo(), "undefined", "initiatingOrigin is not stored"); start(); }, testHelpers.unexpectedXHRFailure); @@ -675,36 +689,36 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("waitForEmailValidation `complete` response", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); xhr.useResult("complete"); lib.waitForEmailValidation("registered@testuser.com", function(status) { - ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); + ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes"); equal(status, "complete", "complete response expected"); start(); }, testHelpers.unexpectedXHRFailure); }); asyncTest("waitForEmailValidation `mustAuth` response", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); xhr.useResult("mustAuth"); lib.waitForEmailValidation("registered@testuser.com", function(status) { - ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); + ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes"); equal(status, "mustAuth", "mustAuth response expected"); start(); }, testHelpers.unexpectedXHRFailure); }); asyncTest("waitForEmailValidation with `noRegistration` response", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); xhr.useResult("noRegistration"); lib.waitForEmailValidation( "registered@testuser.com", testHelpers.unexpectedSuccess, function(status) { - ok(storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); + ok(storage.getReturnTo(), "staged on behalf of is cleared when validation completes"); ok(status, "noRegistration", "noRegistration response causes failure"); start(); }); @@ -712,7 +726,7 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("waitForEmailValidation XHR failure", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); xhr.useResult("ajaxError"); lib.waitForEmailValidation( @@ -726,7 +740,7 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("cancelEmailValidation: ~1 second", function() { xhr.useResult("pending"); - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); lib.waitForEmailValidation( "registered@testuser.com", testHelpers.unexpectedSuccess, @@ -735,19 +749,19 @@ var jwcrypto = require("./lib/jwcrypto"); setTimeout(function() { lib.cancelUserValidation(); - ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared when validation cancelled"); + ok(storage.getReturnTo(), "staged on behalf of is not cleared when validation cancelled"); start(); }, 500); }); - asyncTest("verifyEmail with a good token - callback with email, origin, valid", function() { - storage.setStagedOnBehalfOf(testOrigin); + asyncTest("verifyEmail with a good token - callback with email, returnTo, valid", function() { + storage.setReturnTo(testOrigin); lib.verifyEmail("token", "password", function onSuccess(info) { ok(info.valid, "token was valid"); equal(info.email, TEST_EMAIL, "email part of info"); - equal(info.origin, testOrigin, "origin in info"); - equal(storage.getStagedOnBehalfOf(), "", "initiating origin was removed"); + equal(info.returnTo, testOrigin, "returnTo in info"); + equal(storage.getReturnTo(), "", "initiating returnTo was removed"); start(); }, testHelpers.unexpectedXHRFailure); @@ -1199,7 +1213,7 @@ var jwcrypto = require("./lib/jwcrypto"); asyncTest("shouldAskIfUsersComputer with user who has not been asked and has verified email in this dialog session - call onSuccess with false", function() { lib.authenticate(TEST_EMAIL, "password", function() { - storage.setStagedOnBehalfOf(testOrigin); + storage.setReturnTo(testOrigin); xhr.useResult("complete"); lib.waitForEmailValidation(TEST_EMAIL, function() { diff --git a/resources/views/add_email_address.ejs b/resources/views/add_email_address.ejs index 22d36ab73ce9ab150a89ad7827c32805f2c2efa5..9a4128aeee67fac5702ae45de569cd952d41fe4a 100644 --- a/resources/views/add_email_address.ejs +++ b/resources/views/add_email_address.ejs @@ -60,11 +60,15 @@ <div id="congrats"> <p> - <%- gettext('<strong class="email">Your address</strong> has been verified!') %> + <%- gettext('<strong class="email">Your address</strong> has been verified!') %> + </p> + + <p class="siteinfo"> + <%- format(gettext('Your new address is set up and ready to go. You will be redirected to %s'), ["<strong class='website'></strong>"]) %> + </p> - <p class="siteinfo"> - <%= gettext('Your new address is set up and you should now be signed in. You may now close this window and go back to') %> <strong class="website"></strong> - </p> + <p id="redirection" class="siteinfo"> + <%- format(gettext("Redirecting in %s seconds"), ["<span id='redirectTimeout'></span>" ]) %> </p> </div> </div> diff --git a/resources/views/verify_email_address.ejs b/resources/views/verify_email_address.ejs index cf38f704a28620bedf40bebb932813e7b812d07e..d033b9417e2b39158d875138bb6ec24aca1a0286 100644 --- a/resources/views/verify_email_address.ejs +++ b/resources/views/verify_email_address.ejs @@ -62,7 +62,11 @@ </p> <p class="siteinfo"> - <%= gettext('Your new address is set up and you should now be signed in. You may now close this window and go back to') %> <strong class="website"></strong> + <%- format(gettext('Your new address is set up and ready to go. You will be redirected to %s'), ["<strong class='website'></strong>"]) %> + </p> + + <p id="redirection" class="siteinfo"> + <%- format(gettext("Redirecting in %s seconds"), ["<span id='redirectTimeout'></span>" ]) %> </p> </div>