Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

integrating train 2011.12.28

Conflicts:
	resources/static/dialog/controllers/dialog.js
	scripts/browserid.spec
  • Loading branch information...
commit 3632b30d42ed76b5d98c3c47b3d372a295380e57 2 parents 83c408a + 625dcaa
@lloyd lloyd authored
Showing with 4,089 additions and 3,423 deletions.
  1. +2 −1  .gitignore
  2. +38 −0 ChangeLog
  3. +13 −0 Makefile
  4. +10 −2 README.md
  5. +2 −2 Vagrantfile
  6. +9 −14 bin/browserid
  7. +7 −7 bin/dbwriter
  8. +6 −1 bin/keysigner
  9. +10 −7 bin/load_gen
  10. +6 −1 bin/verifier
  11. +40 −63 example/index.html
  12. +11 −0 lib/bcrypt-compute.js
  13. +51 −0 lib/bcrypt.js
  14. +5 −0 lib/configuration.js
  15. +1 −1  lib/db.js
  16. +26 −26 lib/db/json.js
  17. +52 −27 lib/db/mysql.js
  18. +1 −1  lib/email.js
  19. +1 −1  lib/keysigner/keysigner-compute.js
  20. +1 −1  lib/load_gen/activities/add_email.js
  21. +3 −3 lib/load_gen/activities/include_only.js
  22. +2 −0  lib/load_gen/activities/reauth.js
  23. +1 −1  lib/load_gen/activities/reset_pass.js
  24. +1 −1  lib/load_gen/activities/signup.js
  25. +4 −2 lib/load_gen/common.js
  26. +5 −2 lib/load_gen/user_db.js
  27. +27 −16 lib/secrets.js
  28. +27 −0 lib/statsd.js
  29. +11 −18 lib/wsapi.js
  30. +24 −8 lib/wsapi/authenticate_user.js
  31. +6 −4 lib/wsapi/session_context.js
  32. +6 −1 lib/wsapi/update_password.js
  33. +16 −24 package.json
  34. +11 −11 resources/static/css/common.css
  35. +5 −5 resources/static/css/m.css
  36. +80 −41 resources/static/css/style.css
  37. +178 −0 resources/static/dialog/controllers/actions.js
  38. +12 −14 resources/static/dialog/controllers/addemail.js
  39. +15 −22 resources/static/dialog/controllers/authenticate.js
  40. +5 −3 resources/static/dialog/controllers/checkregistration.js
  41. +109 −0 resources/static/dialog/controllers/code_check.js
  42. +70 −177 resources/static/dialog/controllers/dialog.js
  43. +11 −13 resources/static/dialog/controllers/forgotpassword.js
  44. +12 −18 resources/static/dialog/controllers/page.js
  45. +1 −1  resources/static/dialog/controllers/pickemail.js
  46. +43 −38 resources/static/dialog/controllers/required_email.js
  47. +8 −4 resources/static/dialog/css/m.css
  48. +54 −5 resources/static/dialog/css/popup.css
  49. +0 −180 resources/static/dialog/resources/channel.js
  50. +38 −18 resources/static/dialog/resources/helpers.js
  51. +79 −29 resources/static/dialog/resources/state_machine.js
  52. +19 −9 resources/static/dialog/start.js
  53. +4 −0 resources/static/dialog/views/addemail.ejs
  54. +5 −1 resources/static/dialog/views/error.ejs
  55. +8 −1 resources/static/dialog/views/forgotpassword.ejs
  56. +8 −0 resources/static/dialog/views/invalidRequiredEmail.ejs
  57. +2 −2 resources/static/dialog/views/pickemail.ejs
  58. +2 −2 resources/static/dialog/views/requiredemail.ejs
  59. +0 −955 resources/static/include.js
  60. +1 −0  resources/static/include.js
  61. +1,031 −0 resources/static/include_js/include.js
  62. +229 −0 resources/static/lib/winchan.js
  63. +8 −4 resources/static/pages/add_email_address.js
  64. +15 −13 resources/static/pages/forgot.js
  65. +104 −45 resources/static/pages/manage_account.js
  66. +14 −4 resources/static/pages/page_helpers.js
  67. +11 −6 resources/static/pages/signin.js
  68. +24 −15 resources/static/pages/signup.js
  69. +11 −8 resources/static/pages/verify_email_address.js
  70. +3 −169 resources/static/relay/relay.js
  71. +2 −2 resources/static/shared/browser-support.js
  72. +9 −1 resources/static/shared/error-messages.js
  73. +1 −1  resources/static/shared/helpers.js
  74. +8 −8 resources/static/shared/javascript-extensions.js
  75. +2 −1  resources/static/shared/mediator.js
  76. +62 −31 resources/static/shared/network.js
  77. +17 −27 resources/static/shared/screens.js
  78. +52 −25 resources/static/shared/storage.js
  79. +16 −4 resources/static/shared/tooltip.js
  80. +15 −1 resources/static/shared/user.js
  81. +3 −3 resources/static/shared/validation.js
  82. +2 −2 resources/static/shared/wait-messages.js
  83. +8 −4 resources/static/test/{qunit.html → index.html}
  84. +6 −6 resources/static/test/phantomrunner.js
  85. +99 −0 resources/static/test/qunit/controllers/actions_unit_test.js
  86. +30 −32 resources/static/test/qunit/controllers/addemail_unit_test.js
  87. +17 −44 resources/static/test/qunit/controllers/authenticate_unit_test.js
  88. +20 −39 resources/static/test/qunit/controllers/checkregistration_unit_test.js
  89. +123 −0 resources/static/test/qunit/controllers/code_check_unit_test.js
  90. +87 −56 resources/static/test/qunit/controllers/dialog_unit_test.js
  91. +3 −33 resources/static/test/qunit/controllers/forgotpassword_unit_test.js
  92. +29 −39 resources/static/test/qunit/controllers/page_unit_test.js
  93. +7 −33 resources/static/test/qunit/controllers/pickemail_unit_test.js
  94. +103 −117 resources/static/test/qunit/controllers/required_email_unit_test.js
  95. +1 −1  resources/static/test/qunit/dialog_test.js
  96. +1 −1  resources/static/test/qunit/include_unit_test.js
  97. +27 −18 resources/static/test/qunit/mocks/xhr.js
  98. +8 −15 resources/static/test/qunit/pages/add_email_address_test.js
  99. +7 −32 resources/static/test/qunit/pages/forgot_unit_test.js
  100. +114 −91 resources/static/test/qunit/pages/manage_account_unit_test.js
  101. +7 −17 resources/static/test/qunit/pages/signin_unit_test.js
  102. +11 −23 resources/static/test/qunit/pages/signup_unit_test.js
  103. +32 −51 resources/static/test/qunit/pages/verify_email_address_test.js
  104. +0 −173 resources/static/test/qunit/relay/relay_unit_test.js
  105. +0 −163 resources/static/test/qunit/resources/channel_unit_test.js
  106. +13 −33 resources/static/test/qunit/resources/helpers_unit_test.js
  107. +49 −12 resources/static/test/qunit/resources/state_machine_unit_test.js
  108. +52 −64 resources/static/test/qunit/shared/network_unit_test.js
  109. +34 −10 resources/static/test/qunit/shared/screens_unit_test.js
  110. +9 −0 resources/static/test/qunit/shared/storage_unit_test.js
  111. +11 −0 resources/static/test/qunit/shared/tooltip_unit_test.js
  112. +2 −2 resources/static/test/qunit/shared/user_unit_test.js
  113. +73 −0 resources/static/test/qunit/testHelpers/helpers.js
  114. +1 −1  resources/views/about.ejs
  115. +1 −1  resources/views/communication_iframe.ejs
  116. +1 −1  resources/views/dialog.ejs
  117. +7 −5 resources/views/dialog_layout.ejs
  118. +2 −2 resources/views/forgot.ejs
  119. +45 −11 resources/views/index.ejs
  120. +3 −3 resources/views/layout.ejs
  121. +20 −20 resources/views/privacy.ejs
  122. +1 −25 resources/views/relay.ejs
  123. +3 −3 resources/views/signup.ejs
  124. +10 −10 resources/views/tos.ejs
  125. +4 −4 resources/views/verifyemail.ejs
  126. +6 −6 resources/views/verifyuser.ejs
  127. +4 −5 scripts/browserid.spec
  128. +47 −35 scripts/compress.sh
  129. +1 −1  scripts/hash_password.js
  130. +1 −1  scripts/merge_train.sh
  131. +3 −5 scripts/rpmbuild.sh
  132. +1 −1  scripts/serve_example.js
  133. +0 −18 scripts/update_database.sql
  134. +0 −2  scripts/update_schema.sql
  135. +12 −0 tests/password-update-test.js
  136. +89 −0 tests/secrets-test.js
View
3  .gitignore
@@ -5,4 +5,5 @@
/var
/rpmbuild
/npm-debug.log
-
+/resources/static/build
+/resources/static/production
View
38 ChangeLog
@@ -1,3 +1,41 @@
+train-2011.12.28:
+ * improve animation during cert/assertion procedures in dialog: #709
+ * user visible error message in dialog when under back breaking load: #738
+ * cleanup and removal of stale deps from package.json
+ * improve mobile formatting: #747
+ * fixes in dialog communication channel: #748
+ * add a waiting screen while crypto is running on slow browsers: #706
+ * don't allow a user to re-add address they already have verified: #732
+ * CSP (content security policy) fixes: #676
+ * doc fixes regarding running browserid under vagrant
+ * doc fixes regarding new dependencies (libgmp for (much) faster crypto)
+ * bcrypt now runs out of process, uses all available cores, allows for app level 503 under extreme load: #694
+ * Fix "cancel" in the forgot password screen when accessed via required email: #754
+ * first time a user visits browserid.org, show a "learn more" message: #384
+ * partial code versioning/cache busting implementation: #226 #687
+ * improved build process - resource minification no longer leaves artifacts all over: #700
+ * clean up whitespace. meh. #758
+ * emails now come from "BrowserID@" instead of "noreply@": #756
+ * completely new implementation for cross domain window communication (https://github.com/lloyd/winchan) #764 #766
+ * allow canceling of "use a different email: #765
+ * improve language and UX of required email flow: #608
+ * better, earlier dev errors for required email: #632
+ * new assertion format (smaller by 66%) handled by verifier, to be generated by browserid next train: #507
+ * now you can change your password: #771 #114
+ * load generator improvements: #782
+ * improved PRNG: #789 #735
+ * fix regressions in the above: #719 & #776
+ * CSRF token uses better RNG: #800
+
+train-2011.12.08:
+ * improve performance of unit tests. #686
+ * IE8 fixes. #688
+ * logging improvements. #681
+ * loadgen fixes. #682
+ * android fixes. #704
+ * performance improvements. #680
+ * moar instrumentation. #691
+
train-2011.12.01:
* BrowserID now requires NodeJS >= 0.6.2
* extensive work on load generation tool: #504
View
13 Makefile
@@ -0,0 +1,13 @@
+clean:
+ rm -rf node_modules rpmbuild
+
+npm:
+ npm install
+
+rpm: npm
+ scripts/rpmbuild.sh
+
+test: npm
+ npm test
+
+jenkins_build: clean npm test rpm
View
12 README.md
@@ -26,8 +26,8 @@ or changes will be made.
```
cd browserid
vagrant up
-vagrant ssh vagrant@lucid32:browserid
-node ./run.js
+vagrant ssh
+npm start
```
`vagrant up` will take a while. Go get a cup of coffee. This is because it downloads the 500MB VM.
@@ -44,6 +44,7 @@ Here's the software you'll need installed:
* node.js (>= 0.6.2): http://nodejs.org/
* npm: http://npmjs.org/ (or bundled with node in 0.6.3+)
+* libgmp3
* git
* g++
@@ -54,6 +55,13 @@ Here's the software you'll need installed:
3. run `npm start` to start the servers locally
4. visit the demo application ('rp') in your web browser (url output on the console at runtime)
+You can stop the servers with a Cntl-C in the terminal.
+
+## Staying up to date:
+
+1. rm -Rf var node_modules
+2. npm install
+
## Testing
Unit tests can be run by invoking `npm test` at the top level, and you
View
4 Vagrantfile
@@ -1,7 +1,7 @@
Vagrant::Config.run do |config|
- config.vm.box = "lucid32_2.box"
- config.vm.box_url = "http://people.mozilla.org/~aking/browserid/lucid32_2.box"
+ config.vm.box = "browserid_3.box"
+ config.vm.box_url = "http://people.mozilla.org/~aking/browserid/browserid_3.box"
# name vagrant desktop
config.vm.forward_port("readme", 10000, 10000)
View
23 bin/browserid
@@ -164,13 +164,15 @@ views.setup(app);
// #10 - if nothing else has caught this request, serve static files
app.use(express.static(path.join(__dirname, "..", "resources", "static")));
+function doShutdown(readyForShutdownCB) {
+ require('../lib/bcrypt.js').shutdown();
+ db.close(readyForShutdownCB)
+}
+
// #11 - calls to /code_update from localhost will restart the daemon,
// this feature is not externally accessible and is only used by
// the update logic
-shutdown.installUpdateHandler(app, function(readyForShutdown) {
- logger.debug("closing database connection");
- db.close(readyForShutdown)
-});
+shutdown.installUpdateHandler(app, doShutdown);
// #11.5 - custom 404
app.use(function(req, res,next) {
@@ -195,9 +197,7 @@ db.open(config.get('database'), function (error) {
}
// shut down express gracefully on SIGINT
- shutdown.handleTerminationSignals(app, function(readyForShutdown) {
- db.close(readyForShutdown)
- });
+ shutdown.handleTerminationSignals(app, doShutdown);
var bindTo = config.get('bind_to');
app.listen(bindTo.port, bindTo.host, function() {
@@ -207,12 +207,8 @@ db.open(config.get('database'), function (error) {
// some test users
if (process.env['CREATE_TEST_USERS']) {
logger.warn("creating test users... this can take a while...");
- bcrypt.gen_salt(config.get('bcrypt_work_factor'), function (err, salt) {
- if (err) {
- logger.error("error creating test users - bcrypt salt gen: " + err);
- process.exit(1);
- }
- bcrypt.encrypt("THE PASSWORD", salt, function(err, hash) {
+ require('../lib/bcrypt').encrypt(
+ config.get('bcrypt_work_factor'), "THE PASSWORD", function(err, hash) {
if (err) {
logger.error("error creating test users - bcrypt encrypt pass: " + err);
process.exit(1);
@@ -227,7 +223,6 @@ db.open(config.get('database'), function (error) {
});
}
});
- });
}
});
});
View
14 bin/dbwriter
@@ -125,13 +125,15 @@ wsapi.setup({
only_write_apis: true
}, app);
+function doShutdown(readyForShutdownCB) {
+ require('../lib/bcrypt.js').shutdown();
+ db.close(readyForShutdownCB)
+}
+
// calls to /code_update from localhost will restart the daemon,
// this feature is not externally accessible and is only used by
// the update logic
-shutdown.installUpdateHandler(app, function(readyForShutdown) {
- logger.debug("closing database connection");
- db.close(readyForShutdown)
-});
+shutdown.installUpdateHandler(app, doShutdown);
// open the databse
db.open(config.get('database'), function (error) {
@@ -142,9 +144,7 @@ db.open(config.get('database'), function (error) {
}
// shut down express gracefully on SIGINT
- shutdown.handleTerminationSignals(app, function(readyForShutdown) {
- db.close(readyForShutdown)
- });
+ shutdown.handleTerminationSignals(app, doShutdown);
var bindTo = config.get('bind_to');
app.listen(bindTo.port, bindTo.host, function() {
View
7 bin/keysigner
@@ -39,7 +39,8 @@
const
path = require('path'),
-express = require('express');
+express = require('express'),
+statsd = require('../lib/statsd');
const
config = require('../lib/configuration.js'),
@@ -107,10 +108,14 @@ try {
// and our single function
app.post('/wsapi/cert_key', validate(["email", "pubkey"]), function(req, resp) {
+ var startTime = new Date();
cc.enqueue({
pubkey: req.body.pubkey,
email: req.body.email
}, function (err, r) {
+ var reqTime = new Date - startTime;
+ statsd.timing('certification_time', reqTime);
+
// consider "application" errors to be the same as harder errors
if (!err && r && r.error) err = r.error;
else if (!r || !r.success) err = "no certificate returned from child process";
View
17 bin/load_gen
@@ -58,8 +58,11 @@ var argv = require('optimist')
.describe('o', 'when enabled, only dynamic WSAPI calls will be simulated, not static resource requests')
.default('o', false)
.alias('s', 'server')
+.string('s')
.describe('s', 'base URL to browserid server')
-.demand('s')
+.check(function(argv) {
+ return (typeof argv.s === 'string' || argv.l) != undefined;
+})
.alias('v', 'verifier')
.describe('v', 'base URL to verifier service (default is browserid server + \'/verify\')')
.alias('u', 'user-range')
@@ -67,11 +70,6 @@ var argv = require('optimist')
var args = argv.argv;
-if (args.h) {
- argv.showHelp();
- process.exit(1);
-}
-
// global configuration
const configuration = {
verifier: args.v ? args.v : args.s + "/verify",
@@ -93,7 +91,7 @@ var completed = {
};
// how many activies does an active user undertake per second
-const activitiesPerUserPerSecond = (40.0 / ( 24 * 60 * 60 ));
+const activitiesPerUserPerSecond = (40.0 / ( 24 * 60 * 60 ));
// activities
var activity = {
@@ -139,6 +137,11 @@ if (args.l) {
process.exit(0);
}
+if (args.h) {
+ argv.showHelp();
+ process.exit(1);
+}
+
var activitiesToRun = Object.keys(activity);
// handle modification of activities to run (-o or -a)
View
7 bin/verifier
@@ -47,7 +47,8 @@ metrics = require('../lib/metrics'),
heartbeat = require('../lib/heartbeat'),
logger = require('../lib/logging').logger,
config = require('../lib/configuration'),
-shutdown = require('../lib/shutdown');
+shutdown = require('../lib/shutdown'),
+statsd = require('../lib/statsd');
logger.info("verifier server starting up");
@@ -118,10 +119,14 @@ app.post('/verify', function(req, resp, next) {
return resp.json({ status: "failure", reason: "need assertion and audience" });
}
+ var startTime = new Date();
cc.enqueue({
assertion: assertion,
audience: audience
}, function (err, r) {
+ var reqTime = new Date - startTime;
+ statsd.timing('assertion_verification_time', reqTime);
+
// consider "application" errors to be the same as harder errors
if (!err && r && r.error) err = r.error;
else if (!r || !r.success) err = "no response returned from child process";
View
103 example/index.html
@@ -2,21 +2,20 @@
<html>
<head>
<meta charset="utf-8">
+<meta name="viewport" content="initial-scale=1.0; maximum-scale=1.0; width=device-width;">
<title>
BrowserID Relying Party
</title>
-<link href='http://fonts.googleapis.com/css?family=Permanent+Marker' rel='stylesheet' type='text/css'>
<style type="text/css">
body { margin: auto; font: 13px/1.5 Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif; }
a:link, a:visited { font-style: italic; text-decoration: none; color: #008; }
a:hover { border-bottom: 2px solid black ; }
-.number { font-family: 'Permanent Marker', arial, serif; font-size: 4em; float: left; padding: 0; margin: 0; vertical-align: top; width: 1.3em}
.title { font-size: 2em; font-weight: bold; text-align: center; margin: 1.5em; }
.intro { font-size: 1.2em; width: 600px; margin: auto; }
-.step { width: 600px; margin: auto; margin-top: 1em;}
-.desc { padding-top: 1.5em; min-height: 4.5em;}
-.output {
+.specify { font-size: 1.1em; width: 600px; padding-top: 2em; margin: auto; }
+.assertion, .verifierResp { width: 600px; margin: auto; }
+pre {
font-family: 'lucida console', monaco, 'andale mono', 'bitstream vera sans mono', consolas, monospace;
border: 3px solid #666;
-moz-border-radius: 4px;
@@ -32,53 +31,43 @@
word-wrap: break-word;
}
+@media screen and (max-width: 640px) {
+ .intro, .output, .step {
+ width: 90%;
+ }
+}
+
</style>
</head>
<body>
<div class="title">
- Example BrowserID Relying Party
+ BrowserID Test Relying Party
</div>
<div class="intro">
- This is the simplest possible BrowserID Relying Party. It
- demonstrates the steps required to use BrowserID to verify
- the identity of a user. Follow the steps below...
-</div>
-
-<div class="step">
- <div class="number">1.</div>
- <div class="desc">At page load time, check to see if the user is already (persistently) signed in by calling <tt>navigator.id.get(&lt;callback&gt;, {silent:true});</tt>
- <div class="output" id="oPersistent">...</div>
+ This is a RP for testing, it allows you to drive the <tt>navigator.id.get()</tt> call manually
+ to locally test BrowserID.
</div>
-<div class="step">
- <div class="number">2.</div>
- <div class="desc">If the user is *not already signed in, wait for <a id="clickForLogin" href="#">their click</a>.
+<div class="specify">
+ What flavor of assertion would you like? <br/>
+ <p>
+ <input type="checkbox" id="silent">&nbsp;Silent <br/>
+ <input type="checkbox" id="allowPersistent">&nbsp;Allow persistent sign-in <br/>
+ <input type="text" id="requiredEmail" width="80">&nbsp;Require a specific email <br/>
+ <button>Get an assertion</button>
+ </p>
</div>
-<div class="step">
- <div class="number">3.</div>
- <div class="desc">Once an assertion is obtained, pass it up to the server for verification. The assertion looks like this:</div>
- <div class="output" id="oAssertion">...</div>
+<div class="verifierResp">
+ <pre> ... verifier response ... </pre>
</div>
-<div class="step">
- <div class="number">4.</div>
- <div class="desc">The verification servers checks the assertion and returns a response, that looks like this:</div>
- <div class="output" id="oVerificationResponse"><pre>...</pre></div>
-</div>
-
-<div class="step">
- <div class="number">5.</div>
- <div class="desc">Next, you should provide a logout button that calls <tt>navigator.id.logout()</tt> and then does whatever application specific logout steps are required. <a href="#" id="logout">Click here to logout</a></div>
-</div>
+<div class="assertion">
+ <pre> ... ye' ol' assertion ... </pre>
+</body>
-<div class="step">
- <div class="number">6.</div>
- <div class="desc"><b>All Done!</b> The site can now create an account keyed on the users identity (email address), set cookies, etc! Signing in again is just re-running these same steps.</div>
</div>
-
-</body>
<script src="jquery-min.js"></script>
<script src="https://browserid.org/include.js"></script>
<script>
@@ -94,47 +83,35 @@
audience: window.location.protocol + "//" + window.location.host
},
success: function(data, textStatus, jqXHR) {
- $("#oVerificationResponse > pre").text(JSON.stringify(data, null, 4));
+ $(".verifierResp > pre").text(JSON.stringify(data, null, 4));
},
error: function(jqXHR, textStatus, errorThrown) {
var resp = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : errorThrown;
- $("#oVerificationResponse > pre").text(resp);
+ $(".verifierResp > pre").text(resp);
}
});
};
$(document).ready(function() {
-// at page load time, we'll check to see if the user is already signed in
-// for IE, this MUST be done after document ready.
-navigator.id.get(function(assertion) {
- if (!assertion) {
- $("#oPersistent").text("user isn't (persistently) signed in");
- } else {
- $("#oPersistent").text(assertion);
- checkAssertion(assertion);
- };
-}, { silent: true });
+ $(".specify button").click(function() {
+ $("pre").text("... waiting ...");
+
+ var requiredEmail = $.trim($('#requiredEmail').val());
+ if (!requiredEmail.length) requiredEmail = undefined;
- // install a click handler for when the user clicks 'sign in'
- $("#clickForLogin").click(function(event) {
- event.preventDefault();
navigator.id.get(function(assertion) {
if (!assertion) {
- $("#oAssertion").text("user didn't select an identity.");
+ $(".assertion pre").text("navigator.id.get() returns NULL");
} else {
- $("#oAssertion").text(assertion);
+ $(".assertion pre").text(assertion);
checkAssertion(assertion);
- };
- }, {allowPersistent: true});
- });
-
- $("#logout").click(function(event) {
- event.preventDefault();
- navigator.id.logout(function() {
- // XXX: what should we do after logout?
+ }
+ }, {
+ silent: $('#silent').attr('checked'),
+ allowPersistent: $('#allowPersistent').attr('checked'),
+ requiredEmail: requiredEmail
});
});
-
});
</script>
View
11 lib/bcrypt-compute.js
@@ -0,0 +1,11 @@
+const bcrypt = require('bcrypt');
+
+process.on('message', function(m) {
+ if (m.op === 'encrypt') {
+ var r = bcrypt.encrypt_sync(m.pass, bcrypt.gen_salt_sync(m.factor));
+ process.send({r:r});
+ } else if (m.op === 'compare') {
+ var r = bcrypt.compare_sync(m.pass, m.hash);
+ process.send({r:r});
+ }
+});
View
51 lib/bcrypt.js
@@ -0,0 +1,51 @@
+const
+computecluster = require('compute-cluster'),
+logger = require('../lib/logging.js').logger,
+bcrypt = require('bcrypt');
+
+var cc = new computecluster({
+ module: path.join(__dirname, "bcrypt-compute.js"),
+ max_backlog: 100000
+});
+
+cc.on('error', function(e) {
+ logger.error("error detected in bcrypt computation process! fatal: " + e.toString());
+ setTimeout(function() { process.exit(1); }, 0);
+}).on('info', function(msg) {
+ logger.info("(compute cluster): " + msg);
+}).on('debug', function(msg) {
+ logger.debug("(compute cluster): " + msg);
+});
+
+exports.encrypt = function(workFactor, password, cb) {
+ if (!cc) throw "bcrypt cluster is shut down";
+ cc.enqueue({
+ op: 'encrypt',
+ factor: workFactor,
+ pass: password
+ }, function(err, r) {
+ cb(err, r ? r.r : undefined);
+ });
+};
+
+exports.compare = function(pass, hash, cb) {
+ if (!cc) throw "bcrypt cluster is shut down";
+ cc.enqueue({
+ op: 'compare',
+ pass: pass,
+ hash: hash
+ }, function(err, r) {
+ cb(err, r ? r.r : undefined);
+ })
+};
+
+exports.get_rounds = function(hash) {
+ return bcrypt.get_rounds(hash);
+};
+
+exports.shutdown = function() {
+ if (cc) {
+ cc.exit();
+ cc = undefined;
+ }
+};
View
5 lib/configuration.js
@@ -218,6 +218,11 @@ if (process.env['VAR_PATH']) {
g_config.var_path = process.env['VAR_PATH'];
}
+// allow statsd to be enabled from the environment
+if (process.env['ENABLE_STATSD']) {
+ g_config.statsd = { enabled: true };
+}
+
// what host/port shall we bind to?
g_config.bind_to = {
host: process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1",
View
2  lib/db.js
@@ -144,4 +144,4 @@ exports.addTestUser = function() {
// would we like to check the environment here?
checkReady();
driver['addTestUser'].apply(undefined, arguments);
-};
+};
View
52 lib/db/json.js
@@ -171,35 +171,35 @@ function addEmailToAccount(existing_email, email, cb) {
}
exports.stageUser = function(email, cb) {
- var secret = secrets.generate(48);
-
- // overwrite previously staged users
- sync();
- db.staged[secret] = {
- type: "add_account",
- email: email,
- when: (new Date()).getTime()
- };
- db.stagedEmails[email] = secret;
- flush();
- setTimeout(function() { cb(secret); }, 0);
+ secrets.generate(48, function(secret) {
+ // overwrite previously staged users
+ sync();
+ db.staged[secret] = {
+ type: "add_account",
+ email: email,
+ when: (new Date()).getTime()
+ };
+ db.stagedEmails[email] = secret;
+ flush();
+ setTimeout(function() { cb(secret); }, 0);
+ });
};
exports.stageEmail = function(existing_email, new_email, cb) {
- var secret = secrets.generate(48);
-
- // overwrite previously staged users
- sync();
- db.staged[secret] = {
- type: "add_email",
- existing_email: existing_email,
- email: new_email,
- when: (new Date()).getTime()
- };
- db.stagedEmails[new_email] = secret;
- flush();
-
- setTimeout(function() { cb(secret); }, 0);
+ secrets.generate(48, function(secret) {
+ // overwrite previously staged users
+ sync();
+ db.staged[secret] = {
+ type: "add_email",
+ existing_email: existing_email,
+ email: new_email,
+ when: (new Date()).getTime()
+ };
+ db.stagedEmails[new_email] = secret;
+ flush();
+
+ setTimeout(function() { cb(secret); }, 0);
+ });
};
View
79 lib/db/mysql.js
@@ -61,7 +61,8 @@
const
mysql = require('mysql'),
secrets = require('../secrets.js'),
-logger = require('../logging.js').logger;
+logger = require('../logging.js').logger,
+statsd = require('../statsd');
var client = undefined;
@@ -124,6 +125,28 @@ exports.open = function(cfg, cb) {
logger.debug("connecting to database: " + database);
options.database = database;
client = mysql.createClient(options);
+
+ // replace .query with a function that times queries and
+ // logs to statsd
+ var realQuery = client.query;
+ client.query = function() {
+ var startTime = new Date();
+ var client_cb;
+ var new_cb = function() {
+ var reqTime = new Date - startTime;
+ statsd.timing('query_time', reqTime);
+ if (client_cb) client_cb.apply(null, arguments);
+ };
+ var args = Array.prototype.slice.call(arguments);
+ if (typeof args[args.length - 1] === 'function') {
+ client_cb = args[args.length - 1];
+ args[args.length - 1] = new_cb;
+ } else {
+ args.push(new_cb);
+ }
+ realQuery.apply(client, args);
+ };
+
client.ping(function(err) {
logger.debug("connection to database " + (err ? ("fails: " + err) : "established"));
cb(err);
@@ -228,21 +251,22 @@ exports.lastStaged = function(email, cb) {
else cb(new Date(rows[0].ts * 1000));
}
);
-}
+};
exports.stageUser = function(email, cb) {
- var secret = secrets.generate(48);
- // overwrite previously staged users
- client.query('INSERT INTO staged (secret, new_acct, email) VALUES(?,TRUE,?) ' +
- 'ON DUPLICATE KEY UPDATE secret=?, existing="", new_acct=TRUE, ts=NOW()',
- [ secret, email, secret],
- function(err) {
- if (err) {
- logUnexpectedError(err);
- cb(undefined, err);
- } else cb(secret);
- });
-}
+ secrets.generate(48, function(secret) {
+ // overwrite previously staged users
+ client.query('INSERT INTO staged (secret, new_acct, email) VALUES(?,TRUE,?) ' +
+ 'ON DUPLICATE KEY UPDATE secret=?, existing="", new_acct=TRUE, ts=NOW()',
+ [ secret, email, secret],
+ function(err) {
+ if (err) {
+ logUnexpectedError(err);
+ cb(undefined, err);
+ } else cb(secret);
+ });
+ });
+};
exports.emailForVerificationSecret = function(secret, cb) {
client.query(
@@ -338,19 +362,20 @@ exports.emailsBelongToSameAccount = function(lhs, rhs, cb) {
}
exports.stageEmail = function(existing_email, new_email, cb) {
- var secret = secrets.generate(48);
- // overwrite previously staged users
- client.query('INSERT INTO staged (secret, new_acct, existing, email) VALUES(?,FALSE,?,?) ' +
- 'ON DUPLICATE KEY UPDATE secret=?, existing=?, new_acct=FALSE, ts=NOW()',
- [ secret, existing_email, new_email, secret, existing_email],
- function(err) {
- if (err) {
- logUnexpectedError(err);
- cb(undefined, err);
- }
- else cb(secret);
- });
-}
+ secrets.generate(48, function(secret) {
+ // overwrite previously staged users
+ client.query('INSERT INTO staged (secret, new_acct, existing, email) VALUES(?,FALSE,?,?) ' +
+ 'ON DUPLICATE KEY UPDATE secret=?, existing=?, new_acct=FALSE, ts=NOW()',
+ [ secret, existing_email, new_email, secret, existing_email],
+ function(err) {
+ if (err) {
+ logUnexpectedError(err);
+ cb(undefined, err);
+ }
+ else cb(secret);
+ });
+ });
+};
exports.checkAuth = function(email, cb) {
client.query(
View
2  lib/email.js
@@ -84,7 +84,7 @@ function doSend(landing_page, email, site, secret) {
console.log("\nVERIFICATION URL:\n" + url + "\n");
} else {
emailer.send_mail({
- sender: "noreply@browserid.org",
+ sender: "BrowserID@browserid.org",
to: email,
subject : "Complete Login to " + site + " using BrowserID",
body: mustache.to_html(template, { email: email, link: url, site: site })
View
2  lib/keysigner/keysigner-compute.js
@@ -6,7 +6,7 @@ process.on('message', function(m) {
try {
// parse the pubkey
var pk = ca.parsePublicKey(m.pubkey);
-
+
// same account, we certify the key
// we certify it for a day for now
var expiration = new Date();
View
2  lib/load_gen/activities/add_email.js
@@ -18,7 +18,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
- * Lloyd Hilaiel <lloyd@hilaiel.com>
+ * 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
View
6 lib/load_gen/activities/include_only.js
@@ -18,7 +18,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
- * Lloyd Hilaiel <lloyd@hilaiel.com>
+ * 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
@@ -45,8 +45,8 @@ client = require('../../wsapi_client.js');
exports.startFunc = function(cfg, cb) {
client.get(cfg, '/include.js', {}, undefined, function(r) {
- if (r.code != 200) {
- cb("for include.js fetch response code is not 200: " + r.code);
+ if (!r || r.code !== 200) {
+ cb("for include.js fetch response code is not 200: " + (r ? r.code : "no response"));
} else {
// XXX: check the checksum of body?
cb();
View
2  lib/load_gen/activities/reauth.js
@@ -81,6 +81,8 @@ exports.startFunc = function(cfg, cb) {
// clear cookies from this context (we're going to log in again)
wcli.clearCookies(context);
+ if (context.session && context.session.authenticated)
+ context.session.authenticated = false;
var origin = userdb.any(user.sites);
View
2  lib/load_gen/activities/reset_pass.js
@@ -18,7 +18,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
- * Lloyd Hilaiel <lloyd@hilaiel.com>
+ * 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
View
2  lib/load_gen/activities/signup.js
@@ -18,7 +18,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
- * Lloyd Hilaiel <lloyd@hilaiel.com>
+ * 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
View
6 lib/load_gen/common.js
@@ -37,7 +37,8 @@ exports.authAndKey = function(cfg, user, ctx, email, cb) {
pubkey: keypair.publicKey.serialize()
}, function(resp) {
try {
- if (resp.code !== 200) throw "non-200 status: " + resp.code;
+ if (resp.code !== 200) throw "non-200 status: " + resp.code +
+ + " - " + resp.body;
if (typeof resp.body !== 'string') throw cb("no response body");
userdb.addCertToUserCtx(ctx, email, resp.body);
cb();
@@ -83,7 +84,8 @@ exports.genAssertionAndVerify = function(cfg, user, ctx, email, audience, cb) {
}, function (r) {
try {
if (r.code !== 200) throw "non-200 status: " + resp.code;
- cb(JSON.parse(r.body).status === 'okay' ? undefined : "verification failed");
+ if (!JSON.parse(r.body).status === 'okay') throw "verification failed with: " + r.reason;
+ cb(undefined);
} catch(e) {
return cb("can't verify: " + e.toString());
}
View
7 lib/load_gen/user_db.js
@@ -18,7 +18,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
- * Lloyd Hilaiel <lloyd@hilaiel.com>
+ * 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
@@ -117,7 +117,10 @@ exports.splitUser = function(user) {
return user;
} else {
var newuser = exports.getNewUser();
- newuser.emails[0] = user.emails.shift();
+ // When splitting an account, always split off the *last* email.
+ // The *first* email may be associated with a pre-created account.
+ // see issue #681
+ newuser.emails[0] = user.emails.pop();
exports.releaseUser(user);
return newuser;
}
View
43 lib/secrets.js
@@ -38,33 +38,44 @@ path = require('path'),
fs = require('fs'),
jwk = require('jwcrypto/jwk'),
jwt = require('jwcrypto/jwt'),
-Buffer = require('buffer').Buffer;
+Buffer = require('buffer').Buffer,
+crypto = require('crypto');
+// make this async capable
+function bytesToChars(buf) {
+ var str = "";
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-function randomBytes(length) {
- var buf = new Buffer(length);
- var fd = fs.openSync('/dev/urandom', 'r');
- fs.readSync(fd, buf, 0, buf.length, 0);
- fs.closeSync(fd);
- return buf;
+ // yes, we are biasing the output here a bit.
+ // I'm ok with that. We can improve this over time.
+ for (var i=0; i < buf.length; i++) {
+ str += alphabet.charAt(buf[i] % alphabet.length);
+ }
+
+ return str;
}
-exports.randomBytes = randomBytes;
+exports.generate = function(chars, cb) {
+ if (cb) {
+ crypto.randomBytes(chars, function(ex, buf) {
+ cb(bytesToChars(buf));
+ });
+ } else {
+ return bytesToChars(crypto.randomBytes(chars));
+ }
+};
-exports.generate = function(chars) {
+// we don't bother to make this async, cause it's not needed
+exports.weakGenerate = function(chars) {
var str = "";
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- var bytes = randomBytes(chars);
-
- // yes, we are biasing the output here a bit.
- // I'm ok with that. We can improve this over time.
for (var i=0; i < chars; i++) {
- str += alphabet.charAt(bytes[i] % alphabet.length);
+ str += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
}
-
+
return str;
-}
+};
// functions to set defaults
View
27 lib/statsd.js
@@ -0,0 +1,27 @@
+const
+StatsD = require("node-statsd").StatsD,
+config = require('./configuration');
+
+const PREFIX = "browserid." + config.get('process_type') + ".";
+
+var statsd = undefined;
+
+// start by exporting a stubbed no-op stats reporter
+module.exports = {
+ timing: function(s, v) {
+ if (statsd) statsd.timing(PREFIX + s, v);
+ },
+ increment: function(s, v) {
+ if (statsd) statsd.increment(PREFIX + s, v);
+ }
+};
+
+var statsd_config = config.get('statsd');
+
+if (statsd_config && statsd_config.enabled) {
+ var options = {};
+ options["host"] = options["host"] || "localhost";
+ options["port"] = options["port"] || 8125;
+
+ statsd = new StatsD(options["host"], options["port"]);
+}
View
29 lib/wsapi.js
@@ -22,7 +22,8 @@ url = require('url'),
fs = require('fs'),
path = require('path'),
validate = require('./validate'),
-bcrypt = require('bcrypt');
+statsd = require('./statsd');
+bcrypt = require('./bcrypt');
const COOKIE_SECRET = secrets.hydrateSecret('browserid_cookie', config.get('var_path'));
const COOKIE_KEY = 'browserid_state';
@@ -54,22 +55,11 @@ function isAuthed(req) {
}
function bcryptPassword(password, cb) {
- var bcryptWorkFactor = config.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);
- });
+ var startTime = new Date();
+ bcrypt.encrypt(config.get('bcrypt_work_factor'), password, function() {
+ var reqTime = new Date - startTime;
+ statsd.timing('bcrypt.encrypt_time', reqTime);
+ cb.apply(null, arguments);
});
};
@@ -135,6 +125,9 @@ exports.setup = function(options, app) {
const operation = purl.pathname.substr(WSAPI_PREFIX.length);
+ // count the number of WSAPI operation
+ statsd.increment("wsapi." + operation);
+
// check to see if the api is known here, before spending more time with
// the request.
if (!wsapis.hasOwnProperty(operation) ||
@@ -142,7 +135,7 @@ exports.setup = function(options, app) {
{
// if the fake verification api is enabled (for load testing),
// then let this request fall through
- if (operation !== 'fake_verification' || !process.env['BROWSERID_FAKE_VERIFICATION'])
+ if (operation !== 'fake_verification' || !process.env['BROWSERID_FAKE_VERIFICATION'])
return httputils.badRequest(resp, "no such api");
}
View
32 lib/wsapi/authenticate_user.js
@@ -3,10 +3,11 @@ db = require('../db.js'),
wsapi = require('../wsapi.js'),
httputils = require('../httputils'),
logger = require('../logging.js').logger,
-bcrypt = require('bcrypt'),
+bcrypt = require('../bcrypt'),
http = require('http'),
https = require('https'),
-querystring = require('querystring');
+querystring = require('querystring'),
+statsd = require('../statsd');
exports.method = 'post';
exports.writes_db = false;
@@ -14,18 +15,33 @@ exports.authed = false;
exports.args = ['email','pass'];
exports.process = function(req, res) {
+ function fail(reason) {
+ var r = { success: false };
+ if (reason) r.reason = reason;
+ logger.debug('authentication fails for user: ' + req.body.email + (reason ? (' - ' + reason) : ""));
+ return res.json(r);
+ }
+
db.checkAuth(req.body.email, function(hash) {
- if (typeof hash !== 'string' || typeof req.body.pass !== 'string')
- {
- return res.json({ success: false });
+ if (typeof hash !== 'string') {
+ return fail('no such user');
+ }
+ // this should never be false because higher level code checks, but
+ // let's check again! whee!
+ if (typeof req.body.pass !== 'string') {
+ return fail('missing "pass" argument');
}
+ var startTime = new Date();
bcrypt.compare(req.body.pass, hash, function (err, success) {
+ var reqTime = new Date - startTime;
+ statsd.timing('bcrypt.compare_time', reqTime);
+
if (err) {
- logger.warn("error comparing passwords with bcrypt: " + err);
- res.json({ success: false });
+ logger.error("error comparing passwords with bcrypt: " + err);
+ return fail("internal password check error");
} else if (!success) {
- res.json({ success: false });
+ return fail("mismatch - pass:" + req.body.pass + " - email:" + req.body.email + " - hash:" + hash);
} else {
if (!req.session) req.session = {};
wsapi.setAuthenticatedUser(req.session, req.body.email);
View
10 lib/wsapi/session_context.js
@@ -6,6 +6,7 @@ wsapi = require('../wsapi.js'),
secrets = require('../secrets.js');
// return the CSRF token, authentication status, and current server time (for assertion signing)
+// 2011-12-22: adding a random seed for keygen
// IMPORTANT: this is safe because it's only readable by same-origin code
exports.method = 'get';
@@ -22,9 +23,9 @@ exports.process = function(req, res) {
}
if (typeof req.session.csrf == 'undefined') {
- // FIXME: using express-csrf's approach for generating randomness
- // not awesome, but probably sufficient for now.
- req.session.csrf = crypto.createHash('md5').update('' + new Date().getTime()).digest('hex');
+ // more random CSRF
+ // FIXME: async?
+ req.session.csrf = crypto.randomBytes(16).toString('base64');
logger.debug("NEW csrf token created: " + req.session.csrf);
}
@@ -35,7 +36,8 @@ exports.process = function(req, res) {
csrf_token: req.session.csrf,
server_time: (new Date()).getTime(),
authenticated: auth_status,
- domain_key_creation_time: domainKeyCreationDate.getTime()
+ domain_key_creation_time: domainKeyCreationDate.getTime(),
+ random_seed: crypto.randomBytes(32).toString('base64')
});
};
View
7 lib/wsapi/update_password.js
@@ -3,7 +3,7 @@ db = require('../db.js'),
wsapi = require('../wsapi.js'),
httputils = require('../httputils'),
logger = require('../logging.js').logger,
-bcrypt = require('bcrypt');
+bcrypt = require('../bcrypt');
exports.method = 'post';
exports.writes_db = true;
@@ -23,6 +23,11 @@ exports.process = function(req, res) {
return res.json({ success: false });
}
+ if (!success) {
+ logger.info("password update fails, incorrect old password");
+ return res.json({ success: false });
+ }
+
logger.info("updating password for email " + req.session.authenticatedUser);
wsapi.bcryptPassword(req.body.newpass, function(err, hash) {
if (err) {
View
40 package.json
@@ -3,36 +3,28 @@
, "version": "0.0.1"
, "private": true
, "dependencies": {
- "connect": "1.7.2"
- , "express": "2.5.0"
- , "xml2js": "0.1.5"
- , "nodemailer": "0.1.18"
- , "mustache": "0.3.1-dev"
- , "vows": "0.5.13"
+ "JSONSelect": "0.2.1"
, "bcrypt": "0.4.1"
- , "ejs": "0.4.3"
- , "temp": "0.2.0"
- , "express-csrf": "0.3.2"
- , "uglify-js": "1.0.6"
- , "JSONSelect": "0.2.1"
- , "winston" : "0.5.6"
+ , "compute-cluster": "0.0.5"
+ , "connect": "1.7.2"
, "connect-cookie-session" : "0.0.2"
+ , "connect-logger-statsd": "0.0.1"
+ , "ejs": "0.4.3"
+ , "express": "2.5.0"
+ , "jwcrypto": "0.1.1"
+ , "mustache": "0.3.1-dev"
, "mysql" : "0.9.5"
+ , "node-statsd": "https://github.com/downloads/lloyd/node-statsd/3a73de.tgz"
+ , "nodemailer": "0.1.18"
, "optimist" : "0.2.8"
- , "qs" : "0.3.1"
- , "mime" : "1.2.2"
- , "pkginfo" : "0.2.2"
- , "colors" : "0.5.0"
- , "sax" : "0.2.3"
- , "mimelib-noiconv" : "0.1.3"
- , "jwcrypto": "0.0.3"
, "postprocess": "0.0.3"
- , "urlparse": "0.0.1"
- , "uglifycss": "0.0.4"
- , "node-statsd": "https://github.com/mojodna/node-statsd/tarball/2584c08fad"
- , "connect-logger-statsd": "0.0.1"
, "semver": "1.0.12"
- , "compute-cluster": "0.0.4"
+ , "temp": "0.2.0"
+ , "uglify-js": "1.0.6"
+ , "uglifycss": "0.0.4"
+ , "urlparse": "0.0.1"
+ , "vows": "0.5.13"
+ , "winston" : "0.5.6"
}
, "scripts": {
"postinstall": "./scripts/generate_ephemeral_keys.sh",
View
22 resources/static/css/common.css
@@ -86,15 +86,15 @@ input[type=password] {
font-size: 14px;
padding: 5px;
border-width: 1px;
- border-style: solid;
- border-color: #A3A29D #C6C3B4 #C6C3B4 #A3A29D;
+ border-style: solid;
+ border-color: #A3A29D #C6C3B4 #C6C3B4 #A3A29D;
outline: none;
-
+
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
-
+
-webkit-box-shadow: 1px 1px 0 rgba(255,255,255,0.5);
-moz-box-shadow: 1px 1px 0 rgba(255,255,255,0.5);
-o-box-shadow: 1px 1px 0 rgba(255,255,255,0.5);
@@ -104,12 +104,12 @@ input[type=password] {
input[type=email]:focus,
input[type=password]:focus {
border: 1px solid #549FDC;
-
+
-webkit-border-radius: 0;
-moz-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
-
+
-webkit-box-shadow: 0 0 0 1px #549FDC inset;
-moz-box-shadow: 0 0 0 1px #549FDC inset;
-o-box-shadow: 0 0 0 1px #549FDC inset;
@@ -140,17 +140,17 @@ button,
text-shadow: -1px -1px 0 #37A6FF;
text-transform: lowercase;
cursor: pointer;
-
+
-webkit-box-shadow: 0 0 0 1px #76C2FF inset;
-moz-box-shadow: 0 0 0 1px #76C2FF inset;
-o-box-shadow: 0 0 0 1px #76C2FF inset;
box-shadow: 0 0 0 1px #76C2FF inset;
-
+
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
-
+
background-color: #37A6FF;
background-image: -moz-linear-gradient(center top , #76C2FF 0pt, #37A6FF 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #76C2FF), color-stop(100%, #37A6FF));
@@ -172,12 +172,12 @@ button:active,
border: 1px solid #003E70;
color: #EEEEEE;
text-shadow: -1px -1px 0 #006EC6;
-
+
-webkit-box-shadow: 0 0 5px #003763 inset;
-moz-box-shadow: 0 0 5px #003763 inset;
-o-box-shadow: 0 0 5px #003763 inset;
box-shadow: 0 0 5px #003763 inset;
-
+
background-image: -moz-linear-gradient(center top , #3AA7FF 0%, #006EC6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #3AA7FF), color-stop(100%, #006EC6));
}
View
10 resources/static/css/m.css
@@ -42,7 +42,7 @@
background-color: #549FDC;
color: #fff;
display: block;
-
+
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-o-border-radius: 3px;
@@ -67,7 +67,7 @@
}
#header ul.nav li:first-child a {
-
+
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
-o-border-radius: 3px 0 0 3px;
@@ -110,7 +110,7 @@
position: relative;
margin: 0 20px 20px;
top: auto;
-
+
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
@@ -139,7 +139,7 @@
#legal {
padding: 20px;
text-align: left;
- }
+ }
#legal li {
list-style-position: inside;
@@ -199,7 +199,7 @@
margin: 0 0 36px;
}
- #emails .email,
+ #emails .email,
#emails .activity {
display: block;
width: 260px;
View
121 resources/static/css/style.css
@@ -178,6 +178,28 @@ div.steps {
padding: 75px 125px;
}
+#newuser {
+ background-color: #faca33;
+ line-height: 32px;
+ border-radius: 4px;
+ margin-bottom: 20px;
+ text-align: center;
+ color: #626160;
+ text-shadow: 1px 1px 0 rgba(255,255,255,0.5);
+ height: 0;
+ opacity: 0;
+}
+
+.newuser #newuser {
+ height: 32px;
+ opacity: 1;
+ -webkit-transition: all 500ms;
+ -moz-transition: all 500ms;
+ -ms-transition: all 500ms;
+ -o-transition: all 500ms;
+ transition: all 500ms;
+}
+
#manage {
padding: 75px;
}
@@ -249,45 +271,36 @@ div.steps {
font-weight: normal;
}
-#manage .edit {
- margin: 72px 0 14px;
+#manage section {
+ margin-bottom: 20px;
+}
+
+.buttonrow {
+ margin: 0 0 14px;
}
-#manageAccounts,
-#cancelManage {
- float: right;
+.buttonrow > h2 {
display: inline-block;
- text-align: center;
- line-height: 21px;
- font-weight: bold;
- width: 48px;
- font-size: 12px;
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
- color: #fff;
+ font-size: 1em;
+}
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- -o-border-radius: 5px;
- border-radius: 5px;
+#manage button {
+ line-height: 20px;
+ height: 24px;
+ font-size: 12px;
}
-#manageAccounts {
- background-color: #37A6FF;
- border: 1px solid #37A6FF;
- text-shadow: -1px -1px 0 #37A6FF;
- cursor: pointer;
+.buttonrow > button {
+ width: 48px;
+}
- -webkit-box-shadow: 0 0 0 1px #76C2FF inset;
- -moz-box-shadow: 0 0 0 1px #76C2FF inset;
- -o-box-shadow: 0 0 0 1px #76C2FF inset;
- box-shadow: 0 0 0 1px #76C2FF inset;
+.buttonrow > .edit { }
- background-image: -moz-linear-gradient(#76C2FF 0pt, #37A6FF 100%);
- background-image: -o-linear-gradient(#76C2FF 0pt, #37A6FF 100%);
- background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #76C2FF), color-stop(100%, #37A6FF));
+.edit .buttonrow > .edit {
+ display: none;
}
-#cancelManage {
+.buttonrow > .done {
display: none;
background-color: #006EC6;
border: 1px solid #003E70;
@@ -304,9 +317,12 @@ div.steps {
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #3AA7FF), color-stop(100%, #006EC6));
}
+.edit .buttonrow > .done {
+ display: inline-block;
+}
+
#manage #emailList {
list-style-type: none;
- margin: 0 0 72px 0;
border-top: 1px solid #eee;
}
@@ -318,7 +334,7 @@ div.steps {
}
#emailList .email,
-#emailList .activity {
+.activity {
display: inline-block;
float: left;
white-space: nowrap;
@@ -328,19 +344,19 @@ div.steps {
width: 275px;
}
-#emailList .activity {
+.activity {
width: 275px;
color: #aaa;
text-align: right;
}
-#emailList .activity button,
-#emailList .activity abbr {
+.activity button,
+.activity abbr {
float: right;
display: inline-block;
}
-#emailList .activity button {
+.activity button {
width: 60px;
margin-left: 10px;
margin-right: -70px;
@@ -351,16 +367,13 @@ div.steps {
transition: margin 0.4s ease;
}
-#emailList.remove .activity button {
+.edit .activity button {
margin-right: 0;
}
button.delete {
background-color: #EA7676;
- height: 24px;
- vertical-align: middle;
border: 1px solid #B13D3D;
- font-size: 12px;
font-weight: bold;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #fff;
@@ -398,6 +411,32 @@ button.delete:active {
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #C84343), color-stop(100%, #AA3D3D));
}
+#edit_password {
+ margin-bottom: 10px;
+}
+
+#edit_password label {
+ width: 40%;
+ display: inline-block;
+}
+
+#edit_password input[type=password] {
+ width: 40%;
+}
+
+.showedit {
+ opacity: 0;
+ -webkit-transition: all 500ms;
+ -moz-transition: all 500ms;
+ -ms-transition: all 500ms;
+ -o-transition: all 500ms;
+ transition: all 500ms;
+}
+
+.edit .showedit {
+ opacity: 1;
+}
+
#disclaimer {
color: #888;
text-align: right;
@@ -664,7 +703,7 @@ a.forgot {
}
-header {
+#wrapper > header {
position: absolute;
top: 0;
font-weight: bold;
@@ -706,7 +745,7 @@ header a.signIn:hover, header a.signOut:hover {
display: inline;
}
-header,
+#wrapper > header,
footer {
display: block;
width: 100%;
View
178 resources/static/dialog/controllers/actions.js
@@ -0,0 +1,178 @@
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
+/*global _: true, BrowserID: true, PageController: 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 ***** */
+BrowserID.Modules.Actions = (function() {
+ "use strict";
+
+ var bid = BrowserID,
+ sc,
+ serviceManager = bid.module,
+ user = bid.User,
+ errors = bid.Errors,
+ runningService,
+ onsuccess,
+ onerror;
+
+ function startService(name, options) {
+ // Only one service outside of the main dialog allowed.
+ if(runningService) {
+ serviceManager.stop(runningService);
+ }
+ runningService = name;
+ return serviceManager.start(name, options);
+ }
+
+ function startRegCheckService(email, verifier, message) {
+ this.confirmEmail = email;
+
+ var controller = startService("check_registration", {
+ email: email,
+ verifier: verifier,
+ verificationMessage: message
+ });
+ controller.startCheck();
+ }
+
+ var Module = bid.Modules.PageModule.extend({
+ start: function(data) {
+ var self=this;
+
+ data = data || {};
+
+ onsuccess = data.onsuccess;
+ onerror = data.onerror;
+
+ sc.start.call(self, data);
+
+ if(data.ready) {
+ data.ready();
+ }
+ },
+
+ /**
+ * Show an error message
+ * @method doError
+ * @param {string} [template] - template to use, if not given, use "error"
+ * @param {object} [info] - info to send to template
+ */
+ doError: function(template, info) {
+ if(!info) {
+ info = template;
+ template = "error";
+ }
+ this.renderError(template, info);
+ },
+
+ doOffline: function() {
+ this.renderError("offline", {});
+ },
+
+ doCancel: function() {
+ onsuccess && onsuccess(null);
+ },
+
+ doConfirmUser: function(email) {
+ startRegCheckService.call(this, email, "waitForUserValidation", "user_confirmed");
+ },
+
+ doPickEmail: function(info) {
+ startService("pick_email", info);
+ },
+
+ doAddEmail: function() {
+ startService("add_email", {});
+ },
+
+ doAuthenticate: function(info) {
+ startService("authenticate", info);
+ },
+
+ doAuthenticateWithRequiredEmail: function(info) {
+ startService("required_email", info);
+ },
+
+ doForgotPassword: function(info) {
+ startService("forgot_password", info);
+ },
+
+ doConfirmEmail: function(email) {
+ startRegCheckService.call(this, email, "waitForEmailValidation", "email_confirmed");
+ },
+
+ doEmailConfirmed: function() {
+ var self=this;
+ // yay! now we need to produce an assertion.
+ user.getAssertion(self.confirmEmail, function(assertion) {
+ self.publish("assertion_generated", {
+ assertion: assertion
+ });
+ }, self.getErrorDialog(errors.getAssertion));
+ },
+
+ doAssertionGenerated: 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.
+ onerror = null;
+ onsuccess && onsuccess(assertion);
+ },
+
+ doNotMe: function() {
+ var self=this;
+ user.logoutUser(self.publish.bind(self, "logged_out"), self.getErrorDialog(errors.logoutUser));
+ },
+
+ doSyncThenPickEmail: function(options) {
+ var self = this;
+ user.syncEmails(self.doPickEmail.bind(self, options),
+ self.getErrorDialog(errors.syncEmails));
+ },
+
+ doCheckAuth: function() {
+ var self=this;
+ user.checkAuthenticationAndSync(function onSuccess() {}, function onComplete(authenticated) {
+ self.publish("authentication_checked", {
+ authenticated: authenticated
+ });
+ }, self.getErrorDialog(errors.checkAuthentication));
+ }
+
+ });
+
+ sc = Module.sc;
+
+ return Module;
+}());
View
26 resources/static/dialog/controllers/addemail.js
@@ -38,31 +38,26 @@ BrowserID.Modules.AddEmail = (function() {
"use strict";
var bid = BrowserID,
- user = bid.User,
helpers = bid.Helpers,
dialogHelpers = helpers.Dialog,
+ cancelEvent = dialogHelpers.cancelEvent,
errors = bid.Errors,
tooltip = bid.Tooltip;
- function cancelEvent(event) {
- event && event.preventDefault();
- }
-
- function addEmail(event) {
+ function addEmail(callback) {
var email = helpers.getAndValidateEmail("#newEmail"),
self=this;
- cancelEvent(event);
-
if (email) {
- dialogHelpers.addEmail.call(self, email);
+ dialogHelpers.addEmail.call(self, email, callback);
+ }
+ else {
+ callback && callback();
}
}
- function cancelAddEmail(event) {
- cancelEvent(event);
-
+ function cancelAddEmail() {
this.close("cancel_state");
}
@@ -72,12 +67,15 @@ BrowserID.Modules.AddEmail = (function() {
self.renderDialog("addemail");
- self.bind("#cancelNewEmail", "click", cancelAddEmail);
+ self.bind("#cancelNewEmail", "click", cancelEvent(cancelAddEmail));
AddEmail.sc.start.call(self, options);
},
- submit: addEmail,
+ submit: addEmail
+ // BEGIN TESTING API
+ ,
addEmail: addEmail,
cancelAddEmail: cancelAddEmail
+ // END TESTING API
});
return AddEmail;
View
37 resources/static/dialog/controllers/authenticate.js
@@ -45,6 +45,7 @@ BrowserID.Modules.Authenticate = (function() {
tooltip = bid.Tooltip,
helpers = bid.Helpers,
dialogHelpers = helpers.Dialog,
+ cancelEvent = dialogHelpers.cancelEvent,
dom = bid.DOM,
lastEmail = "";
@@ -52,12 +53,10 @@ BrowserID.Modules.Authenticate = (function() {
return helpers.getAndValidateEmail("#email");
}
- function checkEmail(event) {
+ function checkEmail() {
var email = getEmail(),
self = this;
- cancelEvent(event);
-
if (!email) return;
user.isEmailRegistered(email, function onComplete(registered) {
@@ -70,24 +69,22 @@ BrowserID.Modules.Authenticate = (function() {
}, self.getErrorDialog(errors.isEmailRegistered));
}
- function createUser(event) {
+ function createUser(callback) {
var self=this,
email = getEmail();
- cancelEvent(event);
-
if (email) {
- dialogHelpers.createUser.call(self, email);
+ dialogHelpers.createUser.call(self, email, callback);
+ } else {
+ callback && callback();
}
}
- function authenticate(event) {
+ function authenticate() {
var email = getEmail(),
pass = helpers.getAndValidatePassword("#password"),
self = this;
- cancelEvent(event);
-
if (email && pass) {
dialogHelpers.authenticateUser.call(self, email, pass, function(authenticated) {
if (authenticated) {
@@ -106,10 +103,6 @@ BrowserID.Modules.Authenticate = (function() {
});
}
- function cancelEvent(event) {
- if (event) event.preventDefault();
- }
-
function enterEmailState(el) {
if (!el.is(":disabled")) {
this.submit = checkEmail;
@@ -117,8 +110,7 @@ BrowserID.Modules.Authenticate = (function() {
}
}
- function enterPasswordState(event) {
- cancelEvent(event);
+ function enterPasswordState() {
var self=this;