From 90ebd209a3977b48aac738d848a8c8af24f1b822 Mon Sep 17 00:00:00 2001 From: Lloyd Hilaiel <lloyd@hilaiel.com> Date: Tue, 8 Nov 2011 17:02:28 -0700 Subject: [PATCH] implement 'keep me signed in' and associated API changes. closes #490 --- bin/browserid | 12 +- example/index.html | 109 +++++---- resources/.gitignore | 1 + .../static/communication_iframe/iframe.js | 101 ++++++++ .../controllers/pickemail_controller.js | 17 +- resources/static/dialog/css/popup.css | 37 ++- resources/static/dialog/qunit.html | 2 + resources/static/dialog/register_iframe.html | 6 - resources/static/dialog/register_iframe.js | 188 --------------- resources/static/dialog/resources/storage.js | 86 ++++--- resources/static/dialog/resources/user.js | 62 ++++- resources/static/dialog/scripts/build.js | 6 +- .../scripts/{build.html => build_dialog.html} | 0 .../static/dialog/scripts/build_iframe.html | 11 + .../pickemail_controller_unit_test.js | 43 +++- .../static/dialog/test/qunit/mocks/xhr.js | 2 +- .../test/qunit/resources/storage_unit_test.js | 47 ++-- .../test/qunit/resources/user_unit_test.js | 131 ++++++++++ resources/static/dialog/views/pickemail.ejs | 9 +- resources/static/include.js | 227 ++++++------------ resources/views/communication_iframe.ejs | 4 + resources/views/dialog_layout.ejs | 2 +- scripts/compress.sh | 6 +- 23 files changed, 616 insertions(+), 493 deletions(-) create mode 100644 resources/static/communication_iframe/iframe.js delete mode 100644 resources/static/dialog/register_iframe.html delete mode 100644 resources/static/dialog/register_iframe.js rename resources/static/dialog/scripts/{build.html => build_dialog.html} (100%) create mode 100644 resources/static/dialog/scripts/build_iframe.html create mode 100644 resources/views/communication_iframe.ejs diff --git a/bin/browserid b/bin/browserid index 5651b70ab..a5304f881 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}); }); diff --git a/example/index.html b/example/index.html index c4f48ecc4..218584075 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/resources/.gitignore b/resources/.gitignore index ad546db10..dd352edd1 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 000000000..5869116c9 --- /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 a9197a4c6..ce56f0071 100644 --- a/resources/static/dialog/controllers/pickemail_controller.js +++ b/resources/static/dialog/controllers/pickemail_controller.js @@ -57,9 +57,7 @@ function cancelEvent(event) { - if (event) { - event.preventDefault(); - } + if (event) event.preventDefault(); } function pickEmailState(element, event) { @@ -111,9 +109,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 +138,8 @@ var valid = checkEmail.call(self, email); if (valid) { + storage.site.set(user.getOrigin(), "email", email); + storage.site.set(user.getOrigin(), "remember", $("#remember").is(":checked")); getAssertion.call(self, email); } } @@ -190,7 +187,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(user.getOrigin(), "email"), + remember: storage.site.get(user.getOrigin(), "remember") || false } }); @@ -207,7 +205,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 931825141..b8e687ff6 100644 --- a/resources/static/dialog/css/popup.css +++ b/resources/static/dialog/css/popup.css @@ -273,20 +273,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%; @@ -313,8 +299,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; } @@ -322,7 +311,7 @@ label { font-weight: bold; } -.inputs > li:only-child > label { +.inputs > li:only-child > label.selectable { cursor: default; } @@ -362,6 +351,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; @@ -426,12 +420,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; @@ -586,6 +574,11 @@ html[xmlns] .cf { display: none; } +label[for=remember] { + display: inline; + margin-left: 13px; +} + #thisIsNotMe { margin-right: 10px; } diff --git a/resources/static/dialog/qunit.html b/resources/static/dialog/qunit.html index 17d4b0b53..83516e7fe 100644 --- a/resources/static/dialog/qunit.html +++ b/resources/static/dialog/qunit.html @@ -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/register_iframe.html b/resources/static/dialog/register_iframe.html deleted file mode 100644 index 390bf3390..000000000 --- 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 e1e89397d..000000000 --- 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 809f912a4..53b1e041f 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 a1541ffbd..2f0a576db 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 8ab7a4226..bfe30f66f 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 000000000..12bd1ab2b --- /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/test/qunit/controllers/pickemail_controller_unit_test.js b/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js index 25730ca41..9d8daabe4 100644 --- a/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js +++ b/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js @@ -67,7 +67,7 @@ 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("browserid.org", "email", "testuser2@testuser.com"); controller = el.pickemail({origin: "browserid.org"}).controller(); ok(controller, "controller created"); @@ -92,5 +92,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("browserid.org", "remember", remember); + + controller = el.pickemail({origin: "browserid.org"}).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", function() { + storage.addEmail("testuser@testuser.com", {priv: "priv", pub: "pub"}); + storage.addEmail("testuser2@testuser.com", {priv: "priv", pub: "pub"}); + + controller = el.pickemail({origin: "browserid.org"}).controller(); + + $("input[type=radio]").eq(1).click(); + $("#remember").attr("checked", true); + + controller.signIn(); + + equal(storage.site.get("browserid.org", "email"), "testuser2@testuser.com", "email saved correctly"); + equal(storage.site.get("browserid.org", "remember"), true, "remember saved correctly"); + + $("input[type=radio]").eq(0).click(); + $("#remember").removeAttr("checked"); + + controller.signIn(); + + equal(storage.site.get("browserid.org", "email"), "testuser@testuser.com", "email saved correctly"); + equal(storage.site.get("browserid.org", "remember"), false, "remember saved correctly"); + }); + }); diff --git a/resources/static/dialog/test/qunit/mocks/xhr.js b/resources/static/dialog/test/qunit/mocks/xhr.js index 3c09c8cab..daeb394a0 100644 --- a/resources/static/dialog/test/qunit/mocks/xhr.js +++ b/resources/static/dialog/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/resources/storage_unit_test.js b/resources/static/dialog/test/qunit/resources/storage_unit_test.js index 352d24113..40e17f5a7 100644 --- a/resources/static/dialog/test/qunit/resources/storage_unit_test.js +++ b/resources/static/dialog/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/user_unit_test.js b/resources/static/dialog/test/qunit/resources/user_unit_test.js index 387ea689d..5efbb5dbb 100644 --- a/resources/static/dialog/test/qunit/resources/user_unit_test.js +++ b/resources/static/dialog/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/views/pickemail.ejs b/resources/static/dialog/views/pickemail.ejs index d0ca8939f..e14c07fe1 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 9979ab487..8d2cd68a2 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/views/communication_iframe.ejs b/resources/views/communication_iframe.ejs new file mode 100644 index 000000000..b7fb8d007 --- /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 13dd4d98f..a2b6ce692 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 6233646e5..e40babcdd 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 -- GitLab