Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Password hash #65

Merged
merged 5 commits into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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