diff --git a/README.md b/README.md index 6a4d5d34985171a3e4995de6718d2c3d4ff525f2..78616d91bf808211c18b08f23f717826068e39d7 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ All of the servers here are based on node.js, and some number of 3rd party node * cookie-sessions (>= 0.0.2) * nodemailer (>= 0.1.18) * emavows (>= 0.5.8) +* bcrypt (>= 0.2.3) ## Getting started: diff --git a/browserid/lib/db.js b/browserid/lib/db.js index c300d40fad83be3875be78c344102322380a6e02..84bf966592eebf29b9367c634779b57930a83432 100644 --- a/browserid/lib/db.js +++ b/browserid/lib/db.js @@ -1,5 +1,6 @@ const sqlite = require('sqlite'), - path = require('path'); + path = require('path'), + bcrypt = require('bcrypt'); var VAR_DIR = path.join(path.dirname(__dirname), "var"); @@ -106,7 +107,7 @@ exports.isStaged = function(email) { function generateSecret() { var str = ""; const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (var i=0; i < 32; i++) { + for (var i=0; i < 48; i++) { str += alphabet.charAt(Math.floor(Math.random() * alphabet.length)); } return str; @@ -168,16 +169,18 @@ exports.addKeyToEmail = function(existing_email, email, pubkey, cb) { }); } -/* takes an argument object including email, pass, and pubkey. */ +/* takes an argument object including email, password hash, and pubkey. */ exports.stageUser = function(obj) { var secret = generateSecret(); + // overwrite previously staged users g_staged[secret] = { type: "add_account", email: obj.email, pubkey: obj.pubkey, - pass: obj.pass + pass: obj.hash }; + g_stagedEmails[obj.email] = secret; return secret; }; @@ -241,15 +244,23 @@ exports.gotVerificationSecret = function(secret, cb) { } }; -/* takes an argument object including email, pass, and pubkey. */ exports.checkAuth = function(email, pass, cb) { - db.execute("SELECT users.id FROM emails, users WHERE users.id = emails.user AND emails.address = ? AND users.password = ?", - [ email, pass ], + db.execute("SELECT users.password FROM emails, users WHERE users.id = emails.user AND emails.address = ?", + [ email ], + function (error, rows) { + cb(rows.length === 1 && bcrypt.compare_sync(pass, rows[0].password)); + }); +}; + +exports.checkAuthHash = function(email, hash, cb) { + db.execute("SELECT users.password FROM emails, users WHERE users.id = emails.user AND emails.address = ? AND users.password = ?", + [ email, hash ], function (error, rows) { cb(rows.length === 1); }); }; + /* a high level operation that attempts to sync a client's view with that of the * server. email is the identity of the authenticated channel with the user, * identities is a map of email -> pubkey. diff --git a/browserid/lib/wsapi.js b/browserid/lib/wsapi.js index b6c42e47ba0ba7fde12e57308b616966745902bd..f76acfdf374275bbfb7c030442e5de113011c767 100644 --- a/browserid/lib/wsapi.js +++ b/browserid/lib/wsapi.js @@ -5,15 +5,7 @@ const db = require('./db.js'), url = require('url'), httputils = require('./httputils.js'); email = require('./email.js'), - crypto = require('crypto'); - -// md5 is used to obfuscate passwords simply so we don't store -// users passwords in plaintext anywhere -function obfuscatePassword(pass) { - var hash = crypto.createHash('sha256'); - hash.update(pass); - return hash.digest('base64'); -} + bcrypt = require('bcrypt'); function checkParams(getArgs, resp, params) { try { @@ -63,7 +55,8 @@ exports.stage_user = function(req, resp) { return; } - getArgs.pass = obfuscatePassword(getArgs.pass); + // bcrypt the password + getArgs.hash = bcrypt.encrypt_sync(getArgs.pass, bcrypt.gen_salt_sync(4)) try { // upon success, stage_user returns a secret (that'll get baked into a url @@ -76,9 +69,12 @@ exports.stage_user = function(req, resp) { // store inside the session the details of this pending verification req.session.pendingVerification = { email: getArgs.email, - pass: getArgs.pass // that's an obfuscated password now stored in encrypted session data. - // we must store both email and password to handle the case where - // a user re-creates an account + hash: getArgs.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); @@ -128,7 +124,7 @@ exports.registration_status = function(req, resp) { // session are good yet. var v = req.session.pendingVerification; - db.checkAuth(v.email, v.pass, function(authed) { + db.checkAuthHash(v.email, v.hash, function(authed) { if (authed) { delete req.session.pendingVerification; req.session.authenticatedUser = v.email; @@ -146,8 +142,6 @@ exports.authenticate_user = function(req, resp) { if (!checkParams(getArgs, resp, [ "email", "pass" ])) return; - getArgs.pass = obfuscatePassword(getArgs.pass); - db.checkAuth(getArgs.email, getArgs.pass, function(rv) { if (rv) { if (!req.session) req.session = {};