diff --git a/ChangeLog b/ChangeLog index 7007f19826f694c19d76aa1b76304042d75ed146..2fb3d82083c8d4010c3b83694fa633af04ccc50e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20,6 +20,7 @@ train-2011.11.03: * 'LOG_TO_CONSOLE' env var for verbose console output during tests: #530 * more checks around '/code_update' URL invocation - for bug #699171 * Many minor bug-fixes: #497, #532 + * (2011.11.08) don't crash on mysql connection timeout: #540 train-2011.10.27: * link fixing ('need help?' to point to SUMO): #378 diff --git a/bin/browserid b/bin/browserid index 5651b70ab405a17a66710c44df1a3a0a15bed8a9..f495e14fdceaf77b2b1168cbe5a7574018215fbf 100755 --- a/bin/browserid +++ b/bin/browserid @@ -99,13 +99,18 @@ function router(app) { }); }); + app.get('/communication_iframe', function(req, res, next ) { + res.removeHeader('x-frame-options'); + res.render('communication_iframe.ejs', { + layout: false, + production: config.get('use_minified_resources') + }); + }); + app.get("/unsupported_dialog", function(req,res) { res.render('unsupported_dialog.ejs', {layout: 'dialog_layout.ejs', useJavascript: false}); }); - // simple redirects (internal for now) - app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html',true)); - // Used for a relay page for communication. app.get("/relay", function(req,res, next) { // Allow the relay to be run within a frame @@ -116,7 +121,6 @@ function router(app) { }); }); - app.get('/', function(req,res) { res.render('index.ejs', {title: 'A Better Way to Sign In', fullpage: true}); }); @@ -360,7 +364,13 @@ router(app); app.use(express.static(path.join(__dirname, "..", "resources", "static"))); // open the databse -db.open(config.get('database'), function () { +db.open(config.get('database'), function (error) { + + if (error) { + logger.error("can't open database: " + error); + // let async logging flush, then exit 1 + return setTimeout(function() { process.exit(1); }, 0); + } // shut down express gracefully on SIGINT shutdown.handleTerminationSignals(app, function(readyForShutdown) { diff --git a/example/index.html b/example/index.html index c4f48ecc4e22d98a02c1b3c082fe33487f104fa5..218584075f6923c2dcf1a81994315095f38a68a5 100644 --- a/example/index.html +++ b/example/index.html @@ -39,37 +39,37 @@ a:hover { border-bottom: 2px solid black ; } </div> <div class="intro"> - This is the simplest possible (stateless, static, client-only) BrowserID Relying Party. It - demonstrates the steps required to use BrowserID to verify the identity of a user. - <a id="partyStarter" href="#">Click here to kick off the party</a>. Here's what will happen: + This is the simplest possible BrowserID Relying Party. It + demonstrates the steps required to use BrowserID to verify + the identity of a user. Follow the steps below... </div> <div class="step"> <div class="number">1.</div> - <div class="desc"><b>Browser Interaction:</b> Upon clicking the link above, the webpage will call <tt>navigator.id.getVerifiedEmail()</tt> to indicate to the browser that it would like an identity for the user</div> + <div class="desc">At page load time, check to see if the user is already (persistently) signed in by calling <tt>navigator.id.get(<callback>, {silent:true});</tt> + <div class="output" id="oPersistent">...</div> </div> <div class="step"> <div class="number">2.</div> - <div class="desc"><b>User Interaction:</b> The browser will spawn a dialog that the user can interact with the select what identity they want to provide to the site </div> + <div class="desc">If the user is *not already signed in, wait for <a id="clickForLogin" href="#">their click</a>. </div> <div class="step"> <div class="number">3.</div> - <div class="desc"><b>Assertion Generation:</b> Upon selection of an identity, an <i>assertion</i> will be returned to the webpage via a callback, it looks like this: </div> - <div class="output" id="oAssertion">...waiting for party commencement...</div> + <div class="desc">Once an assertion is obtained, pass it up to the server for verification. The assertion looks like this:</div> + <div class="output" id="oAssertion">...</div> </div> <div class="step"> <div class="number">4.</div> - <div class="desc"><b>Assertion Verification:</b> This site can then send that assertion up to a <i>verification server</i> which cryptographically checks that the identity embedded in the verification actually belongs to the browser in use by the monkey at the keyboard. The request looks like </div> - <div class="output" id="oVerificationRequest">...waiting for party commencement...</div> + <div class="desc">The verification servers checks the assertion and returns a response, that looks like this:</div> + <div class="output" id="oVerificationResponse"><pre>...</pre></div> </div> <div class="step"> <div class="number">5.</div> - <div class="desc"><b>Verification Response</b>: The verification server responds to the site to tell it whether or not the verification blob is valid:</div> - <div class="output" id="oVerificationResponse"><pre>...waiting for party commencement...</pre></div> + <div class="desc">Next, you should provide a logout button that calls <tt>navigator.id.logout()</tt> and then does whatever application specific logout steps are required. <a href="#" id="logout">Click here to logout</a></div> </div> <div class="step"> @@ -81,43 +81,60 @@ a:hover { border-bottom: 2px solid black ; } <script src="jquery-min.js"></script> <script src="https://browserid.org/include.js"></script> <script> - $(function() { - $("#partyStarter").click(function(event) { - event.preventDefault(); - navigator.id.getVerifiedEmail(function(assertion) { - if (!assertion) { - alert("couldn't get the users email address!"); - } else { - // Now we'll send this assertion over to the verification server for validation - $("#oAssertion").empty().text(assertion); - - var url = "https://browserid.org/verify" - var data = { - assertion: assertion, - audience: window.location.protocol + "//" + window.location.host - }; - - $("#oVerificationRequest").empty().text("POST " + url + "\n" + JSON.stringify(data)); - - $.ajax({ - url: "/process_assertion", - type: "post", - dataType: "json", - data: data, - success: function(data, textStatus, jqXHR) { - $("#oVerificationResponse > pre").empty().text(JSON.stringify(data, null, 4)); - }, - error: function(jqXHR, textStatus, errorThrown) { - var statusEl = $("#oVerificationResponse > pre").empty(); - var resp = jqXHR.responseText ? - JSON.stringify(JSON.parse(jqXHR.responseText), null, 4) : errorThrown; - statusEl.text(resp); - } - }); - } - }); + +// 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) { + $("#oVerificationResponse > pre").text(JSON.stringify(data, null, 4)); + }, + error: function(jqXHR, textStatus, errorThrown) { + var resp = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : errorThrown; + $("#oVerificationResponse > pre").text(resp); + } + }); +}; + +// at page load time, we'll check to see if the user is already signed in +navigator.id.get(function(assertion) { + if (!assertion) { + $("#oPersistent").text("user isn't (persistently) signed in"); + } else { + $("#oPersistent").text(assertion); + checkAssertion(assertion); + }; +}, { silent: true }); + +$(document).ready(function() { + // install a click handler for when the user clicks 'sign in' + $("#clickForLogin").click(function(event) { + event.preventDefault(); + navigator.id.get(function(assertion) { + if (!assertion) { + $("#oAssertion").text("user didn't select an identity."); + } else { + $("#oAssertion").text(assertion); + checkAssertion(assertion); + }; }); }); + + $("#logout").click(function(event) { + event.preventDefault(); + navigator.id.logout(function() { + // XXX: what should we do after logout? + }); + }); + +}); + </script> </html> diff --git a/lib/configuration.js b/lib/configuration.js index 22cd848c6e054091b60194ac5bcfb2b64ba0d19c..4b150976df9609226c528a6e5d6bc788b78e48b4 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -83,7 +83,7 @@ g_configs.production = { create_schema: true }, bcrypt_work_factor: 12, - authentication_duration_ms: (7 * 24 * 60 * 60 * 1000), + authentication_duration_ms: (2 * 7 * 24 * 60 * 60 * 1000), certificate_validity_ms: (24 * 60 * 60 * 1000), min_time_between_emails_ms: (60 * 1000) }; diff --git a/package.json b/package.json index 5f5c922c0f1dddc4444b800f512f6c31a8bd024b..fc071efb127db436de08b64842161a75ab00bf0e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ , "JSONSelect": "0.2.1" , "winston" : "0.5.6" , "connect-cookie-session" : "0.0.2" - , "mysql" : "0.9.2" + , "mysql" : "0.9.4" , "optimist" : "0.2.6" , "qs" : "0.3.1" , "mime" : "1.2.2" diff --git a/resources/.gitignore b/resources/.gitignore index ad546db10e4922983382c9235cb61b007e8d2475..dd352edd1596051cc20d0bb3f8e593d4d8a84afe 100644 --- a/resources/.gitignore +++ b/resources/.gitignore @@ -3,6 +3,7 @@ /static/dialog/css/production.css /static/dialog/css/production.min.css /static/dialog/production.js +/static/communication_iframe/production.js /static/include.orig.js /static/js/lib.js /static/js/lib.min.js diff --git a/resources/static/communication_iframe/iframe.js b/resources/static/communication_iframe/iframe.js new file mode 100644 index 0000000000000000000000000000000000000000..5869116c932ab883ff3c972b46328f0c5ab9b272 --- /dev/null +++ b/resources/static/communication_iframe/iframe.js @@ -0,0 +1,101 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/*globals steal + */ +window.console = window.console || { + log: function() {} +}; + +steal + .plugins('jquery') + .then('../dialog/resources/jschannel', + '../dialog/resources/base64', + '../dialog/resources/underscore-min', + '../dialog/resources/channel', + '../dialog/resources/browserid', + '../dialog/resources/storage', + '../dialog/resources/tooltip', + '../dialog/resources/validation', + '../dialog/resources/browserid-extensions', + '../dialog/resources/network', + '../dialog/resources/user', + '../dialog/resources/error-messages', + '../dialog/resources/wait-messages', + afterResourceLoad); + +function afterResourceLoad() { + $(document).ready(function() { + var chan = Channel.build({ + window: window.parent, + origin: "*", + scope: "mozid" + }); + + var remoteOrigin = undefined; + + function setRemoteOrigin(o) { + if (!remoteOrigin) { + remoteOrigin = o; + BrowserID.User.setOrigin(remoteOrigin); + } + } + + chan.bind("getPersistentAssertion", function(trans, params) { + setRemoteOrigin(trans.origin); + + trans.delayReturn(true); + + BrowserID.User.getPersistentSigninAssertion(function(rv) { + console.log(rv); + trans.complete(rv); + }, function() { + trans.error(); + }); + }); + + chan.bind("logout", function(trans, params) { + setRemoteOrigin(trans.origin); + + trans.delayReturn(true); + + BrowserID.User.clearPersistentSignin(function(rv) { + trans.complete(rv); + }, function() { + trans.error(); + }); + }); + }); +} diff --git a/resources/static/dialog/controllers/pickemail_controller.js b/resources/static/dialog/controllers/pickemail_controller.js index a9197a4c60c03029d16fe39de02c24c67f4c0815..eda8675067f2cc817c7529d2451532bfaf7d1b78 100644 --- a/resources/static/dialog/controllers/pickemail_controller.js +++ b/resources/static/dialog/controllers/pickemail_controller.js @@ -42,7 +42,6 @@ user = bid.User, errors = bid.Errors, storage = bid.Storage, - origin, body = $("body"), animationComplete = body.innerWidth() < 640, assertion; @@ -57,9 +56,7 @@ function cancelEvent(event) { - if (event) { - event.preventDefault(); - } + if (event) event.preventDefault(); } function pickEmailState(element, event) { @@ -111,9 +108,6 @@ // the animation, hopefully this minimizes the delay the user notices. var self=this; user.getAssertion(email, function(assert) { - // XXX make a user api call that gets the assertion and sets the site - // email as well. - storage.setSiteEmail(origin, email); assertion = assert || null; startAnimation.call(self); }, self.getErrorDialog(errors.getAssertion)); @@ -143,6 +137,9 @@ var valid = checkEmail.call(self, email); if (valid) { + var origin = user.getOrigin(); + storage.site.set(origin, "email", email); + storage.site.set(origin, "remember", $("#remember").is(":checked")); getAssertion.call(self, email); } } @@ -181,8 +178,7 @@ PageController.extend("Pickemail", {}, { init: function(el, options) { - origin = options.origin; - + var origin = user.getOrigin(); this._super(el, { bodyTemplate: "pickemail.ejs", bodyVars: { @@ -190,7 +186,8 @@ // XXX ideal is to get rid of this and have a User function // that takes care of getting email addresses AND the last used email // for this site. - siteemail: storage.getSiteEmail(options.origin) + siteemail: storage.site.get(origin, "email"), + remember: storage.site.get(origin, "remember") || false } }); @@ -207,7 +204,10 @@ }, "#useNewEmail click": addEmailState, - "#cancelNewEmail click": pickEmailState + "#cancelNewEmail click": pickEmailState, + + signIn: signIn, + addEmail: addEmail }); }()); diff --git a/resources/static/dialog/css/popup.css b/resources/static/dialog/css/popup.css index c3231ddda30403eeb3b65c845978a04e856d2c0b..f8a344820dde43a0aa630e8c86b402690cab956c 100644 --- a/resources/static/dialog/css/popup.css +++ b/resources/static/dialog/css/popup.css @@ -272,20 +272,6 @@ section > .contents { text-align: center; } -#signIn .remember { - display: inline-block; - line-height: 28px; -} - -#signIn .remember .checkAlign { - float: left; -} - -#signIn .remember label { - margin-left: 5px; - float: left; -} - #signIn label.half, .half { width: 50%; @@ -312,8 +298,11 @@ label { text-shadow: 1px 1px 0 rgba(255,255,255,0.5); } -.inputs > li > label { +label.selectable { cursor: pointer; +} + +.inputs > li > label { color: #333; } @@ -321,7 +310,7 @@ label { font-weight: bold; } -.inputs > li:only-child > label { +.inputs > li:only-child > label.selectable { cursor: default; } @@ -361,6 +350,11 @@ input[type=password]:focus { box-shadow: 0 0 0 1px #549FDC inset; } +input[type=radio], +input[type=checkbox] { + cursor: pointer; +} + button { font-size: 14px; height: 28px; @@ -425,12 +419,6 @@ footer .learn a { text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.5); } - /* -#favicon { - text-align: center; - max-width: 940px; -} -*/ header { padding: 20px; font-weight: bold; @@ -585,6 +573,11 @@ html[xmlns] .cf { display: none; } +label[for=remember] { + display: inline; + margin-left: 13px; +} + #thisIsNotMe { margin-right: 10px; } diff --git a/resources/static/dialog/register_iframe.html b/resources/static/dialog/register_iframe.html deleted file mode 100644 index 390bf33901afdb3ae314b4d01847f40504636ca3..0000000000000000000000000000000000000000 --- a/resources/static/dialog/register_iframe.html +++ /dev/null @@ -1,6 +0,0 @@ -<head><title>non-interactive iframe</title> -<script src="/vepbundle"></script> -<script src="../dialog/resources/jschannel.js"></script> -<script src="../dialog/resources/storage.js"></script> -<script src="../dialog/register_iframe.js"></script> -</head><body></body> diff --git a/resources/static/dialog/register_iframe.js b/resources/static/dialog/register_iframe.js deleted file mode 100644 index e1e89397d7fc1275f212d5f860d474f21069260e..0000000000000000000000000000000000000000 --- a/resources/static/dialog/register_iframe.js +++ /dev/null @@ -1,188 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla BrowserID. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -// this is the picker code! it runs in the identity provider's domain, and -// fiddles the dom expressed by picker.html - -var jwk = require("./jwk"), - jwcert = require("./jwcert"), - vep = require("./vep"); - -(function() { - var chan = Channel.build( - { - window: window.parent, - origin: "*", - scope: "mozid" - }); - - // - // for now, DISABLE primary support - // - - /* - // primary requests a keygen to certify - chan.bind("generateKey", function(trans, args) { - // keygen - var keypair = jwk.KeyPair.generate(vep.params.algorithm, 64); - - // save it in a special place for now - BrowserIDStorage.storeTemporaryKeypair(keypair); - - // serialize and return - return keypair.publicKey.serialize(); - }); - - // add the cert - chan.bind("registerVerifiedEmailCertificate", function(trans, args) { - var keypair = BrowserIDStorage.retrieveTemporaryKeypair(); - - // parse the cert - var raw_cert = args.cert; - var cert = new jwcert.JWCert(); - cert.parse(raw_cert); - var email = cert.principal.email; - var pk = cert.pk; - - // check if the pk's match - if (!pk.equals(keypair.publicKey)) { - trans.error("bad cert"); - return; - } - - var new_email_obj= { - created: new Date(), - pub: keypair.publicKey.toSimpleObject(), - priv: keypair.secretKey.toSimpleObject(), - cert: raw_cert, - issuer: cert.issuer, - isPrimary: true - }; - - BrowserIDStorage.addEmail(email, new_email_obj); - }); - */ - - // reenable this once we're ready - /* - function isSuperDomain(domain) { - return true; - } - - // from - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript - function new_guid() { - var S4 = function() { - return (((1+Math.random())*0x10000)|0).toString(16).substring(1); - }; - return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); - } - - // pre-auth binding - chan.bind("preauthEmail", function(trans, email) { - if (!isSuperDomain(trans.origin)) { - alert('not a super domain!'); - return; - } - - // only one preauth, since this shouldn't happen in parallel - var guid = new_guid(); - window.localStorage['PREAUTH_' + guid] = JSON.stringify({'at': new Date(), 'email': email}); - - // the guid is returned, it will be used to actually perform - // the headless verification of email address - return guid; - }); - - // this version of getSpecificVerifiedEmail - // requires a token in the params, as it is called only in the IFRAME - chan.bind("getSpecificVerifiedEmail", function(trans, params) { - var email = params[0], token = params[1]; - trans.delayReturn(true); - - var remoteOrigin = trans.origin; - - // check to see if there's any pubkeys stored in the browser - var haveIDs = false; - try { - var emails = JSON.parse(window.localStorage.emails); - if (typeof emails !== 'object') throw "emails blob bogus!"; - for (var k in emails) { - if (!emails.hasOwnProperty(k)) continue; - haveIDs = true; - break; - } - } catch(e) { - window.localStorage.emails = JSON.stringify({}); - } - - function onsuccess(rv) { - trans.complete(rv); - } - function onerror(error) { - errorOut(trans, error); - } - - // wherever shall we start? - if (haveIDs) { - // can we pre-approve this? - var preauth = null; - if (window.localStorage['PREAUTH_' + token]) { - preauth = JSON.parse(window.localStorage['PREAUTH_' + token]); - } - if (token && preauth) { - window.localStorage['PREAUTH_' + token] = null; - var storedID = JSON.parse(window.localStorage.emails)[email]; - if (storedID && (email == preauth.email)) { - // ultimate success, pre-approved for an ID we have! - var privkey = storedID.priv; - var issuer = storedID.issuer; - var audience = remoteOrigin.replace(/^(http|https):\/\//, ''); - var assertion = CryptoStubs.createAssertion(audience, email, privkey, issuer); - onsuccess(assertion); - - // at this point, we have succeeded, we can stop - return; - - // if we go further than here, we have failed for some reason - } - } - } - - // if we get here, we've failed - trans.error("X", "not a proper token-based call"); - }); - */ -})(); diff --git a/resources/static/dialog/resources/storage.js b/resources/static/dialog/resources/storage.js index 809f912a404b44e6590fd4c0d8f73552cae6247f..53b1e041fe0813f2dd728996a8e1d8e61d0c0414 100644 --- a/resources/static/dialog/resources/storage.js +++ b/resources/static/dialog/resources/storage.js @@ -52,7 +52,7 @@ BrowserID.Storage = (function() { var localStorage = window.localStorage; localStorage.removeItem("tempKeypair"); localStorage.removeItem("stagedOnBehalfOf"); - localStorage.removeItem("sitesToEmail"); + localStorage.removeItem("siteInfo"); } function getEmails() { @@ -87,13 +87,13 @@ BrowserID.Storage = (function() { storeEmails(emails); // remove any sites associated with this email address. - var sitesToEmail = JSON.parse(localStorage.sitesToEmail || "{}"); - for(var site in sitesToEmail) { - if(sitesToEmail[site] === email) { - delete sitesToEmail[site]; + var siteInfo = JSON.parse(localStorage.siteInfo || "{}"); + for(var site in siteInfo) { + if(siteInfo[site].email === email) { + delete siteInfo[site].email; } } - localStorage.sitesToEmail = JSON.stringify(sitesToEmail); + localStorage.siteInfo = JSON.stringify(siteInfo); } else { throw "unknown email address"; @@ -160,25 +160,37 @@ BrowserID.Storage = (function() { return origin; } - function setSiteEmail(site, email) { - if(getEmail(email)) { - var localStorage = window.localStorage; + function siteSet(site, key, value) { + var allSiteInfo = JSON.parse(localStorage.siteInfo || "{}"); + var siteInfo = allSiteInfo[site] = allSiteInfo[site] || {}; - var sitesToEmail = JSON.parse(localStorage.sitesToEmail || "{}"); - sitesToEmail[site] = email; - - localStorage.sitesToEmail = JSON.stringify(sitesToEmail); - } - else { + if(key === "email" && !getEmail(value)) { throw "unknown email address"; } + + siteInfo[key] = value; + + localStorage.siteInfo = JSON.stringify(allSiteInfo); } - function getSiteEmail(site) { - var sitesToEmail = JSON.parse(localStorage.sitesToEmail || "{}"); - return sitesToEmail[site]; + function siteGet(site, key) { + var allSiteInfo = JSON.parse(localStorage.siteInfo || "{}"); + var siteInfo = allSiteInfo[site]; + + return siteInfo && siteInfo[key]; } + function siteRemove(site, key) { + var allSiteInfo = JSON.parse(localStorage.siteInfo || "{}"); + var siteInfo = allSiteInfo[site]; + + if (siteInfo) { + delete siteInfo[key]; + localStorage.siteInfo = JSON.stringify(allSiteInfo); + } + } + + return { /** * Add an email address and optional key pair. @@ -209,18 +221,32 @@ BrowserID.Storage = (function() { * @method invalidateEmail */ invalidateEmail: invalidateEmail, - /** - * Set the associated email address for a site - * @throws "uknown email address" if the email address is not known. - * @method setSiteEmail - */ - setSiteEmail: setSiteEmail, - /** - * Get the associated email address for a site, if known. If not known, - * return undefined. - * @method getSiteEmail - */ - getSiteEmail: getSiteEmail, + + site: { + /** + * Set a data field for a site + * @method site.set + * @param {string} site - site to set info for + * @param {string} key - key to set + * @param {variant} value - value to set + */ + set: siteSet, + /** + * Get a data field for a site + * @method site.get + * @param {string} site - site to get info for + * @param {string} key - key to get + */ + get: siteGet, + /** + * Remove a data field for a site + * @method site.remove + * @param {string} site - site to remove info for + * @param {string} key - key to remove + */ + remove: siteRemove + }, + /** * Clear all stored data - email addresses, key pairs, temporary key pairs, * site/email associations. diff --git a/resources/static/dialog/resources/user.js b/resources/static/dialog/resources/user.js index a1541ffbdc0f35ba165c6cb15603066f6a3cb152..2f0a576dbd853ad4e26adf74ca29fd82498ac9cf 100644 --- a/resources/static/dialog/resources/user.js +++ b/resources/static/dialog/resources/user.js @@ -273,7 +273,7 @@ BrowserID.User = (function() { if (onSuccess) onSuccess(info); }, onFailure); - } else if(onSuccess) { + } else if (onSuccess) { onSuccess(invalidInfo); } }, onFailure); @@ -308,7 +308,7 @@ BrowserID.User = (function() { success: reset }; - if(!reset) status.reason = "throttle"; + if (!reset) status.reason = "throttle"; if (onComplete) onComplete(status); }, onFailure); @@ -540,7 +540,7 @@ BrowserID.User = (function() { if (onSuccess) onSuccess(info); }, onFailure); - } else if(onSuccess) { + } else if (onSuccess) { onSuccess(invalidInfo); } }, onFailure); @@ -554,14 +554,14 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on error. */ removeEmail: function(email, onSuccess, onFailure) { - if(storage.getEmail(email)) { + if (storage.getEmail(email)) { network.removeEmail(email, function() { storage.removeEmail(email); if (onSuccess) { onSuccess(); } }, onFailure); - } else if(onSuccess) { + } else if (onSuccess) { onSuccess(); } }, @@ -656,10 +656,58 @@ BrowserID.User = (function() { */ clearStoredEmailKeypairs: function() { storage.clear(); + }, + + /** + * Get an assertion for the current domain, as long as the user has + * selected that they want the email/site remembered + * @method getPersistentSigninAssertion + * @param {function} onComplete - called on completion. Called with an + * assertion if successful, null otw. + * @param {function} onFailure - called on XHR failure. + */ + getPersistentSigninAssertion: function(onComplete, onFailure) { + var self=this; + + self.checkAuthentication(function(authenticated) { + if (authenticated) { + var remembered = storage.site.get(origin, "remember"); + var email = storage.site.get(origin, "email"); + if (remembered && email) { + self.getAssertion(email, onComplete, onFailure); + } + else if (onComplete) { + onComplete(null); + } + } + else if (onComplete) { + onComplete(null); + } + }, onFailure); + }, + + /** + * Clear the persistent signin field for the current origin + * @method clearPersistentSignin + * @param {function} onComplete - called on completion. Called with + * a boolean, true if successful, false otw. + * @param {function} onFailure - called on XHR failure. + */ + clearPersistentSignin: function(onComplete, onFailure) { + var self=this; + + self.checkAuthentication(function(authenticated) { + if (authenticated) { + storage.site.set(origin, "remember", false); + if (onComplete) { + onComplete(true); + } + } else if (onComplete) { + onComplete(false); + } + }, onFailure); } }; - User.setOrigin(document.location.host); - return User; }()); diff --git a/resources/static/dialog/scripts/build.js b/resources/static/dialog/scripts/build.js index 8ab7a42261f9f2d0ec98520fee8f864019e8109f..bfe30f66f9f2d1731a7a4d8aaae5dace6e4bc1b6 100644 --- a/resources/static/dialog/scripts/build.js +++ b/resources/static/dialog/scripts/build.js @@ -2,8 +2,12 @@ load("steal/rhino/steal.js"); steal.plugins('steal/build','steal/build/scripts','steal/build/styles',function() { - steal.build('../static/dialog/scripts/build.html',{ + steal.build('../static/dialog/scripts/build_dialog.html',{ to: '../static/dialog', compressor: 'concatOnly' }); + steal.build('../static/dialog/scripts/build_iframe.html',{ + to: '../static/communication_iframe', + compressor: 'concatOnly' + }); }); diff --git a/resources/static/dialog/scripts/build.html b/resources/static/dialog/scripts/build_dialog.html similarity index 100% rename from resources/static/dialog/scripts/build.html rename to resources/static/dialog/scripts/build_dialog.html diff --git a/resources/static/dialog/scripts/build_iframe.html b/resources/static/dialog/scripts/build_iframe.html new file mode 100644 index 0000000000000000000000000000000000000000..12bd1ab2b413c658d909a2e4a8e67eff65c0cb0a --- /dev/null +++ b/resources/static/dialog/scripts/build_iframe.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>iframe build page</title> + </head> + <body> + <script type='text/javascript' + src='../../steal/steal.js?communication_iframe/iframe.js'> + </script> + </body> +</html> diff --git a/resources/static/dialog/views/pickemail.ejs b/resources/static/dialog/views/pickemail.ejs index d0ca8939fd53453f73732b09a0ee541e52f7a39b..e14c07fe17753f2a5f8c6faeab0ba587549e6857 100644 --- a/resources/static/dialog/views/pickemail.ejs +++ b/resources/static/dialog/views/pickemail.ejs @@ -5,7 +5,7 @@ <% _.each(identities, function(email_obj, email_address) { %> <li> - <label for="<%= email_address %>" class="serif<% if (email_address === siteemail) { %> preselected<% } %>"> + <label for="<%= email_address %>" class="serif<% if (email_address === siteemail) { %> preselected<% } %> selectable"> <input type="radio" name="email" id="<%= email_address %>" value="<%= email_address %>" <% if (email_address === siteemail) { %> checked="checked" <% } %> /> @@ -16,7 +16,14 @@ </ul> <div class="submit add cf"> + + <label for="remember" class="selectable"> + <input type="checkbox" id="remember" name="remember" <% if (remember) { %> checked="checked" <% } %> /> + Always sign in using this email + </label> + <button id="signInButton">sign in</button> + <p> <a id="thisIsNotMe" href="#">This is not me</a> <a id="useNewEmail" href="#">Use a different email</a> diff --git a/resources/static/include.js b/resources/static/include.js index 9979ab4872895cf9b14db72ad430b5591c396229..8d2cd68a234a2cc781deee6eaaa69714f3ac801f 100644 --- a/resources/static/include.js +++ b/resources/static/include.js @@ -636,16 +636,15 @@ */ getNoSupportReason: getNoSupportReason }; - }()); // this is for calls that are non-interactive function _open_hidden_iframe(doc) { var iframe = doc.createElement("iframe"); - // iframe.style.display = "none"; + iframe.style.display = "none"; doc.body.appendChild(iframe); - iframe.src = ipServer + "/register_iframe"; + iframe.src = ipServer + "/communication_iframe"; return iframe; } @@ -668,7 +667,7 @@ return iframe; } - + function _open_window(url) { url = url || "about:blank"; // we open the window initially blank, and only after our relay frame has @@ -720,40 +719,7 @@ var chan, w, iframe; // keep track of these so that we can re-use/re-focus an already open window. - navigator.id.getVerifiedEmail = function(callback) { - if (w) { - // if there is already a window open, just focus the old window. - w.focus(); - return; - } - - if (!BrowserSupport.isSupported()) { - w = _open_window(ipServer + "/unsupported_dialog"); - return; - } - - var frameid = _get_relayframe_id(); - var iframe = _open_relayframe("browserid_relay_" + frameid); - w = _open_window(); - - // if the RP window closes, close the dialog as well. - _attach_event(window, 'unload', cleanup); - - // clean up a previous channel that never was reaped - if (chan) chan.destroy(); - chan = Channel.build({ - window: iframe.contentWindow, - origin: ipServer, - scope: "mozid", - onReady: function() { - // We have to change the name of the relay frame every time or else Firefox - // has a problem re-attaching new iframes with the same name. Code inside - // of frames with the same name sometimes does not get run. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=350023 - w = _open_window(ipServer + "/sign_in#" + frameid); - } - }); - + navigator.id.get = function(callback, options) { function cleanup() { chan.destroy(); chan = null; @@ -769,105 +735,80 @@ _detatch_event(window, 'unload', cleanup); } - chan.call({ - method: "getVerifiedEmail", - success: function(rv) { - if (callback) { - // return the string representation of the JWT, the client is responsible for unpacking it. - callback(rv); - } - cleanup(); - }, - error: function(code, msg) { - // XXX: we don't make the code and msg available to the user. - if (callback) callback(null); - cleanup(); - } - }); - }; - - /* - // preauthorize a particular email - // FIXME: lots of cut-and-paste code here, need to refactor - // not refactoring now because experimenting and don't want to break existing code - navigator.id.preauthEmail = function(email, onsuccess, onerror) { - var doc = window.document; - var iframe = _create_iframe(doc); - - // clean up a previous channel that never was reaped - if (chan) chan.destroy(); - chan = Channel.build({window: iframe.contentWindow, origin: ipServer, scope: "mozid"}); + if (typeof callback !== 'function') throw "navigator.id.get() requires a callback argument"; - function cleanup() { - chan.destroy(); - chan = undefined; - doc.body.removeChild(iframe); - } - - chan.call({ - method: "preauthEmail", - params: email, - success: function(rv) { - onsuccess(rv); - cleanup(); - }, - error: function(code, msg) { - if (onerror) onerror(code, msg); - cleanup(); + if (options && options.silent) { + _noninteractiveCall('getPersistentAssertion', { }, function(rv) { + callback(rv); + }, function(e, msg) { + callback(null); + }); + } else { + if (w) { + // if there is already a window open, just focus the old window. + w.focus(); + return; } - }); - }; - */ - // get a particular verified email - // FIXME: needs to ditched for now until fixed - /* - navigator.id.getSpecificVerifiedEmail = function(email, token, onsuccess, onerror) { - var doc = window.document; + if (!BrowserSupport.isSupported()) { + w = _open_window(ipServer + "/unsupported_dialog"); + return; + } - // if we have a token, we should not be opening a window, rather we should be - // able to do this entirely through IFRAMEs - var w; - if (token) { - var iframe = _create_iframe(doc); - w = iframe.contentWindow; - } else { - _open_window(); - _open_relay_frame(doc); + var frameid = _get_relayframe_id(); + var iframe = _open_relayframe("browserid_relay_" + frameid); + w = _open_window(); + + // if the RP window closes, close the dialog as well. + _attach_event(window, 'unload', cleanup); + + // clean up a previous channel that never was reaped + if (chan) chan.destroy(); + chan = Channel.build({ + window: iframe.contentWindow, + origin: ipServer, + scope: "mozid", + onReady: function() { + // We have to change the name of the relay frame every time or else Firefox + // has a problem re-attaching new iframes with the same name. Code inside + // of frames with the same name sometimes does not get run. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=350023 + w = _open_window(ipServer + "/sign_in#" + frameid); + } + }); + + chan.call({ + method: "getVerifiedEmail", + success: function(rv) { + if (callback) { + // return the string representation of the JWT, the client is responsible for unpacking it. + callback(rv); + } + cleanup(); + }, + error: function(code, msg) { + // XXX: we don't make the code and msg available to the user. + if (callback) callback(null); + cleanup(); + } + }); } + }; - // clean up a previous channel that never was reaped - if (chan) chan.destroy(); - chan = Channel.build({window: w, origin: ipServer, scope: "mozid"}); - - function cleanup() { - chan.destroy(); - chan = undefined; - if (token) { - // just remove the IFRAME - doc.body.removeChild(iframe); - } else { - w.close(); - } + navigator.id.getVerifiedEmail = function (callback, options) { + if (options) { + throw "getVerifiedEmail doesn't accept options. use navigator.id.get() instead."; } + navigator.id.get(callback); + }; - chan.call({ - method: "getSpecificVerifiedEmail", - params: [email, token], - success: function(rv) { - if (onsuccess) { - // return the string representation of the JWT, the client is responsible for unpacking it. - onsuccess(rv); - } - cleanup(); - }, - error: function(code, msg) { - if (onerror) onerror(code, msg); - cleanup(); - } + navigator.id.logout = function(callback) { + _noninteractiveCall('logout', { }, function(rv) { + callback(rv); + }, function() { + callback(null); }); }; - */ var _noninteractiveCall = function(method, args, onsuccess, onerror) { var doc = window.document; @@ -876,13 +817,13 @@ // clean up channel if (chan) chan.destroy(); chan = Channel.build({window: iframe.contentWindow, origin: ipServer, scope: "mozid"}); - + function cleanup() { chan.destroy(); chan = undefined; doc.body.removeChild(iframe); } - + chan.call({ method: method, params: args, @@ -896,35 +837,9 @@ if (onerror) onerror(code, msg); cleanup(); } - }); + }); } - // - // for now, disabling primary support. - // - - /* - // check if a valid cert exists for this verified email - // calls back with true or false - // FIXME: implement it for real, but - // be careful here because this needs to be limited - navigator.id.checkVerifiedEmail = function(email, onsuccess, onerror) { - onsuccess(false); - }; - - // generate a keypair - navigator.id.generateKey = function(onsuccess, onerror) { - _noninteractiveCall("generateKey", {}, - onsuccess, onerror); - }; - - navigator.id.registerVerifiedEmailCertificate = function(certificate, updateURL, onsuccess, onerror) { - _noninteractiveCall("registerVerifiedEmailCertificate", - {cert:certificate, updateURL: updateURL}, - onsuccess, onerror); - }; - */ - navigator.id._getVerifiedEmailIsShimmed = true; } }()); diff --git a/resources/static/dialog/test/funcunit/dialog_test.js b/resources/static/test/funcunit/dialog_test.js similarity index 100% rename from resources/static/dialog/test/funcunit/dialog_test.js rename to resources/static/test/funcunit/dialog_test.js diff --git a/resources/static/dialog/test/funcunit/funcunit.js b/resources/static/test/funcunit/funcunit.js similarity index 100% rename from resources/static/dialog/test/funcunit/funcunit.js rename to resources/static/test/funcunit/funcunit.js diff --git a/resources/static/dialog/qunit.html b/resources/static/test/qunit.html similarity index 88% rename from resources/static/dialog/qunit.html rename to resources/static/test/qunit.html index edfa72fae3f32be78de27aa41f4ffea442525147..fcea3eb4bcd0fe7475890b87b05fd7f1e7bd182a 100644 --- a/resources/static/dialog/qunit.html +++ b/resources/static/test/qunit.html @@ -1,13 +1,13 @@ <html> <head> <link rel="stylesheet" type="text/css" href="/funcunit/qunit/qunit.css" /> - <title>dialog QUnit Test</title> + <title>BrowserID QUnit Test</title> <script type='text/javascript' src='/vepbundle'></script> - <script type='text/javascript' src='/steal/steal.js?/dialog/test/qunit'></script> + <script type='text/javascript' src='/steal/steal.js?/test/qunit'></script> </head> <body> - <h1 id="qunit-header">dialog Test Suite</h1> + <h1 id="qunit-header">BrowserID Test Suite</h1> <h2 id="qunit-banner"></h2> <div id="qunit-testrunner-toolbar"></div> <h2 id="qunit-userAgent"></h2> @@ -33,8 +33,10 @@ </div> <input id="email" /> + <input id="newEmail" /> <input id="password" /> <input id="vpassword" /> + <input type="checkbox" id="remember" /> <div id="congrats">Congrats!</div> <span id="cannotconfirm" class="error">Cannot confirm</span> <span id="cannotcommunicate" class="error">Cannot communicate</span> diff --git a/resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js b/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js rename to resources/static/test/qunit/controllers/authenticate_controller_unit_test.js diff --git a/resources/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js b/resources/static/test/qunit/controllers/dialog_controller_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js rename to resources/static/test/qunit/controllers/dialog_controller_unit_test.js diff --git a/resources/static/dialog/test/qunit/controllers/page_controller_unit_test.js b/resources/static/test/qunit/controllers/page_controller_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/controllers/page_controller_unit_test.js rename to resources/static/test/qunit/controllers/page_controller_unit_test.js diff --git a/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js b/resources/static/test/qunit/controllers/pickemail_controller_unit_test.js similarity index 67% rename from resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js rename to resources/static/test/qunit/controllers/pickemail_controller_unit_test.js index 25730ca41e0bbbd536078a531591c22e5d843992..75390108f736adca6113bbde3c45e3913031aaed 100644 --- a/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js +++ b/resources/static/test/qunit/controllers/pickemail_controller_unit_test.js @@ -39,7 +39,9 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con var controller, el = $("body"), - storage = BrowserID.Storage; + storage = BrowserID.Storage, + user = BrowserID.User, + testOrigin = "http://browserid.org"; function reset() { el = $("#controller_head"); @@ -52,6 +54,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con setup: function() { reset(); storage.clear(); + user.setOrigin(testOrigin); }, teardown: function() { @@ -67,9 +70,9 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con test("pickemail controller with email associated with site", function() { storage.addEmail("testuser@testuser.com", {priv: "priv", pub: "pub"}); storage.addEmail("testuser2@testuser.com", {priv: "priv", pub: "pub"}); - storage.setSiteEmail("browserid.org", "testuser2@testuser.com"); + storage.site.set(testOrigin, "email", "testuser2@testuser.com"); - controller = el.pickemail({origin: "browserid.org"}).controller(); + controller = el.pickemail().controller(); ok(controller, "controller created"); var radioButton = $("input[type=radio]").eq(1); @@ -82,7 +85,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con test("pickemail controller without email associated with site", function() { storage.addEmail("testuser@testuser.com", {priv: "priv", pub: "pub"}); - controller = el.pickemail({origin: "browserid.org"}).controller(); + controller = el.pickemail().controller(); ok(controller, "controller created"); var radioButton = $("input[type=radio]").eq(0); @@ -92,5 +95,46 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con equal(label.hasClass("preselected"), false, "the label has no class"); }); + function testRemember(remember) { + storage.site.set(testOrigin, "remember", remember); + + controller = el.pickemail().controller(); + ok(controller, "controller created"); + + equal($("#remember").is(":checked"), remember, "appropriate checkbox check"); + } + + test("pickemail controller with remember set to false", function() { + testRemember(false); + }); + + test("pickemail controller with remember set to true", function() { + testRemember(true); + }); + + + test("signIn saves email, remember status to storage", function() { + storage.addEmail("testuser@testuser.com", {priv: "priv", pub: "pub"}); + storage.addEmail("testuser2@testuser.com", {priv: "priv", pub: "pub"}); + + controller = el.pickemail().controller(); + + $("input[type=radio]").eq(1).click(); + $("#remember").attr("checked", true); + + controller.signIn(); + + equal(storage.site.get(testOrigin, "email"), "testuser2@testuser.com", "email saved correctly"); + equal(storage.site.get(testOrigin, "remember"), true, "remember saved correctly"); + + $("input[type=radio]").eq(0).click(); + $("#remember").removeAttr("checked"); + + controller.signIn(); + + equal(storage.site.get(testOrigin, "email"), "testuser@testuser.com", "email saved correctly"); + equal(storage.site.get(testOrigin, "remember"), false, "remember saved correctly"); + }); + }); diff --git a/resources/static/dialog/test/qunit/dialog_test.js b/resources/static/test/qunit/dialog_test.js similarity index 100% rename from resources/static/dialog/test/qunit/dialog_test.js rename to resources/static/test/qunit/dialog_test.js diff --git a/resources/static/dialog/test/qunit/include_unit_test.js b/resources/static/test/qunit/include_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/include_unit_test.js rename to resources/static/test/qunit/include_unit_test.js diff --git a/resources/static/dialog/test/qunit/js/browserid_unit_test.js b/resources/static/test/qunit/js/browserid_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/js/browserid_unit_test.js rename to resources/static/test/qunit/js/browserid_unit_test.js diff --git a/resources/static/dialog/test/qunit/js/page_helpers_unit_test.js b/resources/static/test/qunit/js/page_helpers_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/js/page_helpers_unit_test.js rename to resources/static/test/qunit/js/page_helpers_unit_test.js diff --git a/resources/static/dialog/test/qunit/mocks/mocks.js b/resources/static/test/qunit/mocks/mocks.js similarity index 100% rename from resources/static/dialog/test/qunit/mocks/mocks.js rename to resources/static/test/qunit/mocks/mocks.js diff --git a/resources/static/dialog/test/qunit/mocks/xhr.js b/resources/static/test/qunit/mocks/xhr.js similarity index 99% rename from resources/static/dialog/test/qunit/mocks/xhr.js rename to resources/static/test/qunit/mocks/xhr.js index 3c09c8cabc78adfae74fb6c98dd6106dd98ab1fd..daeb394a07459861b82ce39eb4458fb3f6c7044d 100644 --- a/resources/static/dialog/test/qunit/mocks/xhr.js +++ b/resources/static/test/qunit/mocks/xhr.js @@ -53,7 +53,7 @@ BrowserID.Mocks.xhr = (function() { */ var xhr = { results: { - "get /wsapi/session_context valid": contextInfo, + "get /wsapi/session_context valid": contextInfo, "get /wsapi/session_context invalid": contextInfo, // We are going to test for XHR failures for session_context using // call to serverTime. We are going to use the flag contextAjaxError diff --git a/resources/static/dialog/test/qunit/pages/add_email_address_test.js b/resources/static/test/qunit/pages/add_email_address_test.js similarity index 96% rename from resources/static/dialog/test/qunit/pages/add_email_address_test.js rename to resources/static/test/qunit/pages/add_email_address_test.js index f457a7425db4fc60b12fccc56aaa9e7996fdbb89..93d1863ed3eb89e23eefa38b1bbacc215ae9582a 100644 --- a/resources/static/dialog/test/qunit/pages/add_email_address_test.js +++ b/resources/static/test/qunit/pages/add_email_address_test.js @@ -34,7 +34,7 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/js/pages/add_email_address", function() { +steal.plugins("jquery").then("/dialog/resources/network", "/js/pages/add_email_address", function() { "use strict"; var bid = BrowserID, diff --git a/resources/static/dialog/test/qunit/pages/forgot_unit_test.js b/resources/static/test/qunit/pages/forgot_unit_test.js similarity index 96% rename from resources/static/dialog/test/qunit/pages/forgot_unit_test.js rename to resources/static/test/qunit/pages/forgot_unit_test.js index 1a4107ade72310030327a456d0428bbaf8feb591..2dbbca592ba47822b965e2e7621417b30b9717d7 100644 --- a/resources/static/dialog/test/qunit/pages/forgot_unit_test.js +++ b/resources/static/test/qunit/pages/forgot_unit_test.js @@ -34,7 +34,7 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/forgot", function() { +steal.plugins("jquery").then("/dialog/resources/network", "/dialog/resources/user", "/js/pages/forgot", function() { "use strict"; var bid = BrowserID, diff --git a/resources/static/dialog/test/qunit/pages/verify_email_address_test.js b/resources/static/test/qunit/pages/verify_email_address_test.js similarity index 97% rename from resources/static/dialog/test/qunit/pages/verify_email_address_test.js rename to resources/static/test/qunit/pages/verify_email_address_test.js index c4cc27e0c84bd07b1528d393e60a78bf9208990a..879e8cf0a3d45d27f580bb74b3e78f3d69d5cee1 100644 --- a/resources/static/dialog/test/qunit/pages/verify_email_address_test.js +++ b/resources/static/test/qunit/pages/verify_email_address_test.js @@ -34,7 +34,7 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/js/pages/verify_email_address", function() { +steal.plugins("jquery").then("/dialog/resources/network", "/js/pages/verify_email_address", function() { "use strict"; var bid = BrowserID, diff --git a/resources/static/dialog/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js similarity index 91% rename from resources/static/dialog/test/qunit/qunit.js rename to resources/static/test/qunit/qunit.js index c0ed533a58d256b870ff5d7ddcb59cd85aa15c8d..916a3e5a0dd0e6a709d7181950257c9c23be0f5c 100644 --- a/resources/static/dialog/test/qunit/qunit.js +++ b/resources/static/test/qunit/qunit.js @@ -1,13 +1,14 @@ steal("/dialog/resources/browserid.js", + "/test/qunit/mocks/mocks.js", + "/test/qunit/mocks/xhr.js", "/dialog/resources/browser-support.js", "/dialog/resources/error-messages.js", "/dialog/resources/error-display.js", "/dialog/resources/storage.js", "/dialog/resources/tooltip.js", "/dialog/resources/validation.js", - "/dialog/resources/underscore-min.js", - "/dialog/test/qunit/mocks/mocks.js", - "/dialog/test/qunit/mocks/xhr.js") + "/dialog/resources/underscore-min.js" + ) .plugins( "jquery", "jquery/controller", diff --git a/resources/static/dialog/test/qunit/relay/relay_unit_test.js b/resources/static/test/qunit/relay/relay_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/relay/relay_unit_test.js rename to resources/static/test/qunit/relay/relay_unit_test.js diff --git a/resources/static/dialog/test/qunit/resources/browser-support_unit_test.js b/resources/static/test/qunit/resources/browser-support_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/resources/browser-support_unit_test.js rename to resources/static/test/qunit/resources/browser-support_unit_test.js diff --git a/resources/static/dialog/test/qunit/resources/channel_unit_test.js b/resources/static/test/qunit/resources/channel_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/resources/channel_unit_test.js rename to resources/static/test/qunit/resources/channel_unit_test.js diff --git a/resources/static/dialog/test/qunit/resources/network_unit_test.js b/resources/static/test/qunit/resources/network_unit_test.js similarity index 99% rename from resources/static/dialog/test/qunit/resources/network_unit_test.js rename to resources/static/test/qunit/resources/network_unit_test.js index 25b95e2fca5adb2dad7aed5a001dbda30d96779a..cfd90f41d51b242263c573db20395d800c739f98 100644 --- a/resources/static/dialog/test/qunit/resources/network_unit_test.js +++ b/resources/static/test/qunit/resources/network_unit_test.js @@ -34,7 +34,7 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", "/dialog/test/qunit/mocks/xhr", function() { +steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", function() { "use strict"; var testName, diff --git a/resources/static/dialog/test/qunit/resources/storage_unit_test.js b/resources/static/test/qunit/resources/storage_unit_test.js similarity index 76% rename from resources/static/dialog/test/qunit/resources/storage_unit_test.js rename to resources/static/test/qunit/resources/storage_unit_test.js index 352d24113f763ba09c57072714d3c8a228723278..40e17f5a7e872b4d05368b2b6e49cfc72f170a6b 100644 --- a/resources/static/dialog/test/qunit/resources/storage_unit_test.js +++ b/resources/static/test/qunit/resources/storage_unit_test.js @@ -114,16 +114,32 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/storage", func equal(error.toString(), "unknown email address", "Invalidating an unknown email address"); }); - test("getSiteEmail with site not found", function() { - var email = storage.getSiteEmail("www.testsite.com"); + test("site.set/site.get/site.remove, happy case", function() { + storage.site.set("www.testsite.com", "autoauth", true); + equal(storage.site.get("www.testsite.com", "autoauth"), true, "set/get works correctly"); - equal(typeof email, "undefined", "if site not found, returned undefined"); + storage.site.remove("www.testsite.com", "autoauth"); + equal(typeof storage.site.get("www.testsite.com", "autoauth"), "undefined", "after remove, get returns undefined"); }); - test("setSiteEmail with email that is not known about", function() { + test("clear clears site info", function() { + storage.site.set("www.testsite.com", "autoauth", true); + storage.clear(); + equal(typeof storage.site.get("www.testsite.com", "autoauth"), "undefined", "after clear, get returns undefined"); + }); + + test("site.get on field for site with no info", function() { + equal(typeof storage.site.get("site.with.noinfo", "autoauth"), "undefined", "get works on site with no info"); + }); + + test("site.get on field that is not set", function() { + equal(typeof storage.site.get("www.testsite.com", "notset"), "undefined", "get works on undefined field"); + }); + + test("site.set->email with email that is not known about", function() { var error; try { - storage.setSiteEmail("www.testsite.com", "testuser@testuser.com"); + storage.site.set("www.testsite.com", "email", "testuser@testuser.com"); } catch(e) { error = e; } @@ -131,32 +147,23 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/storage", func equal(error.toString(), "unknown email address", "An unknown email address was added"); }); - test("setSiteEmail with valid email", function() { + test("site.set->email with valid email", function() { storage.addEmail("testuser@testuser.com", {}); - storage.setSiteEmail("www.testsite.com", "testuser@testuser.com"); - var email = storage.getSiteEmail("www.testsite.com"); + storage.site.set("www.testsite.com", "email", "testuser@testuser.com"); + var email = storage.site.get("www.testsite.com", "email"); equal(email, "testuser@testuser.com", "set/get have the same email for the site"); }); - test("removeEmail after setSiteEmail removes site", function() { + test("removeEmail after site.set->email removes email", function() { storage.addEmail("testuser@testuser.com", {}); - storage.setSiteEmail("www.testsite.com", "testuser@testuser.com"); + storage.site.set("www.testsite.com", "email", "testuser@testuser.com"); storage.removeEmail("testuser@testuser.com"); - var email = storage.getSiteEmail("www.testsite.com"); + var email = storage.site.get("www.testsite.com", "email"); equal(typeof email, "undefined", "after removing an email address, email for site is no longer available"); }); - test("clear clears site email info", function() { - storage.addEmail("testuser@testuser.com", {}); - storage.setSiteEmail("www.testsite.com", "testuser@testuser.com"); - storage.clear(); - var email = storage.getSiteEmail("www.testsite.com"); - - equal(typeof email, "undefined", "after clearing, site email is not found"); - }); - test("storeTemporaryKeypair", function() { // XXX needs a test }); diff --git a/resources/static/dialog/test/qunit/resources/tooltip_unit_test.js b/resources/static/test/qunit/resources/tooltip_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/resources/tooltip_unit_test.js rename to resources/static/test/qunit/resources/tooltip_unit_test.js diff --git a/resources/static/dialog/test/qunit/resources/user_unit_test.js b/resources/static/test/qunit/resources/user_unit_test.js similarity index 86% rename from resources/static/dialog/test/qunit/resources/user_unit_test.js rename to resources/static/test/qunit/resources/user_unit_test.js index 387ea689d28d4e43806f2e30a354e8b65d39c021..5efbb5dbb52adfba5fe0391c0cbbd99fb5fb316e 100644 --- a/resources/static/dialog/test/qunit/resources/user_unit_test.js +++ b/resources/static/test/qunit/resources/user_unit_test.js @@ -91,6 +91,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio BrowserID.Network.setXHR(xhr); xhr.useResult("valid"); lib.clearStoredEmailKeypairs(); + lib.setOrigin(testOrigin); }, teardown: function() { BrowserID.Network.setXHR($); @@ -960,4 +961,134 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); + test("getPersistentSigninAssertion with invalid login", function() { + xhr.setContextInfo("authenticated", false); + + lib.syncEmailKeypair("testuser@testuser.com", function() { + storage.site.set(testOrigin, "remember", false); + storage.site.set(testOrigin, "email", "testuser@testuser.com"); + xhr.useResult("invalid"); + + lib.getPersistentSigninAssertion(function onComplete(assertion) { + strictEqual(assertion, null, "assertion with invalid login is null"); + start(); + }, function onFailure() { + ok(false, "no expected XHR failure"); + start(); + }); + }); + + stop(); + }); + + test("getPersistentSigninAssertion with valid login with remember set to true but no email", function() { + xhr.setContextInfo("authenticated", true); + storage.site.set(testOrigin, "remember", true); + storage.site.remove(testOrigin, "email"); + + lib.getPersistentSigninAssertion(function onComplete(assertion) { + strictEqual(assertion, null, "assertion with no email is null"); + start(); + }, function onFailure() { + ok(false, "no expected XHR failure"); + start(); + }); + + stop(); + }); + + test("getPersistentSigninAssertion with valid login with email and remember set to false", function() { + xhr.setContextInfo("authenticated", true); + lib.syncEmailKeypair("testuser@testuser.com", function() { + storage.site.set(testOrigin, "remember", false); + storage.site.set(testOrigin, "email", "testuser@testuser.com"); + // invalidate the email so that we force a fresh key certification with + // the server + storage.invalidateEmail("testuser@testuser.com"); + + lib.getPersistentSigninAssertion(function onComplete(assertion) { + strictEqual(assertion, null, "assertion with remember=false is null"); + start(); + }, function onFailure() { + ok(false, "no expected XHR failure"); + start(); + }); + }); + + stop(); + }); + + test("getPersistentSigninAssertion with valid login, email, and remember set to true", function() { + xhr.setContextInfo("authenticated", true); + lib.syncEmailKeypair("testuser@testuser.com", function() { + storage.site.set(testOrigin, "remember", true); + storage.site.set(testOrigin, "email", "testuser@testuser.com"); + // invalidate the email so that we force a fresh key certification with + // the server + storage.invalidateEmail("testuser@testuser.com"); + + lib.getPersistentSigninAssertion(function onComplete(assertion) { + ok(assertion, "we have an assertion!"); + start(); + }, function onFailure() { + ok(false, "no expected XHR failure"); + start(); + }); + }); + + stop(); + }); + + test("getPersistentSigninAssertion with XHR failure", function() { + xhr.setContextInfo("authenticated", true); + lib.syncEmailKeypair("testuser@testuser.com", function() { + storage.site.set(testOrigin, "remember", true); + storage.site.set(testOrigin, "email", "testuser@testuser.com"); + // invalidate the email so that we force a fresh key certification with + // the server + storage.invalidateEmail("testuser@testuser.com"); + + xhr.useResult("ajaxError"); + + lib.getPersistentSigninAssertion(function onComplete(assertion) { + ok(false, "ajax error should not pass"); + start(); + }, function onFailure() { + ok(true, "ajax error should not pass"); + start(); + }); + }); + + stop(); + }); + + test("clearPersistentSignin with invalid login", function() { + xhr.setContextInfo("authenticated", false); + + lib.clearPersistentSignin(function onComplete(success) { + strictEqual(success, false, "success with invalid login is false"); + start(); + }, function onFailure() { + ok(false, "no expected XHR failure"); + start(); + }); + + stop(); + }); + + test("clearPersistentSignin with valid login with remember set to true", function() { + xhr.setContextInfo("authenticated", true); + storage.site.set(testOrigin, "remember", true); + + lib.clearPersistentSignin(function onComplete(success) { + strictEqual(success, true, "success flag good"); + strictEqual(storage.site.get(testOrigin, "remember"), false, "remember flag set to false"); + start(); + }, function onFailure() { + ok(false, "no expected XHR failure"); + start(); + }); + + stop(); + }); }); diff --git a/resources/static/dialog/test/qunit/resources/validation_unit_test.js b/resources/static/test/qunit/resources/validation_unit_test.js similarity index 100% rename from resources/static/dialog/test/qunit/resources/validation_unit_test.js rename to resources/static/test/qunit/resources/validation_unit_test.js diff --git a/resources/views/communication_iframe.ejs b/resources/views/communication_iframe.ejs new file mode 100644 index 0000000000000000000000000000000000000000..b7fb8d007befcb2d2438a9a2ff788b5fd163d819 --- /dev/null +++ b/resources/views/communication_iframe.ejs @@ -0,0 +1,4 @@ +<head><title>non-interactive iframe</title> + <script type="text/javascript" src="/vepbundle"></script> + <script type="text/javascript" src="steal/steal<%= production ? '.production' : '' %>.js?communication_iframe/iframe.js"></script> +</head><body></body> diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs index 13dd4d98f92ddc60880e28d7105492ba153837ae..a2b6ce692a36d56f5d004f41ce92ce8c40f4b6c2 100644 --- a/resources/views/dialog_layout.ejs +++ b/resources/views/dialog_layout.ejs @@ -31,7 +31,7 @@ <ul class="cf"> <li>By <a href="http://mozillalabs.com">Mozilla Labs</a></li> - <li>—</li> + <li>—</li> <li><a href="#">Privacy</a></li> <li><a href="#">TOS</a></li> </ul> diff --git a/scripts/compress.sh b/scripts/compress.sh index 6233646e5d25ab275e4efb3e87b30ae77697922e..e40babcdddbd83c60d56f75c7da4b884f28c79ed 100755 --- a/scripts/compress.sh +++ b/scripts/compress.sh @@ -30,7 +30,11 @@ echo '' steal/js dialog/scripts/build.js -cd dialog +cd communication_iframe +$UGLIFY < production.js > production.min.js +mv production.min.js production.js + +cd ../dialog $UGLIFY < production.js > production.min.js mv production.min.js production.js