This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

integrating train 2011.08.18

  • Loading branch information...
shane-tomlinson committed Aug 25, 2011
2 parents a867ecb + c3356e3 commit de6afc97e12fee59b1613c69313448340726d2aa
Showing with 4,379 additions and 2,076 deletions.
  1. +2 −1 .gitignore
  2. +24 −0 ChangeLog
  3. +10 −2 DEPLOYMENT.md
  4. +5 −0 LICENSE
  5. +0 −1 README.md
  6. +127 −58 browserid/app.js
  7. +91 −369 browserid/lib/db.js
  8. +381 −0 browserid/lib/db_json.js
  9. +471 −0 browserid/lib/db_mysql.js
  10. +72 −12 browserid/lib/email.js
  11. +35 −0 browserid/lib/httputils.js
  12. +38 −3 browserid/lib/secrets.js
  13. +35 −0 browserid/lib/webfinger.js
  14. +112 −34 browserid/lib/wsapi.js
  15. +37 −5 browserid/run.js
  16. +7 −1 browserid/static/css/style.css
  17. +83 −0 browserid/static/dialog/controllers/addemail_controller.js
  18. +86 −0 browserid/static/dialog/controllers/authenticate_controller.js
  19. +117 −0 browserid/static/dialog/controllers/checkregistration_controller.js
  20. +71 −0 browserid/static/dialog/controllers/chooseemail_controller.js
  21. +171 −0 browserid/static/dialog/controllers/createaccount_controller.js
  22. +165 −464 browserid/static/dialog/controllers/dialog_controller.js
  23. +116 −0 browserid/static/dialog/controllers/forgotpassword_controller.js
  24. +135 −0 browserid/static/dialog/controllers/page_controller.js
  25. +48 −2 browserid/static/dialog/dialog.js
  26. +35 −3 browserid/static/dialog/register_iframe.js
  27. +87 −0 browserid/static/dialog/resources/browserid-errors.js
  28. +58 −0 browserid/static/dialog/resources/browserid-extensions.js
  29. +225 −0 browserid/static/dialog/resources/browserid-network.js
  30. +64 −0 browserid/static/dialog/resources/browserid-wait.js
  31. +35 −0 browserid/static/dialog/resources/channel.js
  32. +35 −0 browserid/static/dialog/resources/crypto-api.js
  33. +0 −781 browserid/static/dialog/resources/main.js
  34. +34 −0 browserid/static/dialog/resources/storage.js
  35. +14 −10 browserid/static/dialog/style.css
  36. +2 −2 browserid/static/dialog/views/addemail.ejs
  37. +1 −1 browserid/static/dialog/views/authenticate.ejs
  38. +11 −4 browserid/static/dialog/views/body.ejs
  39. +1 −1 browserid/static/dialog/views/bottom-addemail.ejs
  40. +1 −1 browserid/static/dialog/views/bottom-confirmemail.ejs
  41. +2 −2 browserid/static/dialog/views/bottom-continue.ejs
  42. +1 −1 browserid/static/dialog/views/bottom-pickemail.ejs
  43. +1 −1 browserid/static/dialog/views/bottom-signin.ejs
  44. +1 −1 browserid/static/dialog/views/bottom.ejs
  45. +4 −3 browserid/static/dialog/views/create.ejs
  46. +4 −3 browserid/static/dialog/views/forgotpassword.ejs
  47. +5 −7 browserid/static/dialog/views/signin.ejs
  48. +41 −6 browserid/static/include.js
  49. +63 −10 browserid/static/js/browserid.js
  50. +88 −47 browserid/tests/db-test.js
  51. +48 −5 browserid/tests/forgotten-email-test.js
  52. +0 −16 browserid/tests/lib/email-interceptor.js
  53. +46 −0 browserid/tests/lib/start-stop.js
  54. +47 −0 browserid/tests/lib/test_env.js
  55. +110 −71 browserid/tests/lib/wsapi.js
  56. +107 −0 browserid/tests/password-length-test.js
  57. +44 −6 browserid/tests/registration-status-wsapi-test.js
  58. +0 −3 browserid/views/manage.ejs
  59. +0 −3 browserid/views/prove.ejs
  60. +95 −30 libs/configuration.js
  61. +72 −43 libs/logging.js
  62. +117 −0 libs/metrics.js
  63. +35 −0 libs/substitute.js
  64. +4 −3 package.json
  65. +42 −16 run.js
  66. +4 −0 scripts/branch_train.sh
  67. +22 −0 scripts/merge_train.sh
  68. +16 −0 scripts/test_db_connectivity.js
  69. +23 −0 test.sh
  70. +0 −1 verifier/.gitignore
  71. +64 −23 verifier/app.js
  72. +35 −0 verifier/lib/httputils.js
  73. +55 −17 verifier/lib/idassertion.js
  74. +35 −0 verifier/lib/jwt.js
  75. +35 −0 verifier/lib/make_assertion.js
  76. +36 −3 verifier/run.js
  77. +35 −0 verifier/tests/run.js
View
@@ -1,4 +1,5 @@
*~
\#*\#
.\#*
-node_modules
+/node_modules
+/var
View
@@ -1,3 +1,27 @@
+train-2011.08.18:
+ * upon clickthrough of the email link, don't have the browser window close itself: #162
+ * passwords must be between 8 and 80 chars: #155
+ * improved handling of emailing & verification urls during local development & testing: #88
+ * language changes in dialog: #150
+ * many improvements to unit tests: #171
+ * forgotten password flow was broken with port to mysql, fixed: #170
+ * improved metrics reporting abstraction: #168
+ * moved all server logging into a single file: #169
+ * all files created at execution time are now in one location: #172
+ * developer ergonomics - improved colorized logging with terse webserver output to console
+ * always require a user to authenticate if they don't have an active session: #74
+ * improved CSRF protection to fix race conditions in previous train: #173
+
+train-2011.08.12:
+ * massive zero-user-visibile refactoring of dialog javascript.
+ * fix cancel button in "waiting for verification state" (issue #147)
+ * all browserid source is now tri-licensed (MPL1.1/GPL/LGPL). (issue #141)
+ * fixes for mobile firefox (fennec). (issue #140)
+ * mysql support implemented for browserid (default persistence production) (issue #71)
+ * json persistence support added - a standalone dead simple persistence layer which is the default for local development and requires no external software.
+ * email secrets are now persisted in the database, so upon server restart outstanding verification links are no longer invalidated (issue #91)
+ * (website) styling changes - like fix issues where links on dev page were being displayed white on white.
+
train-2011.08.04:
* when user closes dialog without clicking "cancel", properly return 'null' to the webpage (via getVerifiedEmail callback) - issue #107
* improve checks to warn developer that prerequisite software is missing. issue #110
View
@@ -85,9 +85,17 @@ Subsequent steps use different software which you might need to install.
* **curl** - used to iniate http requests from the cmd line (to kick the browserid server)
* **java** - used to minify css
- * **libsqlite3-dev** - database libraries
+ * **mysql 5.1+** - the preferred persistence backend
-### 4. Set up post-update hook
+### 4. Set up mysql
+
+ 0. ensure you can connect via TCP - localhost:3306 (like, make sure skip-networking is off in my.cnf)
+ 1. connect to the database as user root
+ 2. `CREATE USER 'browserid'@'localhost' IDENTIFIED BY 'browserid';`
+ 3. `CREATE DATABASE browserid;`
+ 4. `GRANT CREATE, DELETE, INDEX, INSERT, LOCK TABLES, SELECT, UPDATE ON browserid.* TO 'browserid'@'localhost';`
+
+### 5. Set up post-update hook
*This step is optional* - if you want to manually update code you
probably skipped step #1, you can skip this one as well. All you need
View
@@ -0,0 +1,5 @@
+This software is available under your choice of the following licenses:
+
+ * MPL 1.1 or later: http://www.mozilla.org/MPL/
+ * GPL 2.0 or later: http://www.gnu.org/licenses/gpl.html
+ * LGPL 2.1 or later: http://www.gnu.org/licenses/lgpl.html
View
@@ -18,7 +18,6 @@ Here's the software you'll need installed:
* node.js (>= 0.4.5): http://nodejs.org/
* npm: http://npmjs.org/
-* sqlite (3) development libraries: http://www.sqlite.org/
* Several node.js 3rd party libraries - see `package.json` for details
## Getting started:
View
@@ -1,28 +1,60 @@
-const
-fs = require('fs'),
-path = require('path');
-
-// create the var directory if it doesn't exist
-var VAR_DIR = path.join(__dirname, "var");
-try { fs.mkdirSync(VAR_DIR, 0755); } catch(e) { };
+/* ***** 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 ***** */
const
+fs = require('fs'),
+path = require('path'),
url = require('url'),
-crypto = require('crypto'),
wsapi = require('./lib/wsapi.js'),
httputils = require('./lib/httputils.js'),
webfinger = require('./lib/webfinger.js'),
-sessions = require('cookie-sessions'),
+sessions = require('connect-cookie-session'),
express = require('express'),
secrets = require('./lib/secrets.js'),
db = require('./lib/db.js'),
configuration = require('../libs/configuration.js'),
substitution = require('../libs/substitute.js');
-logging = require("../libs/logging.js");
+metrics = require("../libs/metrics.js"),
+logger = require("../libs/logging.js").logger;
+
+logger.info("browserid server starting up");
-// looks unused, see run.js
-// const STATIC_DIR = path.join(path.dirname(__dirname), "static");
-const COOKIE_SECRET = secrets.hydrateSecret('cookie_secret', VAR_DIR);
+// open the databse
+db.open(configuration.get('database'));
+
+const COOKIE_SECRET = secrets.hydrateSecret('browserid_cookie', configuration.get('var_path'));
const COOKIE_KEY = 'browserid_state';
function internal_redirector(new_url) {
@@ -33,7 +65,7 @@ function internal_redirector(new_url) {
}
function router(app) {
- app.set("views", __dirname + '/views');
+ app.set("views", __dirname + '/views');
app.set('view options', {
production: configuration.get('use_minified_resources')
@@ -42,7 +74,7 @@ function router(app) {
// this should probably be an internal redirect
// as soon as relative paths are figured out.
app.get('/sign_in', function(req, res, next ) {
- logging.userEntry('browserid', req);
+ metrics.userEntry(req);
res.render('dialog.ejs', {
title: 'A Better Way to Sign In',
layout: false,
@@ -53,15 +85,6 @@ function router(app) {
// simple redirects (internal for now)
app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html'));
- // return the CSRF token
- // IMPORTANT: this should be safe because it's only readable by same-origin code
- // but we must be careful that this is never a JSON structure that could be hijacked
- // by a third party
- app.get('/csrf', function(req, res) {
- res.write(req.session.csrf);
- res.end();
- });
-
app.get('/', function(req,res) {
res.render('index.ejs', {title: 'A Better Way to Sign In', fullpage: true});
});
@@ -83,7 +106,7 @@ function router(app) {
});
app.get(/^\/manage(\.html)?$/, function(req,res) {
- res.render('manage.ejs', {title: 'My Account', fullpage: false, csrf: req.session.csrf});
+ res.render('manage.ejs', {title: 'My Account', fullpage: false});
});
app.get(/^\/tos(\.html)?$/, function(req, res) {
@@ -108,46 +131,96 @@ function router(app) {
});
app.get('/code_update', function(req, resp, next) {
- console.log("code updated. shutting down.");
+ logger.warn("code updated. shutting down.");
process.exit();
});
};
-exports.varDir = VAR_DIR;
-
exports.setup = function(server) {
+ // request to logger, dev formatted which omits personal data in the requests
+ server.use(express.logger({
+ format: 'dev',
+ stream: {
+ write: function(x) {
+ logger.info(typeof x === 'string' ? x.trim() : x);
+ }
+ }
+ }));
+
+ // over SSL?
+ var overSSL = (configuration.get('scheme') == 'https');
+
server.use(express.cookieParser());
var cookieSessionMiddleware = sessions({
secret: COOKIE_SECRET,
- session_key: COOKIE_KEY,
- path: '/'
+ key: COOKIE_KEY,
+ cookie: {
+ path: '/wsapi',
+ httpOnly: true,
+ // IMPORTANT: we allow users to go 1 weeks on the same device
+ // without entering their password again
+ maxAge: (7 * 24 * 60 * 60 * 1000),
+ secure: overSSL
+ }
});
+ // cookie sessions
server.use(function(req, resp, next) {
- try {
- cookieSessionMiddleware(req, resp, next);
- } catch(e) {
- console.log("invalid cookie found: ignoring");
- delete req.cookies[COOKIE_KEY];
- cookieSessionMiddleware(req, resp, next);
+ // cookie sessions are only applied to calls to /wsapi
+ // as all other resources can be aggressively cached
+ // by layers higher up based on cache control headers.
+ // the fallout is that all code that interacts with sessions
+ // should be under /wsapi
+ if (/^\/wsapi/.test(req.url)) {
+ // we set this parameter so the connect-cookie-session
+ // sends the cookie even though the local connection is HTTP
+ // (the load balancer does SSL)
+ if (overSSL)
+ req.connection.proxySecure = true;
+
+ return cookieSessionMiddleware(req, resp, next);
+
+ } else {
+ return next();
}
});
server.use(express.bodyParser());
- // we make sure that everyone has a session, otherwise we can't do CSRF properly
+ // Check CSRF token early. POST requests are only allowed to
+ // /wsapi and they always must have a valid csrf token
server.use(function(req, resp, next) {
- if (typeof req.session == 'undefined')
- req.session = {};
+ // only on POSTs
+ if (req.method == "POST") {
+ var denied = false;
+ if (!/^\/wsapi/.test(req.url)) { // post requests only allowed to /wsapi
+ denied = true;
+ logger.warn("CSRF validation failure: POST only allowed to /wsapi urls. not '" + req.url + "'");
+ }
- 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');
- }
+ if (req.session === undefined) { // there must be a session
+ denied = true;
+ logger.warn("CSRF validation failure: POST calls to /wsapi require an active session");
+ }
+
+ // the session must have a csrf token
+ if (typeof req.session.csrf !== 'string') {
+ denied = true;
+ logger.warn("CSRF validation failure: POST calls to /wsapi require an csrf token to be set");
+ }
- next();
+ // and the token must match what is sent in the post body
+ if (req.body.csrf != req.session.csrf) {
+ denied = true;
+ // if any of these things are false, then we'll block the request
+ logger.warn("CSRF validation failure, token mismatch. got:" + req.body.csrf + " want:" + req.session.csrf);
+ }
+
+ if (denied) return httputils.badRequest(resp, "CSRF violation");
+
+ }
+ return next();
});
// a tweak to get the content type of host-meta correct
@@ -158,22 +231,18 @@ exports.setup = function(server) {
next();
});
- // prevent framing
- server.use(function(req, resp, next) {
- resp.setHeader('x-frame-options', 'DENY');
- next();
- });
-
- // check CSRF token
+ // Strict Transport Security
server.use(function(req, resp, next) {
- // only on POSTs
- if (req.method == "POST") {
- if (req.body.csrf != req.session.csrf) {
- // error, problem with CSRF
- throw new Error("CSRF violation - " + req.body.csrf + '/' + req.session.csrf);
+ if (overSSL) {
+ // expires in 30 days, include subdomains like www
+ resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains");
}
- }
+ next();
+ });
+ // prevent framing
+ server.use(function(req, resp, next) {
+ resp.setHeader('x-frame-options', 'DENY');
next();
});
Oops, something went wrong.

0 comments on commit de6afc9

Please sign in to comment.