diff --git a/browserid/app.js b/browserid/app.js index f6a7d9c1b2de32440de3e593365e05980a909d05..6224de3f492d5c75fd05c066f32500e962606ddb 100644 --- a/browserid/app.js +++ b/browserid/app.js @@ -1,20 +1,21 @@ -const fs = require('fs'), - path = require('path'); +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) { }; const - 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'), - express = require('express'), - secrets = require('./lib/secrets.js'), - db = require('./lib/db.js') +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'), +express = require('express'), +secrets = require('./lib/secrets.js'), +db = require('./lib/db.js') // looks unused, see run.js // const STATIC_DIR = path.join(path.dirname(__dirname), "static"); @@ -37,11 +38,11 @@ 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 ){ - res.render('dialog.ejs', { - title: 'A Better Way to Sign In', - layout: false, - production: exports.production - }); + res.render('dialog.ejs', { + title: 'A Better Way to Sign In', + layout: false, + production: exports.production + }); }); // simple redirects (internal for now) @@ -52,9 +53,9 @@ function router(app) { // 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(); - }); + 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}); @@ -92,19 +93,19 @@ function router(app) { wsapi.setup(app); app.get('/users/:identity.xml', function(req, resp, next) { - webfinger.renderUserPage(req.params.identity, function (resultDocument) { - if (resultDocument === undefined) { - httputils.fourOhFour(resp, "I don't know anything about: " + req.params.identity + "\n"); - } else { - httputils.xmlResponse(resp, resultDocument); - } - }); + webfinger.renderUserPage(req.params.identity, function (resultDocument) { + if (resultDocument === undefined) { + httputils.fourOhFour(resp, "I don't know anything about: " + req.params.identity + "\n"); + } else { + httputils.xmlResponse(resp, resultDocument); + } }); + }); app.get('/code_update', function(req, resp, next) { - console.log("code updated. shutting down."); - process.exit(); - }); + console.log("code updated. shutting down."); + process.exit(); + }); }; exports.varDir = VAR_DIR; @@ -135,17 +136,17 @@ exports.setup = function(server) { // 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 = {}; + if (typeof req.session == 'undefined') + req.session = {}; - 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'); - } - - next(); - }); + 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'); + } + + next(); + }); // a tweak to get the content type of host-meta correct server.use(function(req, resp, next) { @@ -157,22 +158,22 @@ exports.setup = function(server) { // prevent framing server.use(function(req, resp, next) { - resp.setHeader('x-frame-options', 'DENY'); - next(); - }); + resp.setHeader('x-frame-options', 'DENY'); + next(); + }); // check CSRF token 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); - } + // 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); } + } - next(); - }); + next(); + }); // add the actual URL handlers other than static router(server); } diff --git a/browserid/lib/db.js b/browserid/lib/db.js index 8181f9041c10ee1da658d3d16665d5e08814d237..3ec55a06e723747709bba6a94f0b5e8fe55f11cb 100644 --- a/browserid/lib/db.js +++ b/browserid/lib/db.js @@ -1,5 +1,6 @@ -const sqlite = require('sqlite'), - path = require('path'); +const +sqlite = require('sqlite'), +path = require('path'); var VAR_DIR = path.join(path.dirname(__dirname), "var"); @@ -351,14 +352,14 @@ exports.removeEmail = function(authenticated_email, email, cb) { }; exports.cancelAccount = function(authenticated_email, cb) { - emailToUserID(authenticated_email, function(user_id) { - executeTransaction([ - [ "delete from emails where user = ?", [ user_id ] ] , - [ "delete from keys where email in (select address from emails where user = ?)", [ user_id ] ], - [ "delete from users where id = ?", [ user_id ] ], - ], function (error) { - if (error) cb(error); - else cb(); - }); + emailToUserID(authenticated_email, function(user_id) { + executeTransaction([ + [ "delete from emails where user = ?", [ user_id ] ] , + [ "delete from keys where email in (select address from emails where user = ?)", [ user_id ] ], + [ "delete from users where id = ?", [ user_id ] ], + ], function (error) { + if (error) cb(error); + else cb(); }); + }); }; diff --git a/browserid/lib/email.js b/browserid/lib/email.js index a31e9358470675f2585572993c3908d36584c78a..19fb5d40bdcb7347137dd78f83ce8f04a1319cd5 100644 --- a/browserid/lib/email.js +++ b/browserid/lib/email.js @@ -1,23 +1,24 @@ -const db = require('./db'), - emailer = require('nodemailer'), - fs = require('fs'), - path = require('path'), +const +db = require('./db'), +emailer = require('nodemailer'), +fs = require('fs'), +path = require('path'), mustache = require('mustache'); const template = fs.readFileSync(path.join(__dirname, "prove_template.txt")).toString(); exports.sendVerificationEmail = function(email, site, secret) { - var url = "https://browserid.org/prove?token=" + encodeURIComponent(secret); + var url = "https://browserid.org/prove?token=" + encodeURIComponent(secret); - emailer.send_mail({ - sender: "noreply@browserid.org", - to: email, - subject : "Complete Login to " + site + " using BrowserID", - body: mustache.to_html(template, { email: email, link: url, site: site }) - }, function(err, success){ - if(!success) { - console.log("error sending email: ", err); - console.log("verification URL: ", url); - } - }); + emailer.send_mail({ + sender: "noreply@browserid.org", + to: email, + subject : "Complete Login to " + site + " using BrowserID", + body: mustache.to_html(template, { email: email, link: url, site: site }) + }, function(err, success){ + if(!success) { + console.log("error sending email: ", err); + console.log("verification URL: ", url); + } + }); }; diff --git a/browserid/lib/httputils.js b/browserid/lib/httputils.js index 669cc049449a9958eee9958726404a01ea5fd0f8..96e8a30c906dd86ae08559daeff6373626d6bddd 100644 --- a/browserid/lib/httputils.js +++ b/browserid/lib/httputils.js @@ -43,10 +43,10 @@ exports.xmlResponse = function(resp, doc) }; exports.checkGetArgs = function(req, args) { - [ "email", "pass", "pubkey" ].forEach(function(k) { - if (!urlobj.hasOwnProperty(k) || typeof urlobj[k] !== 'string') { - throw k; - } - }); + [ "email", "pass", "pubkey" ].forEach(function(k) { + if (!urlobj.hasOwnProperty(k) || typeof urlobj[k] !== 'string') { + throw k; + } + }); }; diff --git a/browserid/lib/secrets.js b/browserid/lib/secrets.js index bcf03f302ee054abf58feecdd6fdefc1fefb30f6..7aca40b986e7e82366fd531e22421f17f57b7dff 100644 --- a/browserid/lib/secrets.js +++ b/browserid/lib/secrets.js @@ -1,5 +1,6 @@ -const path = require('path'), - fs = require('fs'); +const +path = require('path'), +fs = require('fs'); function generateSecret() { var str = ""; diff --git a/browserid/lib/webfinger.js b/browserid/lib/webfinger.js index e66f09b3e7a47f2d31028df02449402c18c0c311..f79045b48db076db08c603613dffcece05119064 100644 --- a/browserid/lib/webfinger.js +++ b/browserid/lib/webfinger.js @@ -1,7 +1,8 @@ -const db = require('./db.js'), - fs = require('fs'), - mustache = require('mustache'), - path = require('path'); +const +db = require('./db.js'), +fs = require('fs'), +mustache = require('mustache'), +path = require('path'); const TEMPLATE = fs.readFileSync(path.join(__dirname, "webfinger_template.xml")).toString(); diff --git a/browserid/lib/wsapi.js b/browserid/lib/wsapi.js index 469796eb789a70b58fc24eb28d7ed964bc30b994..70157827ae3679a5cea427e59077204af81b6f74 100644 --- a/browserid/lib/wsapi.js +++ b/browserid/lib/wsapi.js @@ -3,11 +3,12 @@ // now we're using proper express function registration to deal // with HTTP methods and the like, apply middleware, etc. -const db = require('./db.js'), - url = require('url'), - httputils = require('./httputils.js'); - email = require('./email.js'), - bcrypt = require('bcrypt'); +const +db = require('./db.js'), +url = require('url'), +httputils = require('./httputils.js'); +email = require('./email.js'), +bcrypt = require('bcrypt'); function checkParams(params) { return function(req, resp, next) { @@ -17,13 +18,13 @@ function checkParams(params) { } else { params_in_request = req.query; } - + try { params.forEach(function(k) { - if (!params_in_request.hasOwnProperty(k) || typeof params_in_request[k] !== 'string') { - throw k; - } - }); + if (!params_in_request.hasOwnProperty(k) || typeof params_in_request[k] !== 'string') { + throw k; + } + }); } catch(e) { console.log("error : " + e.toString()); return httputils.badRequest(resp, "missing '" + e + "' argument"); @@ -52,196 +53,196 @@ function setup(app) { /* checks to see if an email address is known to the server * takes 'email' as a GET argument */ app.get('/wsapi/have_email', function(req, resp) { - // get inputs from get data! - var email = url.parse(req.url, true).query['email']; - db.emailKnown(email, function(known) { - httputils.jsonResponse(resp, known); - }); + // get inputs from get data! + var email = url.parse(req.url, true).query['email']; + db.emailKnown(email, function(known) { + httputils.jsonResponse(resp, known); }); - + }); + /* First half of account creation. Stages a user account for creation. * this involves creating a secret url that must be delivered to the * user via their claimed email address. Upon timeout expiry OR clickthrough * the staged user account transitions to a valid user account */ app.post('/wsapi/stage_user', checkParams([ "email", "pass", "pubkey", "site" ]), function(req, resp) { - - // bcrypt the password - // we should be cloning this object here. - var stageParams = req.body; - stageParams['hash'] = bcrypt.encrypt_sync(stageParams.pass, bcrypt.gen_salt_sync(10)); - - try { - // upon success, stage_user returns a secret (that'll get baked into a url - // and given to the user), on failure it throws - var secret = db.stageUser(stageParams); - - // store the email being registered in the session data - if (!req.session) req.session = {}; - - // store inside the session the details of this pending verification - req.session.pendingVerification = { - email: stageParams.email, - hash: stageParams.hash // we must store both email and password to handle the case where - // a user re-creates an account - specifically, registration status - // must ensure the new credentials work to properly verify that - // the user has clicked throught the email link. note, this salted, bcrypted - // representation of a user's password will get thrust into an encrypted cookie - // served over an encrypted (SSL) session. guten, yah. - }; - - httputils.jsonResponse(resp, true); - // let's now kick out a verification email! - email.sendVerificationEmail(stageParams.email, stageParams.site, secret); + // bcrypt the password + // we should be cloning this object here. + var stageParams = req.body; + stageParams['hash'] = bcrypt.encrypt_sync(stageParams.pass, bcrypt.gen_salt_sync(10)); - } catch(e) { - // we should differentiate tween' 400 and 500 here. - httputils.badRequest(resp, e.toString()); - } - }); + try { + // upon success, stage_user returns a secret (that'll get baked into a url + // and given to the user), on failure it throws + var secret = db.stageUser(stageParams); - app.get('/wsapi/registration_status', function(req, resp) { - if (!req.session || - (!(typeof req.session.pendingVerification === 'object') && - !(typeof req.session.pendingAddition === 'string'))) - { - httputils.badRequest(resp, "api abuse: registration_status called without a pending email addition/verification"); - return; - } + // store the email being registered in the session data + if (!req.session) req.session = {}; - // Is the current session trying to add an email, or register a new one? - if (req.session.pendingAddition) { - // this is a pending email addition, it requires authentication - if (!isAuthed(req, resp)) { - return httputils.badRequest(resp, "requires authentication"); - } + // store inside the session the details of this pending verification + req.session.pendingVerification = { + email: stageParams.email, + hash: stageParams.hash // we must store both email and password to handle the case where + // a user re-creates an account - specifically, registration status + // must ensure the new credentials work to properly verify that + // the user has clicked throught the email link. note, this salted, bcrypted + // representation of a user's password will get thrust into an encrypted cookie + // served over an encrypted (SSL) session. guten, yah. + }; - // check if the currently authenticated user has the email stored under pendingAddition - // in their acct. - db.emailsBelongToSameAccount(req.session.pendingAddition, - req.session.authenticatedUser, - function(registered) { - if (registered) { - delete req.session.pendingAddition; - httputils.jsonResponse(resp, "complete"); - } else { - httputils.jsonResponse(resp, "pending"); - } - }); - } else { - // this is a pending registration, let's check if the creds stored on the - // session are good yet. - - var v = req.session.pendingVerification; - db.checkAuth(v.email, function(hash) { - if (hash === v.hash) { - delete req.session.pendingVerification; - req.session.authenticatedUser = v.email; - httputils.jsonResponse(resp, "complete"); - } else { - httputils.jsonResponse(resp, "pending"); - } - }); + httputils.jsonResponse(resp, true); + + // let's now kick out a verification email! + email.sendVerificationEmail(stageParams.email, stageParams.site, secret); + + } catch(e) { + // we should differentiate tween' 400 and 500 here. + httputils.badRequest(resp, e.toString()); + } + }); + + app.get('/wsapi/registration_status', function(req, resp) { + if (!req.session || + (!(typeof req.session.pendingVerification === 'object') && + !(typeof req.session.pendingAddition === 'string'))) + { + httputils.badRequest(resp, "api abuse: registration_status called without a pending email addition/verification"); + return; + } + + // Is the current session trying to add an email, or register a new one? + if (req.session.pendingAddition) { + // this is a pending email addition, it requires authentication + if (!isAuthed(req, resp)) { + return httputils.badRequest(resp, "requires authentication"); } - }); + + // check if the currently authenticated user has the email stored under pendingAddition + // in their acct. + db.emailsBelongToSameAccount(req.session.pendingAddition, + req.session.authenticatedUser, + function(registered) { + if (registered) { + delete req.session.pendingAddition; + httputils.jsonResponse(resp, "complete"); + } else { + httputils.jsonResponse(resp, "pending"); + } + }); + } else { + // this is a pending registration, let's check if the creds stored on the + // session are good yet. + + var v = req.session.pendingVerification; + db.checkAuth(v.email, function(hash) { + if (hash === v.hash) { + delete req.session.pendingVerification; + req.session.authenticatedUser = v.email; + httputils.jsonResponse(resp, "complete"); + } else { + httputils.jsonResponse(resp, "pending"); + } + }); + } + }); app.post('/wsapi/authenticate_user', checkParams(["email", "pass"]), function(req, resp) { - db.checkAuth(req.body.email, function(hash) { - var success = bcrypt.compare_sync(req.body.pass, hash); - - if (success) { - if (!req.session) req.session = {}; - req.session.authenticatedUser = req.body.email; - } - httputils.jsonResponse(resp, success); - }); - }); - - app.post('/wsapi/add_email', checkAuthed, checkParams(["email", "pubkey", "site"]), function (req, resp) { - try { - // upon success, stage_user returns a secret (that'll get baked into a url - // and given to the user), on failure it throws - var secret = db.stageEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey); - - // store the email being added in session data - req.session.pendingAddition = req.body.email; - - httputils.jsonResponse(resp, true); - - // let's now kick out a verification email! - email.sendVerificationEmail(req.body.email, req.body.site, secret); - } catch(e) { - // we should differentiate tween' 400 and 500 here. - httputils.badRequest(resp, e.toString()); + db.checkAuth(req.body.email, function(hash) { + var success = bcrypt.compare_sync(req.body.pass, hash); + + if (success) { + if (!req.session) req.session = {}; + req.session.authenticatedUser = req.body.email; } + httputils.jsonResponse(resp, success); }); + }); + + app.post('/wsapi/add_email', checkAuthed, checkParams(["email", "pubkey", "site"]), function (req, resp) { + try { + // upon success, stage_user returns a secret (that'll get baked into a url + // and given to the user), on failure it throws + var secret = db.stageEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey); + + // store the email being added in session data + req.session.pendingAddition = req.body.email; + + httputils.jsonResponse(resp, true); + + // let's now kick out a verification email! + email.sendVerificationEmail(req.body.email, req.body.site, secret); + } catch(e) { + // we should differentiate tween' 400 and 500 here. + httputils.badRequest(resp, e.toString()); + } + }); app.post('/wsapi/remove_email', checkAuthed, checkParams(["email"]), function(req, resp) { - var email = req.body.email; - - db.removeEmail(req.session.authenticatedUser, email, function(error) { - if (error) { - console.log("error removing email " + email); - httputils.badRequest(resp, error.toString()); - } else { - httputils.jsonResponse(resp, true); - }}); - }); + var email = req.body.email; + + db.removeEmail(req.session.authenticatedUser, email, function(error) { + if (error) { + console.log("error removing email " + email); + httputils.badRequest(resp, error.toString()); + } else { + httputils.jsonResponse(resp, true); + }}); + }); app.post('/wsapi/account_cancel', checkAuthed, function(req, resp) { - db.cancelAccount(req.session.authenticatedUser, function(error) { - if (error) { - console.log("error cancelling account : " + error.toString()); - httputils.badRequest(resp, error.toString()); - } else { - httputils.jsonResponse(resp, true); - }}); - }); + db.cancelAccount(req.session.authenticatedUser, function(error) { + if (error) { + console.log("error cancelling account : " + error.toString()); + httputils.badRequest(resp, error.toString()); + } else { + httputils.jsonResponse(resp, true); + }}); + }); app.post('/wsapi/set_key', checkAuthed, checkParams(["email", "pubkey"]), function (req, resp) { - db.addKeyToEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey, function (rv) { - httputils.jsonResponse(resp, rv); - }); + db.addKeyToEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey, function (rv) { + httputils.jsonResponse(resp, rv); }); + }); app.get('/wsapi/am_authed', function(req,resp) { - // if they're authenticated for an email address that we don't know about, - // then we should purge the stored cookie - if (!isAuthed(req)) { - httputils.jsonResponse(resp, false); - } else { - db.emailKnown(req.session.authenticatedUser, function (known) { - if (!known) req.session = {} - httputils.jsonResponse(resp, known); - }); - } - }); + // if they're authenticated for an email address that we don't know about, + // then we should purge the stored cookie + if (!isAuthed(req)) { + httputils.jsonResponse(resp, false); + } else { + db.emailKnown(req.session.authenticatedUser, function (known) { + if (!known) req.session = {} + httputils.jsonResponse(resp, known); + }); + } + }); app.post('/wsapi/logout', function(req,resp) { - req.session = {}; - httputils.jsonResponse(resp, "ok"); - }); + req.session = {}; + httputils.jsonResponse(resp, "ok"); + }); app.post('/wsapi/sync_emails', checkAuthed, function(req,resp) { - var emails = req.body.emails; + var emails = req.body.emails; - db.getSyncResponse(req.session.authenticatedUser, emails, function(err, syncResponse) { - if (err) httputils.serverError(resp, err); - else httputils.jsonResponse(resp, syncResponse); - }); + db.getSyncResponse(req.session.authenticatedUser, emails, function(err, syncResponse) { + if (err) httputils.serverError(resp, err); + else httputils.jsonResponse(resp, syncResponse); }); + }); app.get('/wsapi/prove_email_ownership', checkParams(["token"]), function(req, resp) { - db.gotVerificationSecret(req.query.token, function(e) { - if (e) { - console.log("error completing the verification: " + e); - httputils.jsonResponse(resp, false); - } else { - httputils.jsonResponse(resp, true); - } - }); + db.gotVerificationSecret(req.query.token, function(e) { + if (e) { + console.log("error completing the verification: " + e); + httputils.jsonResponse(resp, false); + } else { + httputils.jsonResponse(resp, true); + } }); + }); } exports.setup = setup; diff --git a/browserid/run.js b/browserid/run.js index 6e5697ba0e0123806d74fdb99a8a709da245c97c..94f0583debcecda53aa6332d0d9b2ea0652be6c2 100755 --- a/browserid/run.js +++ b/browserid/run.js @@ -1,8 +1,8 @@ #!/usr/bin/env node var path = require("path"), - fs = require("fs"), - express = require("express"); +fs = require("fs"), +express = require("express"); const amMain = (process.argv[1] === __filename);