Skip to content

Commit

Permalink
Merge pull request #65 from dwyl/password-hash
Browse files Browse the repository at this point in the history
Password hash
  • Loading branch information
nelsonic authored Sep 12, 2018
2 parents 593dcec + 6348c5c commit a0fff25
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 51 deletions.
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
language: node_js
node_js:
- "6"
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
script: npm run coverage
before_script:
- psql -c 'CREATE DATABASE travis_ci_test;' -U postgres
Expand Down
6 changes: 4 additions & 2 deletions lib/config_validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ var fieldSchema = Joi.object()
;
var configSchema = Joi.object().keys({
table_name: Joi.string().regex(dbNameRegEx).required(), // eslint-disable-line
fields: Joi.object().pattern(dbNameRegEx, fieldSchema).required() // eslint-disable-line
});
fields: Joi.object().pattern(dbNameRegEx, fieldSchema).required(), // eslint-disable-line
salt_number: Joi.number().integer().min(1) // eslint-disable-line
})
.unknown();

module.exports = function (config) {
return Joi.assert(config, configSchema);
Expand Down
9 changes: 7 additions & 2 deletions lib/create_table_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ var mapObj = {
number: function (opts) {
return opts.integer ? 'BIGINT' : 'DOUBLE PRECISION';
},
string: function (opts) {
string: function (opts, name) {
var length = opts.max || 80;
var willBeHashed = name === 'password';

if (willBeHashed) {
length = 60; // http://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d
}

return 'VARCHAR(' + length + ')';
},
Expand All @@ -25,7 +30,7 @@ function mapper (name, type, options) {
constraints += ' CONSTRAINT ' + name + '_unique UNIQUE';
}

return name + ' ' + mapObj[type](opts) + constraints;
return name + ' ' + mapObj[type](opts, name) + constraints;
}

module.exports = mapper;
Expand Down
34 changes: 29 additions & 5 deletions lib/db.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
'use strict';

var bcrypt = require('bcrypt');

var sqlGen = require('./sql_gen.js');
var configValidator = require('./config_validator.js');
var utils = require('./utils.js');

var methods = {
init: function (client, config, _, cb) {
configValidator(config);

return client.query(sqlGen.init(config), cb);
},
insert: function (client, config, options, cb) {
var passwordValue = options.fields.password;
var saltNumber = config.salt_number || 10;
var tableName = config.table_name;
var makeQuery = function (opts) {
var args = sqlGen.insert(tableName, opts).concat([cb]);

return client.query.apply(client, args);
};

return bcrypt.hash(passwordValue, saltNumber, function (_, hash) {
var optionsCopy = utils.shallowCopy(options);

var methods = { init: function (client, config, _, cb) {
configValidator(config);
optionsCopy.fields = utils.shallowCopy(optionsCopy.fields);
optionsCopy.fields.password = hash;

return client.query(sqlGen.init(config), cb);
} };
return makeQuery(optionsCopy);
});
}
};

['select', 'update', 'delete', 'insert'].forEach(function (method) {
['select', 'update', 'delete'].forEach(function (method) {
methods[method] = function (client, config, options, cb) {
var tableName = config.table_name;
var args = sqlGen[method](tableName, options).concat([cb]);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"coverage"
],
"dependencies": {
"bcrypt": "^0.8.7",
"env2": "^2.1.1",
"hoek": "^4.1.0",
"joi": "^9.0.4",
Expand Down
34 changes: 32 additions & 2 deletions test/config_validator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,45 @@ test('config validator', function (t) {
table_name: 'test', // eslint-disable-line
fields: { email: {
type: 'string',
unknown: 'allowed'
} }
unknown: 'field'
} },
unknown: 'config'
}),
'no error when extra options unknown'
);

t.end();
});

test('salt_number', function (t) {
t.doesNotThrow(
validator({
table_name: 'test', // eslint-disable-line
fields: { },
salt_number: 1 // eslint-disable-line
}),
'1 min number of rounds'
);
t.throws(
validator({
table_name: 'test', // eslint-disable-line
fields: { },
salt_number: 1.4 // eslint-disable-line
}),
'salt number must be integer'
);
t.throws(
validator({
table_name: 'test', // eslint-disable-line
fields: { },
salt_number: 0 // eslint-disable-line
}),
'salt number higher than 0'
);

t.end();
});

test('dbNameRegEx', function (t) {
t.ok(
dbNameRegEx.exec('_a1pha_Numer1c'),
Expand Down
110 changes: 71 additions & 39 deletions test/db.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var test = require('tape');
var bcrypt = require('bcrypt');

var client = require('./test_pg_client.js');
var db = require('../lib/db.js');
Expand All @@ -9,7 +10,8 @@ var schema = require('./example_schema.js');
var testInsert = {
email: '[email protected]',
dob: '2001-09-27',
username: 'test'
username: 'test',
password: 'hash me'
};

test('init test client', function (t) {
Expand Down Expand Up @@ -39,48 +41,78 @@ test('db.init', function (t) {


test('db.insert & default select w custom where', function (t) {
db.insert(client, schema, { fields: testInsert })
.then(function () {
return db.select(client, schema, { where: { dob: '2001-09-27' } });
})
.then(function (res) {
t.equal(
res.rows[0].email,
testInsert.email,
'email correct'
);
t.equal(
res.rows[0].username,
testInsert.username,
'username correct'
);
t.equal(
res.rows[0].dob.toLocaleDateString('GMT'),
new Date(testInsert.dob).toLocaleDateString('GMT'),
'get same date back, though now a date object'
);
t.end();
})
.catch(function (err) {
t.fail(err);
t.end();
})
;
db.insert(client, schema, { fields: testInsert }, function () {
return db.select(client, schema, { where: { dob: '2001-09-27' } })
.then(function (res) {
t.equal(
res.rows[0].email,
testInsert.email,
'email correct'
);
t.equal(
res.rows[0].username,
testInsert.username,
'username correct'
);
t.ok(
bcrypt.compareSync(testInsert.password, res.rows[0].password),
'password hashed correctly'
);
t.equal(
res.rows[0].dob.toLocaleDateString('GMT'),
new Date(testInsert.dob).toLocaleDateString('GMT'),
'get same date back, though now a date object'
);
t.end();
})
.catch(function (err) {
t.fail(err);
t.end();
})
;
});
});

test('db.insert & default select w custom where', function (t) {
db.insert(client, schema, { fields: testInsert }, function () {
return db.select(client, schema, { where: { dob: '2001-09-27' } })
.then(function (res) {
t.equal(
res.rows[0].email,
testInsert.email,
'email correct'
);
t.equal(
res.rows[0].username,
testInsert.username,
'username correct'
);
t.ok(
bcrypt.compareSync(testInsert.password, res.rows[0].password),
'password hashed correctly'
);
t.equal(
res.rows[0].dob.toLocaleDateString('GMT'),
new Date(testInsert.dob).toLocaleDateString('GMT'),
'get same date back, though now a date object'
);
t.end();
})
.catch(function (err) {
t.fail(err);
t.end();
})
;
});
});

test('db.insert x 2 same username error', function (t) {
t.plan(1);
db.insert(client, schema, { fields: testInsert })
.then(function () {
return db.insert(client, schema, { fields: testInsert });
})
.then(function () {
t.fails('shouldn\'t allow second insert if unique key given');
})
.catch(function () {
t.pass('shouldn\'t allow second insert if unique key given');
})
;
db.insert(client, schema, { fields: testInsert }, function () {
return db.insert(client, schema, { fields: testInsert }, function (err) {
t.ok(err, 'shouldn\'t allow second insert if unique key given');
});
});
});

test('db.update w where & custom select w default where', function (t) {
Expand Down
5 changes: 5 additions & 0 deletions test/example_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ module.exports = {
max: 20,
unique: true
},
password: {
type: 'string',
min: 5,
max: 20
},
dob: { type: 'date' }
}
};
3 changes: 2 additions & 1 deletion test/sql_gen.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ tape('::init - generate SQL to create a table if none exists', function (t) {
'CREATE TABLE IF NOT EXISTS "user_data" ('
+ 'email VARCHAR(80), '
+ 'username VARCHAR(20) CONSTRAINT username_unique UNIQUE, '
+ 'password VARCHAR(60), '
+ 'dob DATE'
+ ')',
'Create table query generation from config object'
'Create table query generation from config object, pw length overwritten'
);
t.end();
});
Expand Down

0 comments on commit a0fff25

Please sign in to comment.