diff --git a/README.md b/README.md index 1ac3fe7610e83bb2433129778dbcd8e4c252299c..c6372048f053cf122c8a16745a5c8d88cf083e57 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ All of the servers here are based on node.js, and some number of 3rd party node * vows (>= 0.5.8) * bcrypt (>= 0.2.3) * ejs (>= 0.4.3) +* express-csrf (>= 0.3.2) ## Getting started: diff --git a/browserid/app.js b/browserid/app.js index c685e4915292b37d4f94e7d662ea80243517dda5..62193ab387997950e3e757946f2debe417b7678e 100644 --- a/browserid/app.js +++ b/browserid/app.js @@ -5,14 +5,16 @@ const fs = require('fs'), var VAR_DIR = path.join(__dirname, "var"); try { fs.mkdirSync(VAR_DIR, 0755); } catch(e) { }; -const url = require('url'), - wsapi = require('./lib/wsapi.js'), - httputils = require('./lib/httputils.js'), - webfinger = require('./lib/webfinger.js'), - sessions = require('cookie-sessions'), - express = require('express'), - secrets = require('./lib/secrets.js'), - db = require('./lib/db.js'); +const + url = require('url'), + wsapi = require('./lib/wsapi.js'), + httputils = require('./lib/httputils.js'), + webfinger = require('./lib/webfinger.js'), + sessions = require('cookie-sessions'), + express = require('express'), + secrets = require('./lib/secrets.js'), + db = require('./lib/db.js'), + csrf = require('express-csrf'); // looks unused, see run.js // const STATIC_DIR = path.join(path.dirname(__dirname), "static"); @@ -41,6 +43,7 @@ function router(app) { app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html')); app.get('/', function(req,res) { + console.log("CSRF: " + req.session.csrf); res.render('index.ejs', {title: 'A Better Way to Sign In', fullpage: true}); }); @@ -114,6 +117,13 @@ exports.setup = function(server) { server.use(express.bodyParser()); + // we make sure that everyone has a session, otherwise we can't do CSRF properly + server.use(function(req, resp, next) { + if (typeof req.session == 'undefined') + req.session = {}; + next(); + }); + // a tweak to get the content type of host-meta correct server.use(function(req, resp, next) { if (req.url === '/.well-known/host-meta') { @@ -128,6 +138,13 @@ exports.setup = function(server) { next(); }); + // setup CSRF protection + //server.use(csrf.check()); + + server.dynamicHelpers({ + csrf: csrf.token + }); + // add the actual URL handlers other than static router(server); } diff --git a/browserid/lib/wsapi.js b/browserid/lib/wsapi.js index 53c2096c4ab365acdd7de3d352a5a3d603f87386..57bccb035e72245d1b78649cf006a3017d9f4587 100644 --- a/browserid/lib/wsapi.js +++ b/browserid/lib/wsapi.js @@ -214,7 +214,7 @@ function setup(app) { } }); - app.get('/wsapi/logout', function(req,resp) { + app.post('/wsapi/logout', function(req,resp) { req.session = {}; httputils.jsonResponse(resp, "ok"); }); diff --git a/browserid/static/dialog/controllers/dialog_controller.js b/browserid/static/dialog/controllers/dialog_controller.js index 59b9e2b38516e2773525e51dfbe43a2201c9a51e..fe771e70f1ad53f078c7d436bf645e6b1ea0c81f 100644 --- a/browserid/static/dialog/controllers/dialog_controller.js +++ b/browserid/static/dialog/controllers/dialog_controller.js @@ -116,7 +116,7 @@ $.Controller("Dialog", {}, { "#notme click": function(event) { clearEmails(); var self = this; - $.get("/wsapi/logout", function() { + $.post("/wsapi/logout", function() { self.doAuthenticate(); }); }, diff --git a/browserid/tests/forgotten-email-test.js b/browserid/tests/forgotten-email-test.js index ecd259c8eabba3d83d9aa67187b1e77c244960ce..469978e75dd25862236dae90ff16a3de6e8afb5c 100755 --- a/browserid/tests/forgotten-email-test.js +++ b/browserid/tests/forgotten-email-test.js @@ -159,6 +159,12 @@ suite.addBatch({ assert.strictEqual(JSON.parse(r.body), true); } }, + "logout": { + topic: wsapi.post('/wsapi/logout', {}), + "should work": function(r, err) { + assert.strictEqual(JSON.parse(r.body), "ok"); + } + }, "second email, first pass good": { topic: wsapi.get('/wsapi/authenticate_user', { email: 'second@fakeemail.com', pass: 'firstfakepass' }), "should work": function(r, err) { diff --git a/browserid/tests/lib/wsapi.js b/browserid/tests/lib/wsapi.js index f4ff2c051518103dc7c8b548dd57efa952a50a61..2a15fc7b99c3ce44d0fa29735627109c06ca0303 100644 --- a/browserid/tests/lib/wsapi.js +++ b/browserid/tests/lib/wsapi.js @@ -46,3 +46,55 @@ exports.get = function (path, getArgs) { }); }; }; + +// FIXME: dedup code + +// A macro for wsapi requests +exports.post = function (path, postArgs) { + return function () { + var cb = this.callback; + if (typeof postArgs === 'object') + body = querystring.stringify(postArgs); + + var headers = { + 'content-type': 'application/x-www-form-urlencoded' + }; + + if (Object.keys(cookieJar).length) { + headers['Cookie'] = ""; + for (var k in cookieJar) { + headers['Cookie'] += k + "=" + cookieJar[k]; + } + } + + var req = http.request({ + host: '127.0.0.1', + port: '62700', + path: path, + headers: headers, + method: "POST" + }, function(res) { + // see if there are any set-cookies that we should honor + if (res.headers['set-cookie']) { + res.headers['set-cookie'].forEach(function(cookie) { + var m = /^([^;]+)(?:;.*)$/.exec(cookie); + if (m) { + var x = m[1].split('='); + cookieJar[x[0]] = x[1]; + } + }); + } + var body = ''; + res.on('data', function(chunk) { body += chunk; }) + .on('end', function() { + cb({code: res.statusCode, headers: res.headers, body: body}); + }); + }).on('error', function (e) { + cb(); + }); + + // send the POST + req.write(body); + req.end(); + }; +};