Permalink
Browse files

integrating train 2011.10.13

Conflicts:
	ChangeLog
	browserid/static/css/m.css
	browserid/static/dialog/dialog.js
  • Loading branch information...
2 parents 20c0f26 + c9b7bdb commit 03e2ab0fe59543782bf32e109d0829619284ed9a @lloyd lloyd committed Oct 20, 2011
Showing with 1,328 additions and 955 deletions.
  1. +21 −3 ChangeLog
  2. +13 −0 browserid/app.js
  3. +1 −1 browserid/compress.sh
  4. +2 −1 browserid/lib/db.js
  5. +8 −0 browserid/lib/db_json.js
  6. +10 −0 browserid/lib/db_mysql.js
  7. +14 −0 browserid/lib/email.js
  8. +66 −42 browserid/lib/wsapi.js
  9. +11 −0 browserid/static/css/m.css
  10. +18 −9 browserid/static/dialog/controllers/authenticate_controller.js
  11. +4 −4 browserid/static/dialog/controllers/checkregistration_controller.js
  12. +173 −162 browserid/static/dialog/controllers/dialog_controller.js
  13. +7 −7 browserid/static/dialog/controllers/page_controller.js
  14. +56 −25 browserid/static/dialog/controllers/pickemail_controller.js
  15. +14 −11 browserid/static/dialog/css/popup.css
  16. +4 −4 browserid/static/dialog/dialog.js
  17. 0 browserid/static/dialog/resources/{browserid-errors.js → error-messages.js}
  18. +21 −12 browserid/static/dialog/resources/{browserid-network.js → network.js}
  19. +95 −7 browserid/static/dialog/resources/storage.js
  20. +112 −117 browserid/static/dialog/resources/{browserid-identities.js → user.js}
  21. 0 browserid/static/dialog/resources/{browserid-wait.js → wait-messages.js}
  22. +0 −260 browserid/static/dialog/test/qunit/browserid-identities_functional_test.js
  23. +25 −27 browserid/static/dialog/test/qunit/{browserid-network_test.js → network_unit_test.js}
  24. +2 −2 browserid/static/dialog/test/qunit/qunit.js
  25. +76 −9 browserid/static/dialog/test/qunit/storage_unit_test.js
  26. +117 −157 browserid/static/dialog/test/qunit/{browserid-identities_unit_test.js → user_unit_test.js}
  27. +1 −1 browserid/static/dialog/test/qunit/validation_unit_test.js
  28. +1 −1 browserid/static/dialog/views/authenticate.ejs
  29. +4 −10 browserid/static/dialog/views/confirmemail.ejs
  30. +2 −2 browserid/static/dialog/views/pickemail.ejs
  31. +33 −8 browserid/static/include.js
  32. +3 −3 browserid/static/js/browserid.js
  33. +1 −1 browserid/static/js/pages/forgot.js
  34. +28 −14 browserid/static/js/pages/manage_account.js
  35. +3 −3 browserid/static/js/pages/signup.js
  36. +1 −1 browserid/tests/cert-emails-test.js
  37. +104 −0 browserid/tests/cookie-session-security-test.js
  38. +15 −15 browserid/tests/forgotten-email-test.js
  39. +4 −0 browserid/tests/lib/wsapi.js
  40. +2 −2 browserid/tests/list-emails-wsapi-test.js
  41. +180 −0 browserid/tests/password-bcrypt-update-test.js
  42. +7 −7 browserid/tests/registration-status-wsapi-test.js
  43. +10 −9 browserid/views/dialog.ejs
  44. +1 −1 browserid/views/index.ejs
  45. +3 −3 browserid/views/layout.ejs
  46. +22 −3 libs/configuration.js
  47. +4 −1 libs/metrics.js
  48. +2 −0 libs/wsapi_client.js
  49. +1 −1 package.json
  50. +26 −9 scripts/assign_issues.js
View
24 ChangeLog
@@ -1,14 +1,32 @@
+train-2011.10.13:
+ * fix verification of email in different browser than where verification is initiated: #336
+ * Android < 3.0 (browsers that can't handle JSON.parse("null")) now blocked explicitly (until we complete support)
+ * textual fixes to about page: #350
+ * 'cancel account' link added to manage page: #405
+ * warn user that removing last email address effectively cancels account: #394, #404, #137
+ * fixed signing dialog hang when you delete an email on manage page while dialog is open (now that's not obscure :P): #401
+ * Optimize UI in case where user has only 1 email address: #412
+ * smooth out transition from pick email to add new email pages: #410
+ * reposition remove buttons on manage page: #409
+ * identity and labs links open in new tabs: #380
+ * fix innocuous (but ugly) error in firefox error console: #390
+ * implement dynamic bcrypt work factor update: #204
+ * default work factor is now at 12 (NOTE: [re]authentication now takes 6x longer - ~600ms on our current hardware): #212
+ * many test fixes, and code refactoring, cleanup, and reorganization
+ * accept SMTP parameters from the environment: #214 (not yet closed)
+ * WSAPI CHANGES (https://github.com/mozilla/browserid/commit/511b56): all server responses are now objects: #217, #325
+
train-2011.10.06:
* full site & dialog redesign: (many, many closed issues are related to this, including #269, #343, #342, #347, #354, #356, #357, #350, #349, #364, #346, #336)
* improved debugging, all network callbacks are invoked asynchronously: #276
- * MYSQL SCHEMA CHANGE: passwd field no longer in staged table (password is now set after verfiy link clickthrough)
+ * MYSQL SCHEMA CHANGE: passwd field no longer in staged table (password is now set after verify link clickthrough)
* MYSQL SCHEMA CHANGE: add index to emails table: #209
* WSAPI CHANGES (to support new UI): https://github.com/mozilla/browserid/commit/b6ee51
* WSAPI CHANGES: a mis-set client clock no longer causes invalid assertions to be issued (wsapi changed to minimize network requests): #329
* disallow re-registration of existing account: #333
* (non-visible) namespacing in dialog code: #275
* API BREAKING CHANGE: verifier no longer supports GET requests: #98
- * significant performance / UX improvment - keys are generated and certified when needed, not all upfront at sign-in: #278
+ * significant performance / UX improvement - keys are generated and certified when needed, not all upfront at sign-in: #278
* remove 'download printable format' language from privacy policy: #280
* faster keygen via crypto optimizations: https://github.com/mozilla/browserid/commit/778433
* improvements to mobile layout & usability (specific to the new UI)
@@ -33,7 +51,7 @@ train-2011.09.22:
* improved first-time development experience: `git clone && cd browserid && npm install && npm run`
* initial support for running locally under virtualbox via vagrant: issue #261 (thanks ozten!)
* (fix 2011.09.23) fix race condition between relay iframe and window introduced with IE9 support. issue #287
- * (fix 2011.09.23) fix blank popup on second sigin invocation in same session in FFX: issue #286
+ * (fix 2011.09.23) fix blank popup on second signin invocation in same session in FFX: issue #286
* (fix 2011.09.23) explicitly disable caching for /wsapi calls, prevents unwanted caching of CSRF and friends. issue #294
train-2011.09.01:
View
13 browserid/app.js
@@ -219,6 +219,19 @@ exports.setup = function(server) {
}
});
+ // verify all JSON responses are objects - prevents regression on issue #217
+ server.use(function(req, resp, next) {
+ var realRespJSON = resp.json;
+ resp.json = function(obj) {
+ if (!obj || typeof obj !== 'object') {
+ logger.error("INTERNAL ERROR! *all* json responses must be objects");
+ throw "internal error";
+ }
+ realRespJSON.call(resp, obj);
+ };
+ return next();
+ });
+
server.use(express.bodyParser());
// Check CSRF token early. POST requests are only allowed to
View
2 browserid/compress.sh
@@ -48,7 +48,7 @@ echo ''
cd ../js
# re-minimize everything together
-cat jquery-1.6.2.min.js json2.js browserid.js ../dialog/resources/underscore-min.js ../dialog/resources/browserid-extensions.js ../dialog/resources/storage.js ../dialog/resources/browserid-network.js ../dialog/resources/browserid-identities.js ../dialog/resources/tooltip.js ../dialog/resources/validation.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/manage_account.js pages/signin.js pages/signup.js pages/forgot.js > lib.js
+cat jquery-1.6.2.min.js json2.js browserid.js ../dialog/resources/underscore-min.js ../dialog/resources/browserid-extensions.js ../dialog/resources/storage.js ../dialog/resources/network.js ../dialog/resources/user.js ../dialog/resources/tooltip.js ../dialog/resources/validation.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/manage_account.js pages/signin.js pages/signup.js pages/forgot.js > lib.js
$UGLIFY < lib.js > lib.min.js
cd ../css
View
3 browserid/lib/db.js
@@ -103,7 +103,8 @@ exports.onReady = function(f) {
'checkAuth',
'listEmails',
'removeEmail',
- 'cancelAccount'
+ 'cancelAccount',
+ 'updatePassword'
].forEach(function(fn) {
exports[fn] = function() {
checkReady();
View
8 browserid/lib/db_json.js
@@ -238,6 +238,14 @@ exports.checkAuth = function(email, cb) {
setTimeout(function() { cb(m) }, 0);
};
+exports.updatePassword = function(email, hash, cb) {
+ var m = jsel.match(":root > object:has(.emails > :val(" + ESC(email) + "))", db);
+ var err = undefined;
+ if (m.length === 0) err = "no such email address";
+ else m[0].password = hash;
+ setTimeout(function() { cb(err) }, 0);
+};
+
function emailToUserID(email, cb) {
var id = undefined;
View
10 browserid/lib/db_mysql.js
@@ -306,6 +306,16 @@ exports.checkAuth = function(email, cb) {
});
}
+exports.updatePassword = function(email, hash, cb) {
+ client.query(
+ 'UPDATE user SET passwd = ? WHERE id = ( SELECT user FROM email WHERE address = ? )',
+ [ hash, email ],
+ function (err, rows) {
+ if (err) logUnexpectedError(err);
+ cb((err || rows.affectedRows !== 1) ? ("no record with email " + email) : undefined);
+ });
+}
+
/*
* list the user's emails.
*
View
14 browserid/lib/email.js
@@ -42,6 +42,20 @@ mustache = require('mustache'),
config = require('../../libs/configuration.js'),
logger = require('../../libs/logging.js').logger;
+/* if smtp parameters are configured, use them */
+var smtp_params = config.get('smtp');
+console.log("SMTP", smtp_params);
+if (smtp_params && smtp_params.host) {
+ emailer.SMTP = { host: smtp_params.host };
+ logger.info("delivering email via SMTP host: " + emailer.SMTP.host);
+ if (smtp_params.user) {
+ logger.info("authenticating to email host as" + emailer.SMTP.user);
+ emailer.SMTP.use_authentication = true;
+ emailer.SMTP.user = smtp_params.user;
+ emailer.SMTP.pass = smtp_params.pass;
+ }
+}
+
const template = fs.readFileSync(path.join(__dirname, "prove_template.txt")).toString();
var interceptor = undefined;
View
108 browserid/lib/wsapi.js
@@ -132,12 +132,11 @@ function setup(app) {
var auth_status = false;
function sendResponse() {
- res.write(JSON.stringify({
+ res.json({
csrf_token: req.session.csrf,
server_time: (new Date()).getTime(),
authenticated: auth_status
- }));
- res.end();
+ });
};
// if they're authenticated for an email address that we don't know about,
@@ -165,7 +164,7 @@ function setup(app) {
// get inputs from get data!
var email = url.parse(req.url, true).query['email'];
db.emailKnown(email, function(known) {
- resp.json(known);
+ resp.json({ email_known: known });
});
});
@@ -190,7 +189,7 @@ function setup(app) {
// status of the email verification.
req.session.pendingCreation = secret;
- resp.json(true);
+ resp.json({ success: true });
// let's now kick out a verification email!
email.sendNewUserEmail(req.body.email, req.body.site, secret);
@@ -211,28 +210,48 @@ function setup(app) {
// if the user is authenticated as the user in question, we're done
if (isAuthed(req) && req.session.authenticatedUser === email) {
- return resp.json('complete');
+ return resp.json({ status: 'complete' });
}
// if the user isn't authenticated and there's no pendingCreation token,
// then they must authenticate
else if (!req.session.pendingCreation) {
- return resp.json('mustAuth');
+ return resp.json({ status: 'mustAuth' });
}
// if the secret is still in the database, it hasn't yet been verified and
// verification is still pending
db.emailForVerificationSecret(req.session.pendingCreation, function (email) {
- if (email) return resp.json('pending');
+ if (email) return resp.json({ status: 'pending' });
// if the secret isn't known, and we're not authenticated, then the user must authenticate
// (maybe they verified the URL on a different browser, or maybe they canceled the account
// creation)
else {
delete req.session.pendingCreation;
- resp.json('mustAuth');
+ resp.json({ status: 'mustAuth' });
}
});
});
+ function bcrypt_password(password, cb) {
+ var bcryptWorkFactor = configuration.get('bcrypt_work_factor');
+
+ bcrypt.gen_salt(bcryptWorkFactor, function (err, salt) {
+ if (err) {
+ var msg = "error generating salt with bcrypt: " + err;
+ logger.error(msg);
+ return cb(msg);
+ }
+ bcrypt.encrypt(password, salt, function(err, hash) {
+ if (err) {
+ var msg = "error generating password hash with bcrypt: " + err;
+ logger.error(msg);
+ return cb(msg);
+ }
+ return cb(undefined, hash);
+ });
+ });
+ };
+
app.post('/wsapi/complete_user_creation', checkParams(["token", "pass"]), function(req, resp) {
// issue #155, valid password length is between 8 and 80 chars.
if (req.body.pass.length < 8 || req.body.pass.length > 80) {
@@ -248,32 +267,26 @@ function setup(app) {
// bcrypting the password (which is expensive), to prevent a possible
// DoS attack.
db.emailForVerificationSecret(req.body.token, function(email) {
- if (!email) return resp.json(false);
+ if (!email) return resp.json({ success: false} );
// now bcrypt the password
- bcrypt.gen_salt(10, function (err, salt) {
+ bcrypt_password(req.body.pass, function (err, hash) {
if (err) {
- logger.error("error generating salt with bcrypt: " + err);
- return resp.json(false);
+ logger.error("can't bcrypt: " + err);
+ return resp.json({ success: false });
}
- bcrypt.encrypt(req.body.pass, salt, function(err, hash) {
+
+ db.gotVerificationSecret(req.body.token, hash, function(err, email) {
if (err) {
- logger.error("error generating password hash with bcrypt: " + err);
- return resp.json(false);
+ logger.error("error completing the verification: " + err);
+ resp.json({ success: false });
+ } else {
+ // FIXME: not sure if we want to do this (ba)
+ // at this point the user has set a password associated with an email address
+ // that they've verified. We create an authenticated session.
+ setAuthenticatedUser(req.session, email);
+ resp.json({ success: true });
}
-
- db.gotVerificationSecret(req.body.token, hash, function(err, email) {
- if (err) {
- logger.error("error completing the verification: " + err);
- resp.json(false);
- } else {
- // FIXME: not sure if we want to do this (ba)
- // at this point the user has set a password associated with an email address
- // that they've verified. We create an authenticated session.
- setAuthenticatedUser(req.session, email);
- resp.json(true);
- }
- });
});
});
});
@@ -287,7 +300,7 @@ function setup(app) {
// store the email being added in session data
req.session.pendingAddition = secret;
- resp.json(true);
+ resp.json({ success: true });
// let's now kick out a verification email!
email.sendAddAddressEmail(req.body.email, req.body.site, secret);
@@ -300,7 +313,7 @@ function setup(app) {
app.get('/wsapi/email_for_token', checkParams(["token"]), function(req,resp) {
db.emailForVerificationSecret(req.query.token, function(email) {
- resp.json({email: email});
+ resp.json({ email: email });
});
});
@@ -328,16 +341,16 @@ function setup(app) {
function(registered) {
if (registered) {
delete req.session.pendingAddition;
- resp.json('complete');
+ resp.json({ status: 'complete' });
} else if (!req.session.pendingAddition) {
resp.json('failed');
} else {
db.emailForVerificationSecret(req.session.pendingAddition, function (email) {
if (email) {
- return resp.json('pending');
+ return resp.json({ status: 'pending' });
} else {
delete req.session.pendingAddition;
- resp.json('failed');
+ resp.json({ status: 'failed' });
}
});
}
@@ -348,9 +361,9 @@ function setup(app) {
db.gotVerificationSecret(req.body.token, undefined, function(e) {
if (e) {
logger.error("error completing the verification: " + e);
- resp.json(false);
+ resp.json({ success: false });
} else {
- resp.json(true);
+ resp.json({ success: true });
}
});
});
@@ -360,7 +373,7 @@ function setup(app) {
if (typeof hash !== 'string' ||
typeof req.body.pass !== 'string')
{
- return resp.json(false);
+ return resp.json({ success: false });
}
bcrypt.compare(req.body.pass, hash, function (err, success) {
@@ -372,9 +385,20 @@ function setup(app) {
if (!req.session) req.session = {};
setAuthenticatedUser(req.session, req.body.email);
- // if the work factor has changed, update the hash here
+ // if the work factor has changed, update the hash here. issue #204
+ // NOTE: this runs asynchronously and will not delay the response
+ if (configuration.get('bcrypt_work_factor') != bcrypt.get_rounds(hash)) {
+ logger.info("updating bcrypted password for email " + req.body.email);
+ bcrypt_password(req.body.pass, function(err, hash) {
+ db.updatePassword(req.body.email, hash, function(err) {
+ if (err) {
+ logger.error("error updating bcrypted password for email " + req.body.email, err);
+ }
+ });
+ });
+ }
}
- resp.json(success);
+ resp.json({ success: success });
});
});
});
@@ -387,7 +411,7 @@ function setup(app) {
logger.error("error removing email " + email);
httputils.badRequest(resp, error.toString());
} else {
- resp.json(true);
+ resp.json({ success: true });
}});
});
@@ -397,7 +421,7 @@ function setup(app) {
logger.error("error cancelling account : " + error.toString());
httputils.badRequest(resp, error.toString());
} else {
- resp.json(true);
+ resp.json({ success: true });
}});
});
@@ -423,7 +447,7 @@ function setup(app) {
app.post('/wsapi/logout', function(req, resp) {
clearAuthenticatedUser(req.session);
- resp.json('ok');
+ resp.json({ success: true });
});
// in the cert world, syncing is not necessary,
View
11 browserid/static/css/m.css
@@ -216,4 +216,15 @@
#footer {
padding: 20px 30px;
}
+
+ #emailList .email {
+ width: auto;
+ float: left;
+ }
+
+ #emailList .activity {
+ width: auto;
+ float: right;
+ }
+
}
View
27 browserid/static/dialog/controllers/authenticate_controller.js
@@ -39,7 +39,7 @@
var ANIMATION_TIME = 250,
bid = BrowserID,
- identities = bid.Identities,
+ user = bid.User,
validation = bid.Validation;
function checkEmail(el, event) {
@@ -52,7 +52,7 @@
return;
}
- identities.emailRegistered(email, function onComplete(registered) {
+ user.isEmailRegistered(email, function onComplete(registered) {
if(registered) {
enterPasswordState.call(self);
}
@@ -72,7 +72,7 @@
return;
}
- identities.createUser(email, function(keypair) {
+ user.createUser(email, function(keypair) {
if(keypair) {
self.close("user_staged", {
email: email,
@@ -96,7 +96,7 @@
return;
}
- identities.authenticateAndSync(email, pass,
+ user.authenticateAndSync(email, pass,
function onAuthenticate(authenticated) {
if (authenticated) {
self.doWait(bid.Wait.authentication);
@@ -122,7 +122,7 @@
cancelEvent(event);
- identities.requestPasswordReset(email, function() {
+ user.requestPasswordReset(email, function() {
self.close("reset_password", {
email: email
});
@@ -190,20 +190,29 @@
PageController.extend("Authenticate", {}, {
- init: function() {
+ init: function(el, options) {
+ options = options || {};
+
this._super({
bodyTemplate: "authenticate.ejs",
bodyVars: {
- sitename: identities.getOrigin(),
- siteicon: '/i/times.gif'
+ sitename: user.getOrigin(),
+ siteicon: "/i/times.gif",
+ email: options.email || ""
}
});
+
this.submit = checkEmail;
+ // If we already have an email address, check if it is valid, if so, show
+ // password.
+ if(options.email) {
+ this.submit();
+ }
},
"#email keyup": enterEmailState,
"#forgotPassword click": forgotPasswordState,
- '#cancel_forgot_password click': cancelForgotPassword
+ "#cancel_forgot_password click": cancelForgotPassword
});
}());
View
8 browserid/static/dialog/controllers/checkregistration_controller.js
@@ -37,7 +37,7 @@
(function() {
"use strict";
- var identities = BrowserID.Identities;
+ var user = BrowserID.User;
PageController.extend("Checkregistration", {}, {
init: function(el, options) {
@@ -56,14 +56,14 @@
setupRegCheck: function() {
var me=this;
- identities[me.verifier](me.email, function(status) {
+ user[me.verifier](me.email, function(status) {
if (status === "complete") {
- identities.syncEmails(function() {
+ user.syncEmails(function() {
me.close(me.verificationMessage);
});
}
else if (status === "mustAuth") {
- me.close("auth");
+ me.close("auth", { email: me.email });
}
}, me.getErrorDialog(BrowserID.Errors.registration));
}
View
335 browserid/static/dialog/controllers/dialog_controller.js
@@ -40,169 +40,180 @@
//
(function() {
-"use strict";
-
-PageController.extend("Dialog", {}, {
- init: function(el) {
- this.element.show();
-
- // keep track of where we are and what we do on success and error
- this.onsuccess = null;
- this.onerror = null;
- var chan = setupChannel(this);
- this.stateMachine();
- },
-
- getVerifiedEmail: function(origin_url, onsuccess, onerror) {
- this.onsuccess = onsuccess;
- this.onerror = onerror;
-
- BrowserID.Identities.setOrigin(origin_url);
-
- this.doCheckAuth();
-
- var self=this;
- $(window).bind("unload", function() {
- self.doCancel();
- });
- },
-
-
- stateMachine: function() {
- var self=this,
- hub = OpenAjax.hub,
- el = this.element;
-
-
- hub.subscribe("user_staged", function(msg, info) {
- self.doConfirmUser(info.email);
- });
-
- hub.subscribe("user_confirmed", function() {
- self.doEmailConfirmed();
- });
-
- hub.subscribe("authenticated", function(msg, info) {
- //self.doEmailSelected(info.email);
- // XXX benadida, lloyd - swap these two if you want to experiment with
- // generating assertions directly from signin.
- self.syncEmails();
- });
-
- hub.subscribe("reset_password", function(msg, info) {
- self.doConfirmUser(info.email);
- });
-
- hub.subscribe("assertion_generated", function(msg, info) {
- self.doAssertionGenerated(info.assertion);
- });
-
- hub.subscribe("email_staged", function(msg, info) {
- self.doConfirmEmail(info.email);
- });
-
- hub.subscribe("email_confirmed", function() {
- self.doEmailConfirmed();
- });
-
- hub.subscribe("notme", function() {
- self.doNotMe();
- });
-
- hub.subscribe("auth", function() {
- self.doAuthenticate();
- });
-
- hub.subscribe("start", function() {
- self.doCheckAuth();
- });
-
- hub.subscribe("cancel", function() {
- self.doCancel();
- });
-
- },
-
- doConfirmUser: function(email) {
- this.confirmEmail = email;
-
- this.element.checkregistration({
- email: email,
- verifier: "waitForUserRegistration",
- verificationMessage: "user_confirmed"
- });
- },
-
- doCancel: function() {
- var self=this;
- if(self.onsuccess) {
- self.onsuccess(null);
- }
- },
-
- doSignIn: function() {
- this.element.pickemail();
- },
-
- doAuthenticate: function() {
- this.element.authenticate();
- },
-
- doForgotPassword: function(email) {
- this.element.forgotpassword({
- email: email
- });
- },
-
- doConfirmEmail: function(email) {
- this.confirmEmail = email;
-
- this.element.checkregistration({
- email: email,
- verifier: "waitForEmailRegistration",
- verificationMessage: "email_confirmed"
- });
- },
-
- doEmailConfirmed: function() {
- var self=this;
- // yay! now we need to produce an assertion.
- BrowserID.Identities.getAssertion(this.confirmEmail, self.doAssertionGenerated.bind(self));
- },
-
- doAssertionGenerated: function(assertion) {
- var self=this;
- // 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() {
- BrowserID.Identities.logoutUser(this.doAuthenticate.bind(this));
- },
-
- syncEmails: function() {
- var self = this;
- BrowserID.Identities.syncEmails(self.doSignIn.bind(self),
- self.getErrorDialog(BrowserID.Errors.signIn));
- },
-
-
- doCheckAuth: function() {
- var self=this;
- self.doWait(BrowserID.Wait.checkAuth);
- BrowserID.Identities.checkAuthenticationAndSync(function onSuccess() {},
- function onComplete(authenticated) {
- if (authenticated) {
- self.doSignIn();
- } else {
- self.doAuthenticate();
+ "use strict";
+
+ var user = BrowserID.User;
+
+ PageController.extend("Dialog", {}, {
+ init: function(el) {
+ var self=this;
+ //this.element.show();
+
+ // keep track of where we are and what we do on success and error
+ self.onsuccess = null;
+ self.onerror = null;
+ setupChannel(self);
+ self.stateMachine();
+ },
+
+ getVerifiedEmail: function(origin_url, onsuccess, onerror) {
+ this.onsuccess = onsuccess;
+ this.onerror = onerror;
+
+ user.setOrigin(origin_url);
+
+ this.doCheckAuth();
+
+ var self=this;
+ $(window).bind("unload", function() {
+ self.doCancel();
+ });
+ },
+
+
+ stateMachine: function() {
+ var self=this,
+ hub = OpenAjax.hub,
+ el = this.element;
+
+
+ hub.subscribe("user_staged", function(msg, info) {
+ self.doConfirmUser(info.email);
+ });
+
+ hub.subscribe("user_confirmed", function() {
+ self.doEmailConfirmed();
+ });
+
+ hub.subscribe("authenticated", function(msg, info) {
+ //self.doEmailSelected(info.email);
+ // XXX benadida, lloyd - swap these two if you want to experiment with
+ // generating assertions directly from signin.
+ self.syncEmails();
+ });
+
+ hub.subscribe("reset_password", function(msg, info) {
+ self.doConfirmUser(info.email);
+ });
+
+ hub.subscribe("assertion_generated", function(msg, info) {
+ if(info.assertion !== null) {
+ self.doAssertionGenerated(info.assertion);
}
- },
- self.getErrorDialog(BrowserID.Errors.checkAuthentication));
- }
+ else {
+ self.doPickEmail();
+ }
+ });
+
+ hub.subscribe("email_staged", function(msg, info) {
+ self.doConfirmEmail(info.email);
+ });
+
+ hub.subscribe("email_confirmed", function() {
+ self.doEmailConfirmed();
+ });
+
+ hub.subscribe("notme", function() {
+ self.doNotMe();
+ });
+
+ hub.subscribe("auth", function(msg, info) {
+ info = info || {};
+
+ self.doAuthenticate({
+ email: info.email
+ });
+ });
+
+ hub.subscribe("start", function() {
+ self.doCheckAuth();
+ });
+
+ hub.subscribe("cancel", function() {
+ self.doCancel();
+ });
+
+ },
+
+ doConfirmUser: function(email) {
+ this.confirmEmail = email;
+
+ this.element.checkregistration({
+ email: email,
+ verifier: "waitForUserValidation",
+ verificationMessage: "user_confirmed"
+ });
+ },
+
+ doCancel: function() {
+ var self=this;
+ if(self.onsuccess) {
+ self.onsuccess(null);
+ }
+ },
+
+ doPickEmail: function() {
+ this.element.pickemail();
+ },
+
+ doAuthenticate: function(info) {
+ this.element.authenticate(info);
+ },
+
+ doForgotPassword: function(email) {
+ this.element.forgotpassword({
+ email: email
+ });
+ },
+
+ doConfirmEmail: function(email) {
+ this.confirmEmail = email;
+
+ this.element.checkregistration({
+ email: email,
+ verifier: "waitForEmailValidation",
+ verificationMessage: "email_confirmed"
+ });
+ },
+
+ doEmailConfirmed: function() {
+ var self=this;
+ // yay! now we need to produce an assertion.
+ user.getAssertion(this.confirmEmail, self.doAssertionGenerated.bind(self));
+ },
+
+ doAssertionGenerated: function(assertion) {
+ var self=this;
+ // 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() {
+ user.logoutUser(this.doAuthenticate.bind(this));
+ },
+
+ syncEmails: function() {
+ var self = this;
+ user.syncEmails(self.doPickEmail.bind(self),
+ self.getErrorDialog(BrowserID.Errors.signIn));
+ },
+
+
+ doCheckAuth: function() {
+ var self=this;
+ user.checkAuthenticationAndSync(function onSuccess() {},
+ function onComplete(authenticated) {
+ if (authenticated) {
+ self.doPickEmail();
+ } else {
+ self.doAuthenticate();
+ }
+ },
+ self.getErrorDialog(BrowserID.Errors.checkAuthentication));
+ }
});
View
14 browserid/static/dialog/controllers/page_controller.js
@@ -37,8 +37,7 @@
(function() {
"use strict";
- var bid = BrowserID,
- identities = bid.Identities;
+ var ANIMATION_TIME = 250;
$.Controller.extend("PageController", {
@@ -71,12 +70,13 @@
},
renderTemplates: function(body, body_vars) {
- $("body").removeClass("waiting");
if (body) {
var bodyHtml = $.View("//dialog/views/" + body, body_vars);
- $("#dialog").html(bodyHtml).hide().fadeIn(300, function() {
- $("#dialog input").eq(0).focus();
+ var form = $("#formWrap > form");
+ form.html(bodyHtml).hide().fadeIn(ANIMATION_TIME, function() {
+ $("body").removeClass("waiting");
+ form.find("input").eq(0).focus();
});
}
},
@@ -119,14 +119,14 @@
* two fields, message, description.
*/
errorDialog: function(info) {
- $("#dialog").hide();
+ $("form").hide();
$("#error_dialog .title").text(info.message);
$("#error_dialog .content").text(info.description);
$("body").removeClass("authenticated").addClass("error");
- $("#error_dialog").fadeIn(500);
+ $("#error_dialog").fadeIn(ANIMATION_TIME);
},
/**
View
81 browserid/static/dialog/controllers/pickemail_controller.js
@@ -39,7 +39,10 @@
var ANIMATION_TIME = 250,
bid = BrowserID,
- identities = bid.Identities;
+ user = bid.User,
+ body = $("body"),
+ animationComplete = body.innerWidth() < 640,
+ assertion;
function animateSwap(fadeOutSelector, fadeInSelector, callback) {
// XXX instead of using jQuery here, think about using CSS animations.
@@ -80,41 +83,60 @@
});
}
- function signIn(element, event) {
- var self=this,
- body = $("body"),
- animationComplete = body.innerWidth() < 640,
- assertion,
- email = $("input[type=radio]:checked").val();
-
+ function checkEmail(email) {
+ var identity = user.getStoredEmailKeypair(email);
+ if(!identity) {
+ alert("The selected email is invalid or has been deleted.");
+ this.close("assertion_generated", {
+ assertion: null
+ });
+ }
- cancelEvent(event);
+ return !!identity;
+ }
- function onComplete() {
- if(assertion && animationComplete) {
- self.close("assertion_generated", {
- assertion: assertion
- });
- }
+ function tryClose() {
+ if(typeof assertion !== "undefined" && animationComplete) {
+ this.close("assertion_generated", {
+ assertion: assertion
+ });
}
+ }
+ function getAssertion(email) {
// Kick of the assertion fetching/keypair generating while we are showing
// the animation, hopefully this minimizes the delay the user notices.
- identities.getAssertion(email, function(assert) {
- assertion = assert;
- onComplete();
+ var self=this;
+ user.getAssertion(email, function(assert) {
+ assertion = assert || null;
+ tryClose.call(self);
});
+ }
-
- if(body.innerWidth() > 640) {
+ function startAnimation() {
+ if(!animationComplete) {
+ var self=this;
$("#signIn").animate({"width" : "685px"}, "slow", function () {
// post animation
body.delay(500).animate({ "opacity" : "0.5"}, "fast", function () {
animationComplete = true;
- onComplete();
+ tryClose.call(self);
});
});
}
+
+ }
+
+ function signIn(element, event) {
+ cancelEvent(event);
+ var self=this,
+ email = $("input[type=radio]:checked").val();
+
+ var valid = checkEmail.call(self, email);
+ if (valid) {
+ getAssertion.call(self, email);
+ startAnimation.call(self);
+ }
}
function addEmail(element, event) {
@@ -127,12 +149,12 @@
return;
}
- identities.emailRegistered(email, function onComplete(registered) {
+ user.isEmailRegistered(email, function onComplete(registered) {
if(registered) {
bid.Tooltip.showTooltip("#already_taken");
}
else {
- identities.addEmail(email, function(added) {
+ user.addEmail(email, function(added) {
if (added) {
self.close("email_staged", {
email: email
@@ -154,12 +176,21 @@
this._super({
bodyTemplate: "pickemail.ejs",
bodyVars: {
- sitename: identities.getOrigin(),
+ sitename: user.getOrigin(),
siteicon: '/i/times.gif',
- identities: identities.getStoredEmailKeypairs(),
+ identities: user.getStoredEmailKeypairs(),
}
});
+ $("body").css("opacity", "1");
+
+ if($("#selectEmail input[type=radio]:visible").length === 0) {
+ // If there is only one email address, the radio button is never shown,
+ // instead focus the sign in button so that the user can click enter.
+ // issue #412
+ $("#signInButton").focus();
+ }
+
pickEmailState.call(this);
},
View
25 browserid/static/dialog/css/popup.css
@@ -100,7 +100,7 @@ h2 {
background-image: none;
}
-#formWrap, .waiting #formWrap {
+#formWrap, .waiting #formWrap, .checkregistration #formWrap {
background-color: rgba(0, 0, 0, 0.035);
background-image: url('/i/bg.png');
position: relative;
@@ -109,13 +109,13 @@ h2 {
overflow: hidden;
}
-.waiting #formWrap {
+.waiting #formWrap, .checkregistration #formWrap {
width: 325px;
margin: 0 auto;
text-align: center;
}
-.waiting #formWrap > form {
+.waiting #formWrap > form, .checkregistration #formWrap > form {
height: 250px;
width: 325px;
display: table-cell;
@@ -350,18 +350,17 @@ footer .learn a {
color: #549FDC;
}
-#checkemail {
- text-align: center;
- padding: 0 20px;
- background-image: url('/i/bg.png');
-}
-
-#checkemail p {
+.checkregistration p {
color: #62615F;
text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.5);
}
-#checkemail #displayemail {
+.checkregistration p:first-child {
+ margin-bottom: 20px;
+ font-size: 150%;
+}
+
+.checkregistration strong {
color: #222;
font-weight: bold;
}
@@ -513,6 +512,10 @@ html[xmlns] .cf {
overflow-y: auto;
}
+.pickemail .form_section {
+ height: 176px;
+}
+
.add {
font-size: 80%;
}
View
8 browserid/static/dialog/dialog.js
@@ -54,10 +54,10 @@ steal.plugins(
'tooltip',
'validation',
'browserid-extensions',
- 'browserid-network',
- 'browserid-identities',
- 'browserid-errors',
- 'browserid-wait') // 3rd party script's (like jQueryUI), in resources folder
+ 'network',
+ 'user',
+ 'error-messages',
+ 'wait-messages') // 3rd party script's (like jQueryUI), in resources folder
.controllers('page',
'dialog',
View
0 ...atic/dialog/resources/browserid-errors.js → ...static/dialog/resources/error-messages.js
File renamed without changes.
View
33 ...tic/dialog/resources/browserid-network.js → browserid/static/dialog/resources/network.js
@@ -125,7 +125,7 @@ BrowserID.Network = (function() {
success: function(status, textStatus, jqXHR) {
if (onSuccess) {
try {
- var authenticated = JSON.parse(status);
+ var authenticated = status.success;
if (typeof authenticated !== 'boolean') throw status;
@@ -197,7 +197,7 @@ BrowserID.Network = (function() {
site : origin
},
success: function(status) {
- var staged = JSON.parse(status);
+ var staged = status.success;
// why a delay here? Because of the test harness?
// shouldn't the delay be in the test harness?
_.delay(onSuccess, 0, staged);
@@ -233,7 +233,11 @@ BrowserID.Network = (function() {
checkUserRegistration: function(email, onSuccess, onFailure) {
xhr.ajax({
url: "/wsapi/user_creation_status?email=" + encodeURIComponent(email),
- success: createDeferred(onSuccess),
+ success: function(status, textStatus, jqXHR) {
+ if (onSuccess) {
+ _.delay(onSuccess, 0, status.status);
+ }
+ },
error: onFailure
});
},
@@ -255,8 +259,7 @@ BrowserID.Network = (function() {
},
success: function(status, textStatus, jqXHR) {
if (onSuccess) {
- var valid = JSON.parse(status);
- _.delay(onSuccess, 0, valid);
+ _.delay(onSuccess, 0, status.success);
}
},
error: onFailure
@@ -325,8 +328,7 @@ BrowserID.Network = (function() {
},
success: function(status, textStatus, jqXHR) {
if (onSuccess) {
- var valid = JSON.parse(status);
- _.delay(onSuccess, 0, valid);
+ _.delay(onSuccess, 0, status.success);
}
},
error: onFailure
@@ -363,7 +365,7 @@ BrowserID.Network = (function() {
site: origin
},
success: function(status) {
- var staged = JSON.parse(status);
+ var staged = status.success;
_.delay(onSuccess, 0, staged);
},
error: onFailure
@@ -380,7 +382,11 @@ BrowserID.Network = (function() {
checkEmailRegistration: function(email, onSuccess, onFailure) {
xhr.ajax({
url: "/wsapi/email_addition_status?email=" + encodeURIComponent(email),
- success: createDeferred(onSuccess),
+ success: function(status, textStatus, jqXHR) {
+ if (onSuccess) {
+ _.delay(onSuccess, 0, status.status);
+ }
+ },
error: onFailure
});
},
@@ -399,8 +405,7 @@ BrowserID.Network = (function() {
url: "/wsapi/have_email?email=" + encodeURIComponent(email),
success: function(data, textStatus, xhr) {
if(onSuccess) {
- var success = typeof data === "string" ? !JSON.parse(data) : data;
- _.delay(onSuccess, 0, success);
+ _.delay(onSuccess, 0, data.email_known);
}
},
error: onFailure
@@ -420,7 +425,11 @@ BrowserID.Network = (function() {
data: {
email: email
},
- success: createDeferred(onSuccess),
+ success: function(status, textStatus, jqXHR) {
+ if (onSuccess) {
+ _.delay(onSuccess, 0, status.success);
+ }
+ },
failure: onFailure
});
},
View
102 browserid/static/dialog/resources/storage.js
@@ -47,8 +47,12 @@ BrowserID.Storage = (function() {
window.localStorage.emails = JSON.stringify(emails);
}
- function clearEmails() {
+ function clear() {
storeEmails({});
+ var localStorage = window.localStorage;
+ localStorage.removeItem("tempKeypair");
+ localStorage.removeItem("stagedOnBehalfOf");
+ localStorage.removeItem("sitesToEmail");
}
function getEmails() {
@@ -60,10 +64,16 @@ BrowserID.Storage = (function() {
}
// if we had a problem parsing or the emails are null
- clearEmails();
+ clear();
return {};
}
+ function getEmail(email) {
+ var ids = getEmails();
+
+ return ids && ids[email];
+ }
+
function addEmail(email, obj) {
var emails = getEmails();
emails[email] = obj;
@@ -72,18 +82,35 @@ BrowserID.Storage = (function() {
function removeEmail(email) {
var emails = getEmails();
- delete emails[email];
- storeEmails(emails);
+ if(emails[email]) {
+ delete emails[email];
+ 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];
+ }
+ }
+ localStorage.sitesToEmail = JSON.stringify(sitesToEmail);
+ }
+ else {
+ throw "unknown email address";
+ }
}
function invalidateEmail(email) {
- var id = getEmails()[email];
+ var id = getEmail(email);
if (id) {
delete id.priv;
delete id.pub;
delete id.cert;
addEmail(email, id);
}
+ else {
+ throw "unknown email address";
+ }
}
function storeTemporaryKeypair(keypair) {
@@ -133,12 +160,73 @@ BrowserID.Storage = (function() {
return origin;
}
+ function setSiteEmail(site, email) {
+ if(getEmail(email)) {
+ var localStorage = window.localStorage;
+
+ var sitesToEmail = JSON.parse(localStorage.sitesToEmail || "{}");
+ sitesToEmail[site] = email;
+
+ localStorage.sitesToEmail = JSON.stringify(sitesToEmail);
+ }
+ else {
+ throw "unknown email address";
+ }
+ }
+
+ function getSiteEmail(site) {
+ var sitesToEmail = JSON.parse(localStorage.sitesToEmail || "{}");
+ return sitesToEmail[site];
+ }
+
return {
- getEmails: getEmails,
+ /**
+ * Add an email address and optional key pair.
+ * @method addEmail
+ */
addEmail: addEmail,
+ /**
+ * Get all email addresses and their associated key pairs
+ * @method getEmails
+ */
+ getEmails: getEmails,
+ /**
+ * Get one email address and its key pair, if found. Returns undefined if
+ * not found.
+ * @method getEmail
+ */
+ getEmail: getEmail,
+ /**
+ * Remove an email address, its key pairs, and any sites associated with
+ * email address.
+ * @throws "unknown email address" if email address is not known.
+ * @method removeEmail
+ */
removeEmail: removeEmail,
+ /**
+ * Remove the key information for an email address.
+ * @throws "unknown email address" if email address is not known.
+ * @method invalidateEmail
+ */
invalidateEmail: invalidateEmail,
- clearEmails: clearEmails,
+ /**
+ * 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,
+ /**
+ * Clear all stored data - email addresses, key pairs, temporary key pairs,
+ * site/email associations.
+ * @method clear
+ */
+ clear: clear,
storeTemporaryKeypair: storeTemporaryKeypair,
retrieveTemporaryKeypair: retrieveTemporaryKeypair,
setStagedOnBehalfOf: setStagedOnBehalfOf,
View
229 .../dialog/resources/browserid-identities.js → browserid/static/dialog/resources/user.js
@@ -35,12 +35,13 @@
*
* ***** END LICENSE BLOCK ***** */
-BrowserID.Identities = (function() {
+BrowserID.User = (function() {
"use strict";
var jwk, jwt, vep, jwcert, origin,
network = BrowserID.Network,
- storage = BrowserID.Storage;
+ storage = BrowserID.Storage,
+ User;
function prepareDeps() {
if (!jwk) {
@@ -93,7 +94,7 @@ BrowserID.Identities = (function() {
$('body')[func]('authenticated');
if (!authenticated) {
- storage.clearEmails();
+ storage.clear();
}
}
@@ -119,15 +120,72 @@ BrowserID.Identities = (function() {
setTimeout(poll, 3000);
}
else if (onFailure) {
- onFailure();
+ onFailure(status);
}
});
};
poll();
}
- var Identities = {
+
+ /**
+ * Certify an identity with the server, persist it to storage if the server
+ * says the identity is good
+ * @method certifyEmailKeypair
+ */
+ function certifyEmailKeypair(email, keypair, onSuccess, onFailure) {
+ network.certKey(email, keypair.publicKey, function(cert) {
+ persistEmailKeypair(email, keypair, cert, onSuccess, onFailure);
+ }, onFailure);
+ }
+
+ /**
+ * Persist an email address without a keypair
+ * @method persistEmail
+ * @param {string} email - Email address to persist.
+ * @param {function} [onSuccess] - Called on successful completion.
+ * @param {function} [onFailure] - Called on error.
+ */
+ function persistEmail(email, onSuccess, onFailure) {
+ storage.addEmail(email, {
+ created: new Date()
+ });
+
+ if (onSuccess) {
+ onSuccess();
+ }
+ }
+
+ /**
+ * Persist an address and key pair locally.
+ * @method persistEmailKeypair
+ * @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.
+ */
+ function persistEmailKeypair(email, keypair, cert, onSuccess, onFailure) {
+ var now = new Date();
+ var email_obj = storage.getEmails()[email] || {
+ created: now
+ };
+
+ _.extend(email_obj, {
+ updated: now,
+ pub: keypair.publicKey.toSimpleObject(),
+ priv: keypair.secretKey.toSimpleObject(),
+ cert: cert
+ });
+
+ storage.addEmail(email, email_obj);
+
+ if (onSuccess) {
+ onSuccess();
+ }
+ }
+
+ User = {
/**
* Set the interface to use for networking. Used for unit testing.
* @method setNetwork
@@ -169,26 +227,21 @@ BrowserID.Identities = (function() {
// remember this for later
storage.setStagedOnBehalfOf(origin);
- // FIXME: keysize
network.createUser(email, origin, function(created) {
if (onSuccess) {
- if(created) {
- self.stagedEmail = email;
- }
-
onSuccess(created);
}
}, onFailure);
},
/**
* Poll the server until user registration is complete.
- * @method waitForUserRegistration
+ * @method waitForUserValidation
* @param {string} email - email address to check.
* @param {function} [onSuccess] - Called to give status updates.
* @param {function} [onFailure] - Called on error.
*/
- waitForUserRegistration: function(email, onSuccess, onFailure) {
+ waitForUserValidation: function(email, onSuccess, onFailure) {
registrationPoll(network.checkUserRegistration, email, onSuccess, onFailure);
},
@@ -255,7 +308,7 @@ BrowserID.Identities = (function() {
*/
syncEmails: function(onSuccess, onFailure) {
cleanupIdentities();
- var issued_identities = Identities.getStoredEmailKeypairs();
+ var issued_identities = this.getStoredEmailKeypairs();
// FIXME for certs
@@ -294,37 +347,14 @@ BrowserID.Identities = (function() {
var email = emails_to_add.shift();
- self.persistEmail(email, addNextEmail, onFailure);
+ persistEmail(email, addNextEmail, onFailure);
}
addNextEmail();
});
},
/**
- * Signifies that an identity has been confirmed.
- * @method confirmEmail
- * @param {string} email - Email address.
- * @param {function} [onSuccess] - Called on successful completion.
- * @param {function} [onFailure] - Called on error.
- */
- confirmEmail: function(email, onSuccess, onFailure) {
- var self = this;
- if (email === self.stagedEmail) {
- self.stagedEmail = null;
-
- // certify
- Identities.persistEmail(email, function() {
- self.syncEmails(onSuccess, onFailure);
- });
-
- }
- else if (onFailure) {
- onFailure();
- }
- },
-
- /**
* Check whether the current user is authenticated.
* @method checkAuthentication
* @param {function} [onSuccess] - Called when check is complete with one
@@ -414,7 +444,7 @@ BrowserID.Identities = (function() {
* otw.
* @param {function} [onFailure] - Called on XHR failure.
*/
- emailRegistered: function(email, onSuccess, onFailure) {
+ isEmailRegistered: function(email, onSuccess, onFailure) {
network.emailRegistered(email, onSuccess, onFailure);
},
@@ -432,8 +462,6 @@ BrowserID.Identities = (function() {
var self = this;
network.addEmail(email, origin, function(added) {
if (added) {
- self.stagedEmail = email;
-
// we no longer send the keypair, since we will certify it later.
if (onSuccess) {
onSuccess(added);
@@ -442,7 +470,14 @@ BrowserID.Identities = (function() {
}, onFailure);
},
- waitForEmailRegistration: function(email, onSuccess, onFailure) {
+ /**
+ * Wait for the email registration to complete
+ * @method waitForEmailValidation
+ * @param {string} email - email address to check.
+ * @param {function} [onSuccess] - Called to give status updates.
+ * @param {function} [onFailure] - Called on error.
+ */
+ waitForEmailValidation: function(email, onSuccess, onFailure) {
registrationPoll(network.checkEmailRegistration, email, onSuccess, onFailure);
},
@@ -454,12 +489,16 @@ BrowserID.Identities = (function() {
* @param {function} [onFailure] - Called on failure.
*/
removeEmail: function(email, onSuccess, onFailure) {
- network.removeEmail(email, function() {
- storage.removeEmail(email);
- if (onSuccess) {
- onSuccess();
- }
- }, onFailure);
+ if(storage.getEmail(email)) {
+ network.removeEmail(email, function() {
+ storage.removeEmail(email);
+ if (onSuccess) {
+ onSuccess();
+ }
+ }, onFailure);
+ } else if(onSuccess) {
+ onSuccess();
+ }
},
/**
@@ -475,63 +514,9 @@ BrowserID.Identities = (function() {
// FIXME use true key sizes
prepareDeps();
var keypair = jwk.KeyPair.generate(vep.params.algorithm, 64);
- Identities.certifyEmailKeypair(email, keypair, onSuccess, onFailure);
+ certifyEmailKeypair(email, keypair, onSuccess, onFailure);
},
- /**
- * Certify an identity.
- * @method certifyEmailKeypair
- */
- certifyEmailKeypair: function(email, keypair, onSuccess, onFailure) {
- network.certKey(email, keypair.publicKey, function(cert) {
- Identities.persistEmailKeypair(email, keypair, cert, onSuccess, onFailure);
- }, onFailure);
- },
-
- /**
- * Persist an email address without a keypair
- * @method persistEmail
- * @param {string} email - Email address to persist.
- * @param {function} [onSuccess] - Called on successful completion.
- * @param {function} [onFailure] - Called on error.
- */
- persistEmail: function(email, onSuccess, onFailure) {
- storage.addEmail(email, {
- created: new Date()
- });
-
- if (onSuccess) {
- onSuccess();
- }
- },
-
- /**
- * Persist an address and key pair locally.
- * @method persistEmailKeypair
- * @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.
- */
- persistEmailKeypair: function(email, keypair, cert, onSuccess, onFailure) {
- var now = new Date();
- var email_obj = storage.getEmails()[email] || {
- created: now
- };
-
- _.extend(email_obj, {
- updated: now,
- pub: keypair.publicKey.toSimpleObject(),
- priv: keypair.secretKey.toSimpleObject(),
- cert: cert
- });
-
- storage.addEmail(email, email_obj);
-
- if (onSuccess) {
- onSuccess();
- }
- },
/**
* Get an assertion for an identity
@@ -544,17 +529,18 @@ BrowserID.Identities = (function() {
// we use the current time from the browserid servers
// to avoid issues with clock drift on user's machine.
// (issue #329)
- network.serverTime(function(serverTime) {
- var storedID = Identities.getStoredEmailKeypairs()[email],
- assertion;
+ var storedID = storage.getEmail(email),
+ assertion;
function createAssertion(idInfo) {
- var sk = jwk.SecretKey.fromSimpleObject(idInfo.priv);
- var tok = new jwt.JWT(null, serverTime, origin);
- assertion = vep.bundleCertsAndAssertion([idInfo.cert], tok.sign(sk));
- if (onSuccess) {
- onSuccess(assertion);
- }
+ network.serverTime(function(serverTime) {
+ var sk = jwk.SecretKey.fromSimpleObject(idInfo.priv);
+ var tok = new jwt.JWT(null, serverTime, origin);
+ assertion = vep.bundleCertsAndAssertion([idInfo.cert], tok.sign(sk));
+ if (onSuccess) {
+ onSuccess(assertion);
+ }
+ });
}
if (storedID) {
@@ -566,15 +552,14 @@ BrowserID.Identities = (function() {
else {
// we have no key for this identity, go generate the key,
// sync it and then get the assertion recursively.
- Identities.syncEmailKeypair(email, function() {
- Identities.getAssertion(email, onSuccess, onFailure);
+ User.syncEmailKeypair(email, function() {
+ User.getAssertion(email, onSuccess, onFailure);
}, onFailure);
}
}
else if (onSuccess) {
onSuccess();
}
- });
},
/**
@@ -587,17 +572,27 @@ BrowserID.Identities = (function() {
},
/**
+ * Get an individual stored identity.
+ * @method getStoredEmailKeypair
+ * @return {object} identity information for email, if exists, undefined
+ * otw.
+ */
+ getStoredEmailKeypair: function(email) {
+ return storage.getEmail(email);
+ },
+
+ /**
* Clear the list of identities stored locally.
* @method clearStoredEmailKeypairs
*/
clearStoredEmailKeypairs: function() {
- storage.clearEmails();
+ storage.clear();
},
};
- Identities.setOrigin(document.location.host);
+ User.setOrigin(document.location.host);
- return Identities;
+ return User;
}());
View
0 ...static/dialog/resources/browserid-wait.js → .../static/dialog/resources/wait-messages.js
File renamed without changes.
View
260 browserid/static/dialog/test/qunit/browserid-identities_functional_test.js
@@ -1,260 +0,0 @@
-/*jshint browsers:true, forin: true, laxbreak: true */
-/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: 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 = BrowserID.Identities.getStoredIdentities();
- equal("object", typeof identities, "we have some identities");
- });
-
- test("clearStoredIdentities", function() {
- BrowserID.Identities.clearStoredIdentities();
- var identities = BrowserID.Identities.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() {
- BrowserID.Identities.stageIdentity("testuser@testuser.com", "testuser", function(keypair) {
- equal("object", typeof keypair, "We have a key pair");
- start();
- }, failure("stageIdentity failure"));
-
- stop();
- });
-
- test("confirmIdentity", function() {
- /* BrowserID.Identities.confirmIdentity("testuser@testuser.com", function() {
- start();
- });
-
- stop();
- */
- });
-
- test("authenticateAndSync", function() {
- BrowserID.Storage.clearEmails();
- BrowserID.Identities.authenticateAndSync("testuser@testuser.com", "testuser", function() {
- }, function() {
- var identities = BrowserID.Identities.getStoredIdentities();
- ok("testuser@testuser.com" in identities, "authenticateAndSync syncs email addresses");
- start();
- }, failure("Authentication failure"));
-
- stop();
-
- });
-
- test("checkAuthenticationAndSync", function() {
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Storage.clearEmails();
- BrowserID.Identities.checkAuthenticationAndSync(function() {
- var identities = BrowserID.Identities.getStoredIdentities();
- ok("testuser@testuser.com" in identities, "checkAuthenticationAndSync syncs email addresses");
- start();
- });
- }, failure("Authentication failure"));
-
- stop();
- });
-
- test("addIdentity", function() {
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Identities.removeIdentity("testemail@testemail.com", function() {
- BrowserID.Identities.addIdentity("testemail@testemail.com", function(keypair) {
- equal("object", typeof keypair, "we have a keypair");
-
- var identities = BrowserID.Identities.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() {
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Identities.removeIdentity("testemail@testemail.com", "issuer", function() {
- // XXX verify the identity here
- BrowserID.Identities.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() {
- BrowserID.Identities.persistIdentity("testemail2@testemail.com", { pub: "pub", priv: "priv" });
- var identities = BrowserID.Identities.getStoredIdentities();
- ok("testemail2@testemail.com" in identities, "Our new email is added");
- });
-
- /*
- test("removeIdentity that we add", function() {
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Identities.syncIdentity("testemail@testemail.com", "issuer", function(keypair) {
- BrowserID.Identities.removeIdentity("testemail@testemail.com", function() {
- var identities = BrowserID.Identities.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() {
- BrowserID.Storage.clearEmails();
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Identities.syncIdentities(function onSuccess() {
- ok(true, "we have synced identities");
- start();
- }, failure("identity sync failure"));
- }, failure("Authentication failure"));
-
- stop();
- });
-
- test("syncIdentities with identities preloaded", function() {
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Identities.syncIdentities(function onSuccess() {
- ok(true, "we have synced identities");
- start();
- }, failure("identity sync failure"));
- }, failure("Authentication failure"));
-
- stop();
- });
-
- test("getIdentityAssertion", function() {
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Identities.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() {
- BrowserID.Storage.clearEmails();
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- BrowserID.Identities.removeIdentity("testemail@testemail.com", function() {
- BrowserID.Identities.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 = BrowserID.Identities.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() {
- BrowserID.Network.authenticate("testuser@testuser.com", "testuser", function() {
- // First, force removal that way we know it is not part of our list.
- BrowserID.Identities.removeIdentity("unvalidated@unvalidated.com", function() {
-
- BrowserID.Storage.clearEmails();
- BrowserID.Identities.syncIdentities(function onSuccess() {
-
- var identities = BrowserID.Identities.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.
- BrowserID.Identities.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.
- BrowserID.Storage.clearEmails();
- BrowserID.Identities.syncIdentities(function onSuccess() {
-
- var identities = BrowserID.Identities.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();
- });
-*/