Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

integrating train 2011.08.25

  • Loading branch information...
commit aefe662a1017c727c3b49ec83110445d000fc39e 2 parents 0a8a820 + 2f3fe55
Shane Tomlinson shane-tomlinson authored
Showing with 4,993 additions and 437 deletions.
  1. +18 −0 ChangeLog
  2. +0 −2  DEPLOYMENT.md
  3. +5 −1 README.md
  4. +5 −1 browserid/app.js
  5. +8 −3 browserid/compress.sh
  6. +1 −1  browserid/lib/db.js
  7. +5 −1 browserid/lib/db_json.js
  8. +30 −28 browserid/lib/db_mysql.js
  9. +78 −0 browserid/lib/fake_verification.js
  10. +87 −45 browserid/lib/wsapi.js
  11. +5 −0 browserid/static/css/style.css
  12. +2 −6 browserid/static/dialog/controllers/addemail_controller.js
  13. +9 −8 browserid/static/dialog/controllers/authenticate_controller.js
  14. +1 −4 browserid/static/dialog/controllers/checkregistration_controller.js
  15. +2 −9 browserid/static/dialog/controllers/createaccount_controller.js
  16. +20 −67 browserid/static/dialog/controllers/dialog_controller.js
  17. +2 −7 browserid/static/dialog/controllers/forgotpassword_controller.js
  18. +18 −1 browserid/static/dialog/controllers/page_controller.js
  19. +1 −0  browserid/static/dialog/dialog.js
  20. +3 −3 browserid/static/dialog/qunit.html
  21. +344 −0 browserid/static/dialog/resources/browserid-identities.js
  22. +156 −44 browserid/static/dialog/resources/browserid-network.js
  23. +260 −0 browserid/static/dialog/test/qunit/browserid-identities_functional_test.js
  24. +460 −0 browserid/static/dialog/test/qunit/browserid-identities_unit_test.js
  25. +150 −0 browserid/static/dialog/test/qunit/browserid-network_test.js
  26. +8 −3 browserid/static/dialog/test/qunit/qunit.js
  27. +225 −0 browserid/static/funcunit/qunit/qunit.css
  28. +1,448 −0 browserid/static/funcunit/qunit/qunit.js
  29. +53 −73 browserid/static/js/browserid.js
  30. +11 −115 browserid/tests/lib/wsapi.js
  31. +123 −0 browserid/tests/set-key-wsapi-test.js
  32. +156 −0 browserid/tests/sync-emails-wsapi-test.js
  33. +3 −0  browserid/views/layout.ejs
  34. +7 −10 browserid/views/prove.ejs
  35. 0  {browserid/lib → libs}/secrets.js
  36. +165 −0 libs/wsapi_client.js
  37. +2 −1  package.json
  38. +61 −0 performance/README.md
  39. +144 −0 performance/lib/add_email.js
  40. +55 −0 performance/lib/include_only.js
  41. +139 −0 performance/lib/reauth.js
  42. +47 −0 performance/lib/reset_pass.js
  43. +47 −0 performance/lib/signin.js
  44. +130 −0 performance/lib/signup.js
  45. +31 −0 performance/lib/test.js
  46. +134 −0 performance/lib/user_db.js
  47. +305 −0 performance/run.js
  48. +16 −0 run.js
  49. +10 −3 scripts/merge_train.sh
  50. +3 −1 verifier/app.js
18 ChangeLog
View
@@ -1,3 +1,21 @@
+train-2011.08.25:
+ * created command line load generation tool and performance analysis work: #125
+ * beginning unit/functional tests for front end: #183
+ * front end refactor to facilitate unit/functional tests and UX iteration: #183
+ * error messages are shown on front end: #184
+ * users must now verify account ownership before attempting a key sync.
+ * manage page date format: #191
+ * manage page button only displayed if user is currently authenticated: #195
+ * manage page emails are synced on page open: #181
+ * wsapi_client created for clients needing programatic access to wsapi.
+ * harden set_key against duplicate keys.
+ * fix new email addresses added not being synced on client: #199
+ * upgrade to bcrypt 0.2.4.
+ * minify include.js by default: #206
+ * more than one email address can be added per dialog lifespan: #215
+ * verifyier no longer verifies assertions issued by another server.
+ * (2011.08.31) no error message displayed if you try to authenticate with an invalid u/p: #222
+
train-2011.08.18:
* upon clickthrough of the email link, don't have the browser window close itself: #162
* passwords must be between 8 and 80 chars: #155
2  DEPLOYMENT.md
View
@@ -140,8 +140,6 @@ post update hook, annotated to help you follow along:
rm -rf /home/browserid/code.old
mv /home/browserid/code{,.old}
mv $NEWCODE /home/browserid/code
- ln -s /home/browserid/var_browserid /home/browserid/code/browserid/var
- ln -s /home/browserid/var_verifier /home/browserid/code/verifier/var
echo "fixing permissions"
find /home/browserid/code -exec chgrp www-data {} \; > /dev/null 2>&1
6 README.md
View
@@ -29,7 +29,11 @@ Here's the software you'll need installed:
## Testing
-Unit tests are under `browserid/tests/`, and you should run them often. Like before committing code.
+Unit tests can be run by invoking `test.sh` at the top level, and you
+should run them often. Like before committing code. To fully test
+the code you should install mysql and have a well permissions `test`
+user (can create and drop databases). If you don't have mysql installed,
+code testing is still possible (it just uses a little json database).
## Development model
6 browserid/app.js
View
@@ -42,7 +42,7 @@ httputils = require('./lib/httputils.js'),
webfinger = require('./lib/webfinger.js'),
sessions = require('connect-cookie-session'),
express = require('express'),
-secrets = require('./lib/secrets.js'),
+secrets = require('../libs/secrets.js'),
db = require('./lib/db.js'),
configuration = require('../libs/configuration.js'),
substitution = require('../libs/substitute.js');
@@ -252,3 +252,7 @@ exports.setup = function(server) {
// add the actual URL handlers other than static
router(server);
}
+
+exports.shutdown = function() {
+ db.close();
+};
11 browserid/compress.sh
View
@@ -12,15 +12,20 @@ if [ ! -x "$JAVA" ]; then
exit 1
fi
+YUI_LOCATION='../../static/steal/build/scripts/yui.jar'
+echo ''
+echo '****Compressing include.js****'
+echo ''
-YUI_LOCATION='../../static/steal/build/scripts/yui.jar'
+cd static
+mv include.js include.orig.js
+$UGLIFY -nc include.orig.js > include.js
echo ''
echo '****Building dialog HTML, CSS, and JS****'
echo ''
-cd static
steal/js dialog/scripts/build.js
cd dialog
@@ -33,7 +38,7 @@ echo ''
cd ../js
# re-minimize everything together
-cat jquery-1.6.2.min.js ../dialog/resources/underscore-min.js browserid.js > lib.js
+cat jquery-1.6.2.min.js ../dialog/resources/underscore-min.js ../dialog/resources/browserid-network.js ../dialog/resources/browserid-identities.js ../dialog/resources/storage.js browserid.js > lib.js
$UGLIFY < lib.js > lib.min.js
cd ../css
2  browserid/lib/db.js
View
@@ -78,7 +78,7 @@ exports.open = function(cfg, cb) {
exports.close = function(cb) {
driver.close(function(err) {
ready = false;
- cb(err);
+ if (cb) cb(err);
});
};
6 browserid/lib/db_json.js
View
@@ -41,7 +41,7 @@
const
path = require('path'),
fs = require('fs'),
-secrets = require('./secrets'),
+secrets = require('../../libs/secrets'),
jsel = require('JSONSelect'),
logger = require('../../libs/logging.js').logger,
configuration = require('../../libs/configuration.js'),
@@ -179,6 +179,10 @@ exports.addKeyToEmail = function(existing_email, email, pubkey, cb) {
}
var m = jsel.match("object:has(.address:val(" + ESC(email) + ")) > .keys", db[userID].emails);
+
+ if (jsel.match(".key:val(" + ESC(pubkey) + ")", m).length > 0) {
+ return cb("cannot set a key that is already known");
+ }
var kobj = {
key: pubkey,
58 browserid/lib/db_mysql.js
View
@@ -62,7 +62,7 @@
const
mysql = require('mysql'),
-secrets = require('./secrets'),
+secrets = require('../../libs/secrets'),
logger = require('../../libs/logging.js').logger;
var client = undefined;
@@ -282,38 +282,40 @@ exports.emailsBelongToSameAccount = function(lhs, rhs, cb) {
function addKeyToEmailRecord(emailId, pubkey, cb) {
client.query(
- // XXX: 2 weeks is wrong, but then so is keypairs.
- "INSERT INTO pubkey(email, content, expiry) VALUES(?, ?, DATE_ADD(NOW(), INTERVAL 2 WEEK))",
+ "SELECT COUNT(*) AS n FROM pubkey WHERE email = ? AND content = ?",
[ emailId, pubkey ],
- function(err, info) {
- if (err) logUnexpectedError(err);
- // smash null into undefined.
- cb(err ? err : undefined);
+ function(err, rows) {
+ if (err) {
+ logUnexpectedError(err);
+ return cb(err);
+ }
+ if (rows[0].n > 0) {
+ return cb("cannot set a key that is already known");
+ }
+
+ client.query(
+ // XXX: 2 weeks is wrong, but then so is keypairs.
+ "INSERT INTO pubkey(email, content, expiry) VALUES(?, ?, DATE_ADD(NOW(), INTERVAL 2 WEEK))",
+ [ emailId, pubkey ],
+ function(err, info) {
+ if (err) logUnexpectedError(err);
+ // smash null into undefined.
+ cb(err ? err : undefined);
+ });
});
}
exports.addKeyToEmail = function(existing_email, email, pubkey, cb) {
- // this function will NOT add a new email address to a user record. The only
- // way that happens is when a verification secret is provided to us. Limiting
- // the code paths that result in us concluding that a user owns an email address
- // is a Good Thing.
- exports.emailsBelongToSameAccount(existing_email, email, function(ok) {
- if (!ok) {
- cb("authenticated user doesn't have permission to add a public key to " + email);
- return;
- }
-
- // now we know that the user has permission to add a key.
- client.query(
- "SELECT id FROM email WHERE address = ?", [ email ],
- function(err, rows) {
- if (err) { logUnexpectedError(err); cb(err); }
- else if (rows.length === 0) cb("cannot find email address: " + email);
- else {
- addKeyToEmailRecord(rows[0].id, pubkey, cb);
- }
- });
- });
+ // now we know that the user has permission to add a key.
+ client.query(
+ "SELECT id FROM email WHERE address = ?", [ email ],
+ function(err, rows) {
+ if (err) { logUnexpectedError(err); cb(err); }
+ else if (rows.length === 0) cb("cannot find email address: " + email);
+ else {
+ addKeyToEmailRecord(rows[0].id, pubkey, cb);
+ }
+ });
}
exports.stageEmail = function(existing_email, new_email, pubkey, cb) {
78 browserid/lib/fake_verification.js
View
@@ -0,0 +1,78 @@
+/* ***** 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):
+ * Lloyd Hilaiel <lloyd@hilaiel.com>
+ *
+ * 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 little module will, when included, hook the email verification system
+ * and instead of sending emails will make verification tokens available
+ * via the WSAPI. This is *highly* insecure and should only be used when
+ * testing (performance or otherwise).
+ */
+
+const
+email = require('./email.js'),
+configuration = require('../../libs/configuration.js'),
+url = require('url');
+
+// a paranoid check of the configuration. This module should only
+// be included when in a testing environment
+var c = configuration.get('env');
+if (!/^test_/.test(c)) {
+ console.log("FATAL ERROR: you're using fake verification in a configuration that you shouldn't");
+ console.log("stop including fake_verification.js. it's not safe here.");
+ process.exit(1);
+} else {
+ console.log("HEAR YE: Fake verfication enabled, aceess via /wsapi/fake_verification?email=foo@bar.com");
+}
+
+// we store outstanding tokens in memory, folks.
+var tokens = { };
+
+// set up an interceptor
+email.setInterceptor(function(email, site, secret) {
+ tokens[email] = secret;
+});
+
+exports.addVerificationWSAPI = function(app) {
+ app.get('/wsapi/fake_verification', function(req, res) {
+ var email = url.parse(req.url, true).query['email'];
+ if (tokens.hasOwnProperty(email)) {
+ res.write(tokens[email]);
+ delete tokens[email];
+ } else {
+ res.writeHead(400, {"Content-Type": "text/plain"});
+ }
+ res.end();
+ });
+};
132 browserid/lib/wsapi.js
View
@@ -139,35 +139,49 @@ function setup(app) {
}
// bcrypt the password
- stageParams['hash'] = bcrypt.encrypt_sync(stageParams.pass, bcrypt.gen_salt_sync(10));
-
- try {
- // upon success, stage_user returns a secret (that'll get baked into a url
- // and given to the user), on failure it throws
- db.stageUser(stageParams, function(secret) {
- // store the email being registered in the session data
- if (!req.session) req.session = {};
-
- // store inside the session the details of this pending verification
- req.session.pendingVerification = {
- email: stageParams.email,
- hash: stageParams.hash // we must store both email and password to handle the case where
- // a user re-creates an account - specifically, registration status
- // must ensure the new credentials work to properly verify that
- // the user has clicked throught the email link. note, this salted, bcrypted
- // representation of a user's password will get thrust into an encrypted cookie
- // served over an encrypted (SSL) session. guten, yah.
- };
+ bcrypt.gen_salt(10, function (err, salt) {
+ if (err) {
+ winston.error("error generating salt with bcrypt: " + err);
+ return resp.json(false);
+ }
- resp.json(true);
+ bcrypt.encrypt(stageParams.pass, salt, function(err, hash) {
+ if (err) {
+ winston.error("error generating password hash with bcrypt: " + err);
+ return resp.json(false);
+ }
- // let's now kick out a verification email!
- email.sendVerificationEmail(stageParams.email, stageParams.site, secret);
+ stageParams['hash'] = hash;
+
+ try {
+ // upon success, stage_user returns a secret (that'll get baked into a url
+ // and given to the user), on failure it throws
+ db.stageUser(stageParams, function(secret) {
+ // store the email being registered in the session data
+ if (!req.session) req.session = {};
+
+ // store inside the session the details of this pending verification
+ req.session.pendingVerification = {
+ email: stageParams.email,
+ hash: stageParams.hash // we must store both email and password to handle the case where
+ // a user re-creates an account - specifically, registration status
+ // must ensure the new credentials work to properly verify that
+ // the user has clicked throught the email link. note, this salted, bcrypted
+ // representation of a user's password will get thrust into an encrypted cookie
+ // served over an encrypted (SSL) session. guten, yah.
+ };
+
+ resp.json(true);
+
+ // let's now kick out a verification email!
+ email.sendVerificationEmail(stageParams.email, stageParams.site, secret);
+ });
+ } catch(e) {
+ // we should differentiate tween' 400 and 500 here.
+ httputils.badRequest(resp, e.toString());
+ }
});
- } catch(e) {
- // we should differentiate tween' 400 and 500 here.
- httputils.badRequest(resp, e.toString());
- }
+ });
});
app.get('/wsapi/registration_status', function(req, resp) {
@@ -217,17 +231,23 @@ function setup(app) {
app.post('/wsapi/authenticate_user', checkParams(["email", "pass"]), function(req, resp) {
db.checkAuth(req.body.email, function(hash) {
- var success =
- (typeof hash === 'string' &&
- typeof req.body.pass === 'string' &&
- bcrypt.compare_sync(req.body.pass, hash));
-
- if (success) {
- if (!req.session) req.session = {};
- req.session.authenticatedUser = req.body.email;
+ if (typeof hash !== 'string' ||
+ typeof req.body.pass !== 'string')
+ {
+ return resp.json(false);
}
- resp.json(success);
+ bcrypt.compare(req.body.pass, hash, function (err, success) {
+ if (err) {
+ winston.warn("error comparing passwords with bcrypt: " + err);
+ success = false;
+ }
+ if (success) {
+ if (!req.session) req.session = {};
+ req.session.authenticatedUser = req.body.email;
+ }
+ resp.json(success);
+ });
});
});
@@ -275,14 +295,14 @@ function setup(app) {
app.post('/wsapi/set_key', checkAuthed, checkParams(["email", "pubkey"]), function (req, resp) {
db.emailsBelongToSameAccount(req.session.authenticatedUser, req.body.email, function(sameAccount) {
// not same account? big fat error
- if (!sameAccount) {
- httputils.badRequest(resp, "that email does not belong to you");
- } else {
- // same account, we add the key
- db.addKeyToEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey, function (rv) {
- resp.json(rv === undefined);
- });
- }
+ if (!sameAccount) return httputils.badRequest(resp, "that email does not belong to you");
+
+ // same account, we add the key
+ db.addKeyToEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey, function (rv) {
+ // addKeyToEmail returns errors as strings, and undefined on success.
+ if (rv) logger.warn("set_key WSAPI call failed to add key: " + rv.toString());
+ resp.json(rv === undefined);
+ });
});
});
@@ -311,9 +331,24 @@ function setup(app) {
});
app.post('/wsapi/sync_emails', checkAuthed, function(req,resp) {
- var emails = req.body.emails;
+ // validate that the post body contains an object with an .emails
+ // property that is an array of strings.
+ var valid = true;
+ try {
+ req.body.emails = JSON.parse(req.body.emails);
+ Object.keys(req.body.emails).forEach(function(k) {
+ if (typeof req.body.emails[k] !== 'string') {
+ throw "bogus value for key " + k;
+ }
+ });
+ } catch (e) {
+ logger.warn("invalid request to sync_emails: " + e);
+ return httputils.badRequest(resp, "sync_emails requires a JSON formatted 'emails' " +
+ "post argument");
+ }
- db.getSyncResponse(req.session.authenticatedUser, emails, function(err, syncResponse) {
+ logger.debug('sync emails called. client provides: ' + JSON.stringify(Object.keys(req.body.emails)));
+ db.getSyncResponse(req.session.authenticatedUser, req.body.emails, function(err, syncResponse) {
if (err) httputils.serverError(resp, err);
else resp.json(syncResponse);
});
@@ -329,6 +364,13 @@ function setup(app) {
}
});
});
+
+ // if the BROWSERID_FAKE_VERIFICATION env var is defined, we'll include
+ // fake_verification.js. This is used during testing only and should
+ // never be included in a production deployment
+ if (process.env['BROWSERID_FAKE_VERIFICATION']) {
+ require('./fake_verification.js').addVerificationWSAPI(app);
+ }
}
exports.setup = setup;
5 browserid/static/css/style.css
View
@@ -93,6 +93,11 @@ header > #manageLink {
-webkit-border-radius: 0 0 10px 10px;
-moz-border-radius: 0 0 10px 10px;
border-radius: 0 0 10px 10px;
+ display: none;
+}
+
+.authenticated header > #manageLink {
+ display: block;
}
header > #manageLink:hover {
8 browserid/static/dialog/controllers/addemail_controller.js
View
@@ -61,22 +61,18 @@
// add the actual email
// now we need to actually try to stage the creation of this account.
var email = $("#email_input").val();
- var keypair = CryptoStubs.genKeyPair();
// kick the user to waiting/status page while we talk to the server.
this.doWait(BrowserIDWait.addEmail);
var self = this;
- BrowserIDNetwork.addEmail(email, keypair, function() {
+ BrowserIDIdentities.addIdentity(email, function(keypair) {
// email successfully staged, now wait for email confirmation
self.close("addemail:complete", {
email: email,
keypair: keypair
});
- },
- function() {
- self.runErrorDialog(BrowserIDErrors.addEmail);
- });
+ }, self.getErrorDialog(BrowserIDErrors.addEmail));
}
});
17 browserid/static/dialog/controllers/authenticate_controller.js
View
@@ -1,5 +1,5 @@
-/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
-/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: true, PageController: true */
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
+/*global Channel:true, CryptoStubs:true, alert:true, errorOut:true, setupChannel:true, getEmails:true, clearEmails: true, console: true, _: true, pollTimeout: true, addEmail: true, removeEmail:true, BrowserIDNetwork: true, BrowserIDWait:true, BrowserIDErrors: true, PageController: true */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@@ -52,7 +52,7 @@
"#forgotpassword click": function(event) {
this.close("authenticate:forgotpassword");
},
-
+
"#create click": function(event) {
this.close("authenticate:createuser");
},
@@ -69,17 +69,18 @@
var pass = $("#password_input").val();
var self = this;
- BrowserIDNetwork.authenticate(email, pass, function(authenticated) {
+ BrowserIDIdentities.authenticateAndSync(email, pass, function(authenticated) {
if (authenticated) {
self.doWait(BrowserIDWait.authentication);
+ }
+ },
+ function(authenticated) {
+ if (authenticated) {
self.close("authenticate:authenticated");
} else {
self.find("#nosuchaccount").hide().fadeIn(400);
}
- }, function(resp) {
- self.runErrorDialog(BrowserIDErrors.authentication);
- self.close("cancel");
- });
+ }, self.getErrorDialog(BrowserIDErrors.authentication));
}
});
5 browserid/static/dialog/controllers/checkregistration_controller.js
View
@@ -87,10 +87,7 @@
} else {
self.runErrorDialog(BrowserIDErrors.registration);
}
- },
- function(jqXHR, textStatus, errorThrown) {
- self.runErrorDialog(BrowserIDErrors.registration);
- });
+ }, self.getErrorDialog(BrowserIDErrors.registration));
}, 3000);
}
11 browserid/static/dialog/controllers/createaccount_controller.js
View
@@ -62,23 +62,16 @@
// now we need to actually try to stage the creation of this account.
var email = this.find("#email_input").val();
var pass = this.find("#password_input").val();
- var keypair = CryptoStubs.genKeyPair();
this.doWait(BrowserIDWait.createAccount);
var self = this;
- BrowserIDNetwork.stageUser(email, pass, keypair, function() {
+ BrowserIDIdentities.stageIdentity(email, pass, function(keypair) {
self.close("createaccount:created", {
email: email,
keypair: keypair
});
- },
- function() {
- self.runErrorDialog(BrowserIDErrors.createAccount);
- }
- );
-
-
+ }, self.getErrorDialog(BrowserIDErrors.createAccount));
},
setupWatchers: function() {
87 browserid/static/dialog/controllers/dialog_controller.js
View
@@ -74,7 +74,7 @@ PageController.extend("Dialog", {}, {
var self=this, hub = OpenAjax.hub, el = this.element;
hub.subscribe("createaccount:created", function(msg, info) {
- self.doConfirmEmail(info.email, info.keypair);
+ self.doConfirmEmail(info.email);
});
hub.subscribe("createaccount:signin", function() {
@@ -114,7 +114,7 @@ PageController.extend("Dialog", {}, {
});
hub.subscribe("addemail:complete", function(msg, info) {
- self.doConfirmEmail(info.email, info.keypair);
+ self.doConfirmEmail(info.email);
});
hub.subscribe("start", function() {
@@ -163,9 +163,8 @@ PageController.extend("Dialog", {}, {
this.element.addemail();
},
- doConfirmEmail: function(email, keypair) {
+ doConfirmEmail: function(email) {
this.confirmEmail = email;
- this.confirmKeypair = keypair;
this.element.checkregistration({email: email});
},
@@ -174,26 +173,20 @@ PageController.extend("Dialog", {}, {
var self = this;
// this is a secondary registration from browserid.org, persist
// email, keypair, and that fact
- self.persistAddressAndKeyPair(self.confirmEmail,
- self.confirmKeypair, "browserid.org:443");
- self.syncIdentities();
-
+ BrowserIDIdentities.confirmIdentity(self.confirmEmail,
+ self.doSignIn.bind(self));
},
doEmailSelected: function(email) {
- var self=this,
- // yay! now we need to produce an assertion.
- storedID = getEmails()[email],
- privkey = storedID.priv,
- issuer = storedID.issuer,
- audience = BrowserIDNetwork.origin,
- assertion = CryptoStubs.createAssertion(audience, email, privkey, issuer);
-
- // Clear onerror before the call to onsuccess - the code to onsuccess
- // calls window.close, which would trigger the onerror callback if we
- // tried this afterwards.
- self.onerror = null;
- self.onsuccess(assertion);
+ var self=this;
+ // yay! now we need to produce an assertion.
+ BrowserIDIdentities.getIdentityAssertion(email, function(assertion) {
+ // Clear onerror before the call to onsuccess - the code to onsuccess
+ // calls window.close, which would trigger the onerror callback if we
+ // tried this afterwards.
+ self.onerror = null;
+ self.onsuccess(assertion);
+ });
},
doNotMe: function() {
@@ -201,64 +194,24 @@ PageController.extend("Dialog", {}, {
BrowserIDNetwork.logout(this.doAuthenticate.bind(this));
},
- persistAddressAndKeyPair: function(email, keypair, issuer) {
- var new_email_obj= {
- created: new Date(),
- pub: keypair.pub,
- priv: keypair.priv
- };
- if (issuer) {
- new_email_obj.issuer = issuer;
- }
-
- addEmail(email, new_email_obj);
- },
-
syncIdentities: function() {
- // send up all email/pubkey pairs to the server, it will response with a
- // list of emails that need new keys. This may include emails in the
- // sent list, and also may include identities registered on other devices.
- // we'll go through the list and generate new keypairs
-
- // identities that don't have an issuer are primary authentications,
- // and we don't need to worry about rekeying them.
- var emails = getEmails();
- var issued_identities = {};
- _(emails).each(function(email_obj, email_address) {
- issued_identities[email_address] = email_obj.pub;
- });
-
var self = this;
- BrowserIDNetwork.syncEmails(issued_identities,
- function onKeySyncSuccess(email, keypair) {
- self.persistAddressAndKeyPair(email, keypair, "browserid.org:443");
- },
- function onKeySyncFailure() {
- self.runErrorDialog(BrowserIDErrors.syncAddress);
- },
- function onSuccess() {
- self.doSignIn();
- },
- function onFailure(jqXHR, textStatus, errorThrown) {
- self.runErrorDialog(BrowserIDErrors.signIn);
- }
- );
-
+ BrowserIDIdentities.syncIdentities(self.doSignIn.bind(self),
+ self.getErrorDialog(BrowserIDErrors.signIn));
},
doCheckAuth: function() {
this.doWait(BrowserIDWait.checkAuth);
var self=this;
- BrowserIDNetwork.checkAuth(function(authenticated) {
+ BrowserIDIdentities.checkAuthenticationAndSync(function onSuccess() {},
+ function onComplete(authenticated) {
if (authenticated) {
- self.syncIdentities();
+ self.doSignIn();
} else {
self.doAuthenticate();
}
- }, function() {
- self.runErrorDialog(BrowserIDErrors.checkAuthentication);
- });
+ }, self.getErrorDialog(BrowserIDErrors.checkAuthentication));
}
});
9 browserid/static/dialog/controllers/forgotpassword_controller.js
View
@@ -92,21 +92,16 @@
// now we need to actually try to stage the creation of this account.
var email = this.find("#email_input").val();
var pass = this.find("#password_input").val();
- var keypair = CryptoStubs.genKeyPair();
this.doWait(BrowserIDWait.createAccount);
var self = this;
- BrowserIDNetwork.stageUser(email, pass, keypair, function() {
+ BrowserIDIdentities.stageIdentity(email, pass, function(keypair) {
self.close("createaccount:created", {
email: email,
keypair: keypair
});
- },
- function() {
- self.runErrorDialog(BrowserIDErrors.createAccount);
- }
- );
+ }, self.getErrorDialog(BrowserIDErrors.createAccount));
}
});
19 browserid/static/dialog/controllers/page_controller.js
View
@@ -105,7 +105,13 @@
}
},
- runErrorDialog: function(info) {
+ /**
+ * Immediately show the error dialog
+ * @method errorDialog
+ * @param {object} info - info to use for the error dialog. Should have
+ * two fields, message, description.
+ */
+ errorDialog: function(info) {
$("#dialog").hide();
$("#error_dialog .title").text(info.message);
@@ -119,6 +125,17 @@
$("#error_dialog").fadeIn(500);
},
+ /**
+ * Get a curried function to an error dialog.
+ * @method getErrorDialog
+ * @method {object} info - info to use for the error dialog. Should have
+ * two fields, message, description.
+ */
+ getErrorDialog: function(info) {
+ var self=this;
+ return self.errorDialog.bind(self, info);
+ },
+
onCancel: function(event) {
event.preventDefault();
event.stopPropagation();
1  browserid/static/dialog/dialog.js
View
@@ -51,6 +51,7 @@ steal.plugins(
'storage',
'browserid-extensions',
'browserid-network',
+ 'browserid-identities',
'browserid-errors',
'browserid-wait') // 3rd party script's (like jQueryUI), in resources folder
6 browserid/static/dialog/qunit.html
View
@@ -1,11 +1,11 @@
<html>
<head>
- <link rel="stylesheet" type="text/css" href="../../../../../../../funcunit/qunit/qunit.css" />
+ <link rel="stylesheet" type="text/css" href="/funcunit/qunit/qunit.css" />
<title>dialog QUnit Test</title>
<script type='text/javascript'>
steal = {ignoreControllers: true}
</script>
- <script type='text/javascript' src='../../../../../../../steal/steal.js?/web/browserid/browserid/static/dialog/dialog/test/qunit'></script>
+ <script type='text/javascript' src='/steal/steal.js?/dialog/test/qunit'></script>
</head>
<body>
@@ -17,4 +17,4 @@ <h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-test-area"></div>
</body>
-</html>
+</html>
344 browserid/static/dialog/resources/browserid-identities.js
View
@@ -0,0 +1,344 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global _: true, network: true, addEmail: true, removeEmail: true, clearEmails: true, getEmails: true, CryptoStubs: true */
+/* ***** 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 ***** */
+var BrowserIDIdentities = (function() {
+ "use strict";
+ function getIssuedIdentities() {
+ var emails = getEmails();
+ var issued_identities = {};
+ _(emails).each(function(email_obj, email_address) {
+ issued_identities[email_address] = email_obj.pub;
+ });
+
+ return issued_identities;
+ }
+
+ function removeUnknownIdentities(unknown_emails) {
+ // first remove idenitites that the server doesn't know about
+ if (unknown_emails) {
+ _(unknown_emails).each(function(email_address) {
+ removeEmail(email_address);
+ });
+ }
+ }
+
+ var network = BrowserIDNetwork;
+
+ var Identities = {
+ /**
+ * Set the interface to use for networking. Used for unit testing.
+ * @method setNetwork
+ * @param {BrowserIDNetwork} networkInterface - BrowserIDNetwork interface
+ * to use.
+ */
+ setNetwork: function(networkInterface) {
+ network = networkInterface;
+ },
+
+ /**
+ * Sync local identities with browserid.org. Generally should not need to
+ * be called.
+ * @method syncIdentities
+ * @param {function} [onSuccess] - Called whenever complete.
+ * @param {function} [onFailure] - Called on failure.
+ */
+ syncIdentities: function(onSuccess, onFailure) {
+ var issued_identities = getIssuedIdentities();
+
+ // send up all email/pubkey pairs to the server, it will response with a
+ // list of emails that need new keys. This may include emails in the
+ // sent list, and also may include identities registered on other devices.
+ // we'll go through the list and generate new keypairs
+
+ // identities that don't have an issuer are primary authentications,
+ // and we don't need to worry about rekeying them.
+
+ var self = this;
+ network.syncEmails(issued_identities, function(resp) {
+ removeUnknownIdentities(resp.unknown_emails);
+
+ // now let's begin iteratively re-keying the emails mentioned in the server provided list
+ var emailsToAdd = resp.key_refresh;
+
+ function addNextEmail() {
+ if (!emailsToAdd || !emailsToAdd.length) {
+ onSuccess();
+ return;
+ }
+
+ var email = emailsToAdd.shift();
+
+ self.syncIdentity(email, "browserid.org:443", addNextEmail, onFailure);
+ }
+
+ addNextEmail();
+ }, onFailure);
+ },
+
+ /**
+ * Stage an identity - this creates an identity that must be verified.
+ * Used when creating a new account or resetting the password of an
+ * existing account.
+ * @method stageIdentity
+ * @param {string} email - Email address.
+ * @param {function} [onSuccess] - Called on successful completion.
+ * @param {function} [onFailure] - Called on error.
+ */
+ stageIdentity: function(email, password, onSuccess, onFailure) {
+ var self=this,
+ keypair = CryptoStubs.genKeyPair();
+
+ self.stagedEmail = email;
+ self.stagedKeypair = keypair;
+
+ network.stageUser(email, password, keypair, function() {
+ if (onSuccess) {
+ onSuccess(keypair);
+ }
+ }, onFailure);
+ },
+
+ /**
+ * Signifies that an identity has been confirmed.
+ * @method confirmIdentity
+ * @param {string} email - Email address.
+ * @param {function} [onSuccess] - Called on successful completion.
+ * @param {function} [onFailure] - Called on error.
+ */
+ confirmIdentity: function(email, onSuccess, onFailure) {
+ var self = this;
+ if (email === self.stagedEmail) {
+ self.stagedEmail = null;
+ self.persistIdentity(self.stagedEmail, self.stagedKeypair, "browserid.org:443", function() {
+ self.syncIdentities(onSuccess, onFailure);
+ }, onFailure);
+ }
+ else if (onFailure) {
+ onFailure();
+ }
+ },
+
+ /**
+ * Check whether the current user is authenticated. If authenticated, sync
+ * identities.
+ * @method checkAuthenticationAndSync
+ * @param {function} [onSuccess] - Called if authentication check succeeds
+ * but before sync starts. Useful for displaying status messages about the
+ * sync taking a moment.
+ * @param {function} [onComplete] - Called on sync completion.
+ * @param {function} [onFailure] - Called on failure.
+ */
+ checkAuthenticationAndSync: function(onSuccess, onComplete, onFailure) {
+ var self=this;
+ network.checkAuth(function(authenticated) {
+ if (authenticated) {
+ if (onSuccess) {
+ onSuccess(authenticated);
+ }
+
+ self.syncIdentities(function() {
+ if (onComplete) {
+ onComplete(authenticated);
+ }
+ }, onFailure);
+ }
+ else {
+ onComplete(authenticated);
+ }
+ }, onFailure);
+ },
+
+ /**
+ * Authenticate the user with the given email and password, if
+ * authentication successful, sync addresses with server.
+ * @method authenticateAndSync
+ * @param {string} email - Email address to authenticate.
+ * @param {string} password - Password.
+ * @param {function} [onSuccess] - Called whenever authentication succeeds
+ * but before sync starts. Useful for displaying status messages about the
+ * sync taking a moment.
+ * @param {function} [onComplete] - Called on sync completion.
+ * @param {function} [onFailure] - Called on failure.
+ */
+ authenticateAndSync: function(email, password, onSuccess, onComplete, onFailure) {
+ var self=this;
+ network.authenticate(email, password, function(authenticated) {
+ if (authenticated) {
+ if (onSuccess) {
+ onSuccess(authenticated);
+ }
+
+ self.syncIdentities(function() {
+ if (onComplete) {
+ onComplete(authenticated);
+ }
+ }, onFailure);
+ } else if (onComplete) {
+ // If not authenticated, we have to complete still.
+ onComplete(authenticated);
+ }
+ }, onFailure);
+ },
+
+ /**
+ * Sync an identity with the server. Creates and stores locally and on the
+ * server a keypair for the given email address.
+ * @method syncIdentity
+ * @param {string} email - Email address.
+ * @param {string} [issuer] - Issuer of keypair.
+ * @param {function} [onSuccess] - Called on successful completion.
+ * @param {function} [onFailure] - Called on error.
+ */
+ syncIdentity: function(email, issuer, onSuccess, onFailure) {
+ var keypair = CryptoStubs.genKeyPair();
+ network.setKey(email, keypair, function() {
+ Identities.persistIdentity(email, keypair, issuer, function() {
+ if (onSuccess) {
+ onSuccess(keypair);
+ }
+ }, onFailure);
+ }, onFailure);
+ },
+
+ /**
+ * Add an identity to an already created account. Sends address and
+ * keypair to the server, user then needs to verify account ownership. This
+ * does not add the new email address/keypair to the local list of
+ * valid identities.
+ * @method addIdentity
+ * @param {string} email - Email address.
+ * @param {function} [onSuccess] - Called on successful completion.
+ * @param {function} [onFailure] - Called on error.
+ */
+ addIdentity: function(email, onSuccess, onFailure) {
+ var self=this,
+ keypair = CryptoStubs.genKeyPair();
+
+ self.stagedEmail = email;
+ self.stagedKeypair = keypair;
+
+ network.addEmail(email, keypair, function() {
+ if (onSuccess) {
+ onSuccess(keypair);
+ }
+ }, onFailure);
+ },
+
+ /**
+ * Persist an address and key pair locally.
+ * @method persistIdentity
+ * @param {string} email - Email address to persist.
+ * @param {object} keypair - Key pair to save
+ * @param {function} [onSuccess] - Called on successful completion.
+ * @param {function} [onFailure] - Called on error.
+ */
+ persistIdentity: function(email, keypair, issuer, onSuccess, onFailure) {
+ var new_email_obj= {
+ created: new Date(),
+ pub: keypair.pub,
+ priv: keypair.priv
+ };
+
+ if (issuer) {
+ new_email_obj.issuer = issuer;
+ }
+
+ addEmail(email, new_email_obj);
+
+ if (onSuccess) {
+ onSuccess();
+ }
+ },
+
+ /**
+ * Remove an email address.
+ * @method removeIdentity
+ * @param {string} email - Email address to remove.
+ * @param {function} [onSuccess] - Called when complete.
+ * @param {function} [onFailure] - Called on failure.
+ */
+ removeIdentity: function(email, onSuccess, onFailure) {
+ network.removeEmail(email, function() {
+ removeEmail(email);
+ if (onSuccess) {
+ onSuccess();
+ }
+ }, onFailure);
+ },
+
+ /**
+ * Get an assertion for an identity
+ * @method getIdentityAssertion
+ * @param {string} email - Email to get assertion for.
+ * @param {function} [onSuccess] - Called with assertion on success.
+ * @param {function} [onFailure] - Called on failure.
+ */
+ getIdentityAssertion: function(email, onSuccess, onFailure) {
+ var storedID = getEmails()[email],
+ assertion;
+
+ if (storedID) {
+ assertion = CryptoStubs.createAssertion(network.origin, email, storedID.priv, storedID.issuer);
+ }
+
+ if (onSuccess) {
+ onSuccess(assertion);
+ }
+
+ },
+
+ /**
+ * Get the list of identities stored locally.
+ * @method getStoredIdentities
+ * @return {object} identities.
+ */
+ getStoredIdentities: function() {
+ return getEmails();
+ },
+
+ /**
+ * Clear the list of identities stored locally.
+ * @method clearStoredIdentities
+ */
+ clearStoredIdentities: function() {
+ clearEmails();
+ }
+
+
+ };
+
+ return Identities;
+}());
200 browserid/static/dialog/resources/browserid-network.js
View
@@ -1,5 +1,5 @@
/*jshint browsers:true, forin: true, laxbreak: true */
-/*global _: true, console: true, addEmail: true, removeEmail: true, CryptoStubs: true */
+/*global _: true, console: true, addEmail: true, removeEmail: true, clearEmails: true, CryptoStubs: true */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@@ -34,24 +34,43 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-"use strict";
var BrowserIDNetwork = (function() {
+ "use strict";
+
var csrf_token = undefined;
+
function withCSRF(cb) {
if (csrf_token) setTimeout(cb, 0);
else {
$.get('/wsapi/csrf', {}, function(result) {
csrf_token = result;
cb();
- });
+ }, 'html');
}
}
+ function filterOrigin(origin) {
+ return origin.replace(/^.*:\/\//, '');
+ }
+
var Network = {
+ /**
+ * Set the origin of the current host being logged in to.
+ * @method setOrigin
+ * @param {string} origin
+ */
setOrigin: function(origin) {
BrowserIDNetwork.origin = filterOrigin(origin);
},
+ /**
+ * Authenticate the current user
+ * @method authenticate
+ * @param {string} email - address to authenticate
+ * @param {string} password - password.
+ * @param {function} [onSuccess] - callback to call for success
+ * @param {function} [onFailure] - called on XHR failure
+ */
authenticate: function(email, password, onSuccess, onFailure) {
withCSRF(function() {
$.ajax({
@@ -73,6 +92,13 @@ var BrowserIDNetwork = (function() {
});
},
+ /**
+ * Check whether a user is currently logged in.
+ * @method checkAuth
+ * @param {function} [onSuccess] - Success callback, called with one
+ * boolean parameter, whether the user is authenticated.
+ * @param {function} [onFailure] - called on XHR failure.
+ */
checkAuth: function(onSuccess, onFailure) {
$.ajax({
url: '/wsapi/am_authed',
@@ -85,17 +111,36 @@ var BrowserIDNetwork = (function() {
},
+ /**
+ * Log the authenticated user out
+ * @method logout
+ * @param {function} [onSuccess] - called on completion
+ */
logout: function(onSuccess) {
withCSRF(function() {
$.post("/wsapi/logout", {
csrf: csrf_token
- },
- function() {
- onSuccess();
- });
+ }, function() {
+ csrf_token = undefined;
+ withCSRF(function() {
+ if(onSuccess) {
+ onSuccess();
+ }
+ });
+ } );
});
},
+ /**
+ * Create a new user or reset a current user's password. Requires a user
+ * to verify identity.
+ * @method stageUser
+ * @param {string} email - Email address to prepare.
+ * @param {string} password - Password for user.
+ * @param {object} keypair - User's public/private key pair.
+ * @param {function} [onSuccess] - Callback to call when complete.
+ * @param {function} [onFailure] - Called on XHR failure.
+ */
stageUser: function(email, password, keypair, onSuccess, onFailure) {
withCSRF(function() {
$.ajax({
@@ -105,7 +150,7 @@ var BrowserIDNetwork = (function() {
email: email,
pass: password,
pubkey : keypair.pub,
- site : BrowserIDNetwork.origin,
+ site : BrowserIDNetwork.origin || document.location.host,
csrf : csrf_token
},
success: onSuccess,
@@ -114,6 +159,56 @@ var BrowserIDNetwork = (function() {
});
},
+ /**
+ * Call with a token to prove an email address ownership.
+ * @method proveEmailOwnership
+ * @param {string} token - token proving email ownership.
+ * @param {function} [onSuccess] - Callback to call when complete. Called
+ * with one boolean parameter that specifies the validity of the token.
+ * @param {function} [onFailure] - Called on XHR failure.
+ */
+ proveEmailOwnership: function(token, onSuccess, onFailure) {
+ $.ajax({
+ url: '/wsapi/prove_email_ownership',
+ data: {
+ token: token
+ },
+ success: function(status, textStatus, jqXHR) {
+ if (onSuccess) {
+ var valid = JSON.parse(status);
+ onSuccess(valid);
+ }
+ },
+ error: onFailure
+ });
+ },
+
+ /**
+ * Cancel the current user's account.
+ * @method cancelUser
+ * @param {function} [onSuccess] - called whenever complete.
+ */
+ cancelUser: function(onSuccess) {
+ withCSRF(function() {
+ $.post("/wsapi/account_cancel", {"csrf": csrf_token}, function(result) {
+ // XXX move this out of here, we now have
+ // BrowserIDIdentities.clearStoredIdentities
+ clearEmails();
+ if(onSuccess) {
+ onSuccess();
+ }
+ });
+ });
+ },
+
+ /**
+ * Add an email to the current user's account.
+ * @method addEmail
+ * @param {string} email - Email address to add.
+ * @param {object} keypair - Email's public/private key pair.
+ * @param {function} [onSuccess] - Called when complete.
+ * @param {function} [onFailure] - Called on XHR failure.
+ */
addEmail: function(email, keypair, onSuccess, onFailure) {
withCSRF(function() {
$.ajax({
@@ -122,7 +217,7 @@ var BrowserIDNetwork = (function() {
data: {
email: email,
pubkey: keypair.pub,
- site: BrowserIDNetwork.origin,
+ site: BrowserIDNetwork.origin || document.location.host,
csrf: csrf_token
},
success: onSuccess,
@@ -131,6 +226,15 @@ var BrowserIDNetwork = (function() {
});
},
+ /**
+ * Check whether the email is already registered.
+ * @method haveEmail
+ * @param {string} email - Email address to check.
+ * @param {function} [onSuccess] - Called with one boolean parameter when
+ * complete. Parameter is true if `email` is already registered, false
+ * otw.
+ * @param {function} [onFailure] - Called on XHR failure.
+ */
haveEmail: function(email, onSuccess, onFailure) {
$.ajax({
url: '/wsapi/have_email?email=' + encodeURIComponent(email),
@@ -144,6 +248,34 @@ var BrowserIDNetwork = (function() {
});
},
+ /**
+ * Remove an email address from the current user.
+ * @method removeEmail
+ * @param {string} email - Email address to remove.
+ * @param {function} [onSuccess] - Called whenever complete.
+ * @param {function} [onFailure] - Called on XHR failure.
+ */
+ removeEmail: function(email, onSuccess, onFailure) {
+ withCSRF(function() {
+ $.ajax({
+ type: 'POST',
+ url: '/wsapi/remove_email',
+ data: {
+ email: email,
+ csrf: csrf_token
+ },
+ success: onSuccess,
+ failure: onFailure
+ });
+ });
+ },
+
+ /**
+ * Check the current user's registration status
+ * @method checkRegistration
+ * @param {function} [onSuccess] - Called when complete.
+ * @param {function} [onFailure] - Called on XHR failure.
+ */
checkRegistration: function(onSuccess, onFailure) {
$.ajax({
url: '/wsapi/registration_status',
@@ -156,6 +288,10 @@ var BrowserIDNetwork = (function() {
});
},
+ /**
+ * Set the public key for the email address.
+ * @method setKey
+ */
setKey: function(email, keypair, onSuccess, onError) {
withCSRF(function() {
$.ajax({
@@ -172,54 +308,30 @@ var BrowserIDNetwork = (function() {
});
},
- syncEmails: function(issued_identities, onKeySyncSuccess, onKeySyncFailure, onSuccess, onFailure) {
+ /**
+ * Sync emails
+ * @method syncEmails
+ * @param {object} issued_identities - Identities to check against.
+ * @param {function} [onSuccess] - Called with response when complete.
+ * @param {function} [onFailure] - Called on XHR failure.
+ */
+ syncEmails: function(issued_identities, onSuccess, onFailure) {
withCSRF(function() {
$.ajax({
type: "POST",
url: '/wsapi/sync_emails',
data: {
- emails: issued_identities,
+ emails: JSON.stringify(issued_identities),
csrf: csrf_token
},
- success: function(resp, textStatus, jqXHR) {
- // first remove idenitites that the server doesn't know about
- if (resp.unknown_emails) {
- _(resp.unknown_emails).each(function(email_address) {
- removeEmail(email_address);
- });
- }
-
- // now let's begin iteratively re-keying the emails mentioned in the server provided list
- var emailsToAdd = resp.key_refresh;
-
- function addNextEmail() {
- if (!emailsToAdd || !emailsToAdd.length) {
- onSuccess();
- return;
- }
-
- // pop the first email from the list
- var email = emailsToAdd.shift();
- var keypair = CryptoStubs.genKeyPair();
-
- BrowserIDNetwork.setKey(email, keypair, function() {
- // update emails list and commit to local storage, then go do the next email
- onKeySyncSuccess(email, keypair);
- addNextEmail();
- }, onKeySyncFailure);
- }
-
- addNextEmail();
- },
+ success: onSuccess,
error: onFailure
});
});
}
+
};
return Network;
- function filterOrigin(origin) {
- return origin.replace(/^.*:\/\//, '');
- }
}());
260 browserid/static/dialog/test/qunit/browserid-identities_functional_test.js
View
@@ -0,0 +1,260 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, clearEmails: true, BrowserIDNetwork: true , BrowserIDIdentities: true */
+/* ***** 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 test assumes for authentication that there is a user named
+ * "testuser@testuser.com" with the password "testuser"
+ */
+steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid-identities", function() {
+ module("browserid-identities");
+
+ function failure(message) {
+ return function() {
+ ok(false, message);
+ start();
+ };
+ }
+
+ test("getStoredIdentities", function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal("object", typeof identities, "we have some identities");
+ });
+
+ test("clearStoredIdentities", function() {
+ BrowserIDIdentities.clearStoredIdentities();
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ var count = 0;
+ for(var key in identities) {
+ if(identities.hasOwnProperty(key)) {
+ count++;
+ }
+ }
+
+ equal(0, count, "after clearing, there are no identities");
+ });
+
+ test("stageIdentity", function() {
+ BrowserIDIdentities.stageIdentity("testuser@testuser.com", "testuser", function(keypair) {
+ equal("object", typeof keypair, "We have a key pair");
+ start();
+ }, failure("stageIdentity failure"));
+
+ stop();
+ });
+
+ test("confirmIdentity", function() {
+ /* BrowserIDIdentities.confirmIdentity("testuser@testuser.com", function() {
+ start();
+ });
+
+ stop();
+ */
+ });
+
+ test("authenticateAndSync", function() {
+ clearEmails();
+ BrowserIDIdentities.authenticateAndSync("testuser@testuser.com", "testuser", function() {
+ }, function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testuser@testuser.com" in identities, "authenticateAndSync syncs email addresses");
+ start();
+ }, failure("Authentication failure"));
+
+ stop();
+
+ });
+
+ test("checkAuthenticationAndSync", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ clearEmails();
+ BrowserIDIdentities.checkAuthenticationAndSync(function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testuser@testuser.com" in identities, "checkAuthenticationAndSync syncs email addresses");
+ start();
+ });
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+
+ test("addIdentity", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ BrowserIDIdentities.removeIdentity("testemail@testemail.com", function() {
+ BrowserIDIdentities.addIdentity("testemail@testemail.com", function(keypair) {
+ equal("object", typeof keypair, "we have a keypair");
+
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation.");
+
+ start();
+ }, failure("addIdentity failure"));
+ }, failure("removeIdentity failure"));
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+
+ /*
+ test("syncIdentity on confirmed email address", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ BrowserIDIdentities.removeIdentity("testemail@testemail.com", "issuer", function() {
+ // XXX verify the identity here
+ BrowserIDIdentities.syncIdentity("testemail@testemail.com", "issuer", function(keypair) {
+ ok(false, "Syncing a non-verified identity should fail");
+
+ start();
+ }, failure("syncIdentity failure"));
+ }, failure("removeIdentity failure"));
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+*/
+
+ test("persistIdentity", function() {
+ BrowserIDIdentities.persistIdentity("testemail2@testemail.com", { pub: "pub", priv: "priv" });
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testemail2@testemail.com" in identities, "Our new email is added");
+ });
+
+ /*
+ test("removeIdentity that we add", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ BrowserIDIdentities.syncIdentity("testemail@testemail.com", "issuer", function(keypair) {
+ BrowserIDIdentities.removeIdentity("testemail@testemail.com", function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal(false, "testemail@testemail.com" in identities, "Our new email is removed");
+ start();
+ }, failure("removeIdentity failure"));
+ }, failure("syncIdentity failure"));
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+ */
+ test("syncIdentities with no identities", function() {
+ clearEmails();
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+ ok(true, "we have synced identities");
+ start();
+ }, failure("identity sync failure"));
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+
+ test("syncIdentities with identities preloaded", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+ ok(true, "we have synced identities");
+ start();
+ }, failure("identity sync failure"));
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+
+ test("getIdentityAssertion", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ BrowserIDIdentities.getIdentityAssertion("testuser@testuser.com", function(assertion) {
+ equal("string", typeof assertion, "we have an assertion!");
+ start();
+ });
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+
+ /*
+ test("syncIdentity on non-confirmed email address", function() {
+ clearEmails();
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ BrowserIDIdentities.removeIdentity("testemail@testemail.com", function() {
+ BrowserIDIdentities.syncIdentity("testemail@testemail.com", "issuer", function(keypair) {
+ ok(false, "Syncing a non-verified identity should fail");
+
+ start();
+ }, function() {
+ ok(true, "trying to sync an identity that is not yet verified should fail");
+
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal("testemail@testemail.com" in identities, false, "Our new email is added");
+
+ start();
+ });
+ }, failure("removeIdentity failure"));
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+
+ test("syncIdentity without first validating email", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+ // First, force removal that way we know it is not part of our list.
+ BrowserIDIdentities.removeIdentity("unvalidated@unvalidated.com", function() {
+
+ clearEmails();
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ // Make sure the server has forgotten about this email address.
+ equal("unvalidated@unvalidated.com" in identities, false, "The removed email should not be on the list.");
+
+ // This next call will call /wsapi/set_key on a
+ // key that has not been validated.
+ BrowserIDIdentities.syncIdentity("unvalidated@unvalidated.com", "issuer", function(keypair) {
+ // Clear all the local emails, then refetch the list from the server
+ // just to be sure we are seeing what the server sees.
+ clearEmails();
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ // woah. Things just went wrong.
+ equal("unvalidated@unvalidated.com" in identities, false, "The unvalidated email should not be added just through calling sync_key");
+ start();
+ }, failure("syncIdentities failure"));
+ }, function() {
+ ok(true, "We expect syncIdentity to fail on an address we cannot confirm");
+ });
+ }, failure("syncIdentities failure"));
+ }, failure("removeEmail failure"));
+ }, failure("Authentication failure"));
+
+ stop();
+ });
+*/
+});
460 browserid/static/dialog/test/qunit/browserid-identities_unit_test.js
View
@@ -0,0 +1,460 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, clearEmails: true, BrowserIDNetwork: true , BrowserIDIdentities: true */
+/* ***** 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 test assumes for authentication that there is a user named
+ * "testuser@testuser.com" with the password "testuser"
+ */
+steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid-identities", function() {
+ var credentialsValid, unknownEmails, keyRefresh, syncValid;
+ var netStub = {
+ reset: function() {
+ credentialsValid = syncValid = true;
+ unknownEmails = [];
+ keyRefresh = [];
+ },
+
+ stageUser: function(email, password, keypair, onSuccess) {
+ onSuccess();
+ },
+
+ authenticate: function(email, password, onSuccess, onFailure) {
+ onSuccess(credentialsValid);
+ },
+
+ checkAuth: function(onSuccess, onFailure) {
+ onSuccess(credentialsValid);
+ },
+
+ addEmail: function(email, keypair, onSuccess, onFailure) {
+ onSuccess();
+ },
+
+ removeEmail: function(email, onSuccess, onFailure) {
+ onSuccess();
+ },
+
+ syncEmails: function(issued_identities, onSuccess, onFailure) {
+ onSuccess({
+ unknown_emails: unknownEmails,
+ key_refresh: keyRefresh
+ });
+ },
+
+ setKey: function(email, keypair, onSuccess, onFailure) {
+ if (syncValid) {
+ onSuccess();
+ }
+ else {
+ onFailure();
+ }
+ }
+ };
+
+ module("browserid-identities-unit", {
+
+ setup: function() {
+ BrowserIDIdentities.setNetwork(netStub);
+ netStub.reset();
+ },
+ teardown: function() {
+ BrowserIDIdentities.setNetwork(BrowserIDNetwork);
+ }
+ });
+
+ function failure(message) {
+ return function() {
+ ok(false, message);
+ start();
+ };
+ }
+
+ test("getStoredIdentities", function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal("object", typeof identities, "we have some identities");
+ });
+
+ test("clearStoredIdentities", function() {
+ BrowserIDIdentities.clearStoredIdentities();
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ var count = 0;
+ for(var key in identities) {
+ if(identities.hasOwnProperty(key)) {
+ count++;
+ }
+ }
+
+ equal(0, count, "after clearing, there are no identities");
+ });
+
+ test("stageIdentity", function() {
+ BrowserIDIdentities.stageIdentity("testuser@testuser.com", "testuser", function(keypair) {
+ equal("object", typeof keypair, "We have a key pair");
+ start();
+ }, failure("stageIdentity failure"));
+
+ stop();
+ });
+
+
+ test("confirmIdentity on staged identity", function() {
+ BrowserIDIdentities.stageIdentity("testuser@testuser.com", "testuser", function(keypair) {
+ BrowserIDIdentities.confirmIdentity("testuser@testuser.com", function() {
+ ok(true, "confirming staged identity");
+ start();
+ });
+ }, failure("stageIdentity failure"));
+
+ stop();
+ });
+
+
+ test("confirmIdentity on non staged identity", function() {
+ BrowserIDIdentities.stageIdentity("testuser@testuser.com", "testuser", function(keypair) {
+ BrowserIDIdentities.confirmIdentity("testuser2@testuser.com", function onSuccess() {
+ ok(false, "confirming unstaged identity");
+ start();
+ }, function onFailure() {
+ ok(true, "confirming unstaged identity should fail");
+ start();
+ });
+ }, failure("stageIdentity failure"));
+
+ stop();
+ });
+
+
+ test("confirmIdentity on previously confirmed identity", function() {
+ BrowserIDIdentities.stageIdentity("testuser@testuser.com", "testuser", function(keypair) {
+ BrowserIDIdentities.confirmIdentity("testuser@testuser.com", function() {
+ BrowserIDIdentities.confirmIdentity("testuser@testuser.com", function() {
+ ok(false, "confirming previously confirmed identity should fail");
+ start();
+ }, function onFailure() {
+ ok(true, "confirming previously confirmed identity should fail");
+ start();
+ });
+ });
+ }, failure("stageIdentity failure"));
+
+ stop();
+ });
+
+
+
+ test("authenticateAndSync with valid credentials", function() {
+ BrowserIDIdentities.authenticateAndSync("testuser@testuser.com", "testuser", function() {
+ }, function(authenticated) {
+ equal(true, authenticated, "we are authenticated!");
+ start();
+ }, failure("Authentication failure"));
+
+ stop();
+
+ });
+
+
+
+ test("authenticateAndSync with invalid credentials", function() {
+ credentialsValid = false;
+ BrowserIDIdentities.authenticateAndSync("testuser@testuser.com", "testuser", function onSuccess(authenticated) {
+ ok(false, "This should not be called on authentication failure");
+ }, function onComplete(authenticated) {
+ equal(false, authenticated, "invalid authentication.");
+ start();
+ }, failure("Authentication failure"));
+
+ stop();
+
+ });
+
+
+
+ test("checkAuthenticationAndSync with valid authentication", function() {
+ credentialsValid = true;
+ BrowserIDIdentities.checkAuthenticationAndSync(function onSuccess() {},
+ function onComplete(authenticated) {
+ ok(authenticated, true, "We are authenticated!");
+ start();
+ });
+
+ stop();
+ });
+
+
+
+ test("checkAuthenticationAndSync with invalid authentication", function() {
+ credentialsValid = false;
+ BrowserIDIdentities.checkAuthenticationAndSync(function onSuccess() {
+ ok(false, "We are not authenticated!");
+ start();
+ }, function onComplete(authenticated) {
+ equal(authenticated, false, "We are not authenticated!");
+ start();
+ });
+
+ stop();
+ });
+
+
+ test("authenticateAndSync with valid authentication", function() {
+ credentialsValid = true;
+ keyRefresh = ["testuser@testuser.com"];
+ clearEmails();
+
+ BrowserIDIdentities.authenticateAndSync("testuser@testuser.com", "testuser", function() {
+ }, function(authenticated) {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testuser@testuser.com" in identities, "authenticateAndSync syncs email addresses");
+ ok(authenticated, "we are authenticated")
+ start();
+ });
+
+ stop();
+ });
+
+
+
+ test("authenticateAndSync with invalid authentication", function() {
+ credentialsValid = false;
+ keyRefresh = ["testuser@testuser.com"];
+ clearEmails();
+
+ BrowserIDIdentities.authenticateAndSync("testuser@testuser.com", "testuser", function() {
+ }, function(authenticated) {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal("testuser@testuser.com" in identities, false, "authenticateAndSync does not sync if authentication is invalid");
+ equal(authenticated, false, "not authenticated");
+ start();
+ });
+
+ stop();
+ });
+
+
+
+ test("addIdentity", function() {
+ BrowserIDIdentities.addIdentity("testemail@testemail.com", function(keypair) {
+ equal("object", typeof keypair, "we have a keypair");
+
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation.");
+
+ start();
+ }, failure("addIdentity failure"));
+
+ stop();
+ });
+
+
+
+ test("syncIdentity with successful sync", function() {
+ clearEmails();
+
+ syncValid = true;
+ BrowserIDIdentities.syncIdentity("testemail@testemail.com", "issuer", function(keypair) {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testemail@testemail.com" in identities, "Valid email is synced");
+
+ start();
+ }, failure("syncIdentity failure"));
+
+ stop();
+ });
+
+
+ test("syncIdentity with invalid sync", function() {
+ clearEmails();
+
+ syncValid = false;
+ BrowserIDIdentities.syncIdentity("testemail@testemail.com", "issuer", function(keypair) {
+ ok(false, "sync was invalid, this should have failed");
+ start();
+ }, function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal("testemail@testemail.com" in identities, false, "Invalid email is not synced");
+
+ start();
+ });
+
+ stop();
+ });
+
+
+
+ test("persistIdentity", function() {
+ BrowserIDIdentities.persistIdentity("testemail2@testemail.com", { pub: "pub", priv: "priv" }, undefined, function onSuccess() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testemail2@testemail.com" in identities, "Our new email is added");
+ start();
+ });
+
+ stop();
+ });
+
+
+
+ test("removeIdentity that is added", function() {
+ addEmail("testemail@testemail.com", {pub: "pub", priv: "priv"});
+
+ BrowserIDIdentities.removeIdentity("testemail@testemail.com", function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal(false, "testemail@testemail.com" in identities, "Our new email is removed");
+ start();
+ }, failure("removeIdentity failure"));
+
+ stop();
+ });
+
+
+
+ test("removeIdentity that is not added", function() {
+ clearEmails();
+
+ BrowserIDIdentities.removeIdentity("testemail@testemail.com", function() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ equal(false, "testemail@testemail.com" in identities, "Our new email is removed");
+ start();
+ }, failure("removeIdentity failure"));
+
+ stop();
+ });
+
+
+
+ test("syncIdentities with no pre-loaded identities and no identities to add", function() {
+ clearEmails();
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok(true, "we have synced identities");
+ equal(_.size(identities), 0, "there are no identities");
+ start();
+ }, failure("identity sync failure"));
+
+ stop();
+ });
+
+ test("syncIdentities with no pre-loaded identities and identities to add", function() {
+ clearEmails();
+ keyRefresh = ["testuser@testuser.com"];
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testuser@testuser.com" in identities, "Our new email is added");
+ equal(_.size(identities), 1, "there is one identity");
+ start();
+ }, failure("identity sync failure"));
+
+ stop();
+ });
+
+ test("syncIdentities with identities preloaded and none to add", function() {
+ clearEmails();
+ addEmail("testuser@testuser.com", {});
+
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testuser@testuser.com" in identities, "Our new email is added");
+ equal(_.size(identities), 1, "there is one identity");
+ start();
+ }, failure("identity sync failure"));
+
+ stop();
+ });
+
+
+ test("syncIdentities with identities preloaded and one to add", function() {
+ clearEmails();
+ addEmail("testuser@testuser.com", {});
+ keyRefresh = ["testuser2@testuser.com"];
+
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testuser@testuser.com" in identities, "Our old email address is still there");
+ ok("testuser2@testuser.com" in identities, "Our new email is added");
+ equal(_.size(identities), 2, "there are two identities");
+ start();
+ }, failure("identity sync failure"));
+
+ stop();
+ });
+
+
+ test("syncIdentities with identities preloaded and one to remove", function() {
+ clearEmails();
+ addEmail("testuser@testuser.com", {});
+ addEmail("testuser2@testuser.com", {});
+ unknownEmails = ["testuser2@testuser.com"];
+
+ BrowserIDIdentities.syncIdentities(function onSuccess() {
+ var identities = BrowserIDIdentities.getStoredIdentities();
+ ok("testuser@testuser.com" in identities, "Our old email address is still there");
+ equal("testuser2@testuser.com" in identities, false, "Our unknown email is removed");
+ equal(_.size(identities), 1, "there is one identity");
+ start();
+ }, failure("identity sync failure"));
+
+ stop();
+ });
+
+
+ test("getIdentityAssertion with known email", function() {
+ clearEmails();
+ var keypair = CryptoStubs.genKeyPair();
+ addEmail("testuser@testuser.com", { priv: keypair.priv, issuer: "issuer" });
+
+ BrowserIDIdentities.getIdentityAssertion("testuser@testuser.com", function onSuccess(assertion) {
+ equal("string", typeof assertion, "we have an assertion!");
+ start();
+ });
+
+ stop();
+ });
+
+
+ test("getIdentityAssertion with unknown email", function() {
+ clearEmails();
+ var keypair = CryptoStubs.genKeyPair();
+ addEmail("testuser@testuser.com", { priv: keypair.priv, issuer: "issuer" });
+
+ BrowserIDIdentities.getIdentityAssertion("testuser2@testuser.com", function onSuccess(assertion) {
+ equal("undefined", typeof assertion, "email was unknown, we do not have an assertion");
+ start();
+ });
+
+ stop();
+ });
+
+});
150 browserid/static/dialog/test/qunit/browserid-network_test.js
View
@@ -0,0 +1,150 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserIDNetwork: true */
+/* ***** 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 test assumes for authentication that there is a user named
+ * "testuser@testuser.com" with the password "testuser"
+ */
+steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid-network", function() {
+ module("browserid-network");
+
+ test("setOrigin", function() {
+ BrowserIDNetwork.setOrigin("https://www.mozilla.com");
+
+ equal("www.mozilla.com", BrowserIDNetwork.origin, "origin's are properly filtered");
+ });
+
+
+ test("authenticate with valid user", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function onSuccess(authenticated) {
+ start();
+ equal(true, authenticated, "valid authentication");
+ }, function onFailure() {
+ start();
+ ok(false, "valid authentication");
+ });
+
+ stop();
+ });
+
+ test("authenticate with invalid user", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "invalid", function onSuccess(authenticated) {
+ start();
+ equal(false, authenticated, "invalid authentication");
+ }, function onFailure() {
+ start();
+ ok(false, "invalid authentication");
+ });
+
+ stop();
+ });
+
+ test("checkAuth", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function onSuccess(authenticated) {
+ BrowserIDNetwork.checkAuth(function onSuccess(authenticated) {
+ start();
+ equal(true, authenticated, "we have an authentication");
+ }, function onFailure() {
+ start();
+ ok(false, "checkAuth failure");
+ });
+ }, function onFailure() {
+ start();
+ ok(false, "valid authentication");
+ });
+
+ stop();
+ });
+
+ test("logout->checkAuth: are we really logged out?", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function onSuccess(authenticated) {
+ BrowserIDNetwork.logout(function onSuccess(authenticated) {
+ BrowserIDNetwork.checkAuth(function onSuccess(authenticated) {
+ start();
+ equal(false, authenticated, "after logout, we are not authenticated");
+ }, function onFailure() {
+ start();
+ ok(false, "checkAuth failure");
+ });
+ });
+ });
+
+ stop();
+ });
+
+
+ test("prove_email_ownership with valid login cookie but invalid token", function() {
+ BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function onSuccess(authenticated) {
+ var token = "badtoken";
+ BrowserIDNetwork.proveEmailOwnership(token, function onSuccess(proven) {
+ equal(proven, false, "bad token could not be proved");
+ start();
+ }, function onFailure() {
+ start();
+ });
+ });
+
+ stop();
+ });
+
+ test("prove_email_ownership without valid login cookie", function() {
+
+ });
+
+ test("stageUser", function() {
+ ok(true, "stageUser");
+ });
+
+ test("haveEmail", function() {
+ ok(true, "haveEmail");
+ });
+
+ test("removeEmail", function() {
+ ok(true, "removeEmail");
+ });
+
+ test("checkRegistration", function() {
+ ok(true, "checkRegistration");
+ });
+
+ test("setKey", function() {
+ ok(true, "setKey");
+ });
+
+ test("syncEmails", function() {
+ ok(true, "syncEmails");
+ });
+});
11 browserid/static/dialog/test/qunit/qunit.js
View
@@ -1,3 +1,8 @@
-steal
- .plugins("funcunit/qunit", "/web/browserid/browserid/static/dialog/dialog")
- .then("dialog_test");
+steal("/dialog/resources/storage.js",
+ "/dialog/resources/underscore-min.js",
+ "/dialog/resources/crypto-api.js",
+ "/dialog/resources/crypto.js")
+ .plugins("funcunit/qunit")
+ .then("browserid-network_test")
+ .then("browserid-identities_unit_test")
+// .then("browserid-identities_functional_test");
225 browserid/static/funcunit/qunit/qunit.css
View
@@ -0,0 +1,225 @@
+/**
+ * QUnit - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {