Skip to content
This repository has been archived by the owner on May 11, 2022. It is now read-only.

Commit

Permalink
chore: migrate to using bcrypt for passwords
Browse files Browse the repository at this point in the history
Due to ranisalt/node-argon2#37, I've had to
abandon argon2 in favor of bcrypt for now.  The code is migrated, but
the login helper tests still need to be updated appropriately.
  • Loading branch information
jniles committed Mar 17, 2016
1 parent 1454605 commit 9c390fb
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 32 deletions.
1 change: 1 addition & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ machine:

dependencies:
pre:
- npm install -g node-gyp
- npm install -g bower babel
- bower install
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"author": "jniles",
"license": "MIT",
"dependencies": {
"argon2": "^0.10.1",
"bcrypt": "^0.8.5",
"bluebird": "^3.3.3",
"body-parser": "^1.15.0",
"compression": "^1.6.1",
Expand Down
10 changes: 7 additions & 3 deletions server/controllers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
* @todo - migrate this to using passportjs and JSON Web Tokens based on the invitation email.
*/

import bcrypt from 'bcrypt';
import Promise from 'bluebird';
import db from '../lib/db';
import { Unauthorized, Forbidden, NotFound } from '../lib/errors';
import argon from 'argon2';
import logger from '../lib/logger';

// create async functions in the bcrypt library
Promise.promisifyAll(bcrypt);

/**
* Basic password-based authentication for users that are registered with the system. Passwords
* are stored securely using the Argon2 password hashing library for NodeJS, and verified using
Expand All @@ -35,8 +39,8 @@ export async function login(req, res, next) {
throw new Unauthorized(`Bad username and password combination for ${req.body.username}`);
}

// passwords are hashed with argon and random salt. Use argon to verify correctness.
const bool = await argon.verify(user.password, req.body.password);
// passwords are hashed with bcrypt, using a salt. We can verify them with a simple hash
const bool = await bcrypt.compareAsync(req.body.password, user.password);

if (!bool) {
throw new Unauthorized(`Bad username and password combination for ${req.body.username}`);
Expand Down
13 changes: 8 additions & 5 deletions server/controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
*/

import express from 'express';
import Promise from 'bluebird';
import path from 'path';
import workerpool from 'workerpool';
import uuid from 'node-uuid';
import argon from 'argon2';
import bcrypt from 'bcrypt';

import db from '../lib/db';
import mailer from '../lib/mailer';
import logger from '../lib/logger';
import { NotFound } from '../lib/errors';

Promise.promisifyAll(bcrypt);

// default to 5 seconds of timeout
const pool = workerpool.pool(path.join(__dirname, '../lib/RSAWorker.js'));

Expand Down Expand Up @@ -79,11 +82,11 @@ export async function create(req, res, next) {

logger.verbose(`Found invitation for ${invitation.email}. Hashing password...`);

// generate a cryptographically secure salt using the argon2 library
const salt = await argon.generateSalt();
// generate a cryptographically secure salt using bcrypt
const salt = await bcrypt.genSaltAsync(13);

// hash the user's password argon2 before verifying
const hash = await argon.hash(data.password, salt);
// hash the user's password with bcrypt
const hash = await bcrypt.hashAsync(data.password, salt);

logger.verbose(`Password hash: ${hash}.`);
logger.verbose(`Creating user: ${data.username}.`);
Expand Down
4 changes: 2 additions & 2 deletions server/lib/db/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ INSERT INTO subproject (projectid, label) VALUES
(1, "Empowerment"),
(1, "Atelier/Conference");


-- password is 'password' hashed with bcrypt
INSERT INTO user (username, displayname, email, password, roleid, hidden, projectid, signatureid) VALUES
("admin", "Adminstrator", "developers@imaworldhealth.org", "$argon2i$m=4096,t=3,p=1$B4cjObbiYdN1IHXLFhq2Mg$RR5DMmvjU3bBY328K/SkTuC0yER7kD1tVN3AN7mfY9E", 1, 1, 1, 1);
("admin", "Adminstrator", "developers@imaworldhealth.org", "$2a$10$RjH4nNRIR4A4uw.iBIhPMexfiRuJDIG1lF4lwp3wYabYzavBj5qL.", 1, 1, 1, 1);
4 changes: 3 additions & 1 deletion server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ import { handler } from './lib/errors';
/** create the server */
const server = express();

server.use((req, res, next) => { console.log(req.ip, req.path); next(); });

/** (pre authentication) */
server.use(middleware);

server.post('/auth/basic', auth.login);
server.post('/auth/logout', auth.logout);

// make sure so unauthorized requests can get through
// server.use(auth.gateway);
server.use(auth.gateway);

// bind controllers
server.use(controllers);
Expand Down
29 changes: 21 additions & 8 deletions test/api/auth/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import request from 'supertest-as-promised';
import {} from './_env';
import * as helpers from '../../helpers/helpers';

let agent = null;
/** the url for this test suite */
const url = '/auth/basic';

/**
* Before the test suite, start the server and connect the database. Also
* sets up the agent for sharing cookie information.
* Before the test suite, start the server and connect the database. In this case, we wish to
* avoid sharing cookies, so we create a new request using the server exported from the helpers
* module.
*/
test.before(async t => {
agent = await helpers.setup();
try {
await helpers.setup();
} catch (e) {
throw e;
}
});

test('auth:failure:username', async t => {
Expand All @@ -26,7 +31,7 @@ test('auth:failure:username', async t => {
password: 'password',
};

const res = await agent.post(url).send(user);
const res = await request(helpers.app).post(url).send(user);

const description =
`Bad username and password combination for ${user.username}`;
Expand All @@ -43,7 +48,7 @@ test('auth:failure:password', async t => {
password: 'password1', // the real password is 'password'
};

const res = await agent.post(url).send(user);
const res = await request(helpers.app).post(url).send(user);

const description =
`Bad username and password combination for ${user.username}`;
Expand All @@ -60,7 +65,7 @@ test('auth:failure:both', async t => {
password: 'garbage',
};

const res = await agent.post(url).send(user);
const res = await request(helpers.app).post(url).send(user);

const description =
`Bad username and password combination for ${user.username}`;
Expand All @@ -69,6 +74,14 @@ test('auth:failure:both', async t => {
t.is(res.body.description, description);
});

test('auth:forbidden', async t => {
t.plan(1);

// useing vouchers api just for kicks
const res = await request(helpers.app).get('/vouchers');
t.is(res.status, 403);
});

test('auth:success', async t => {
t.plan(5);

Expand All @@ -77,7 +90,7 @@ test('auth:success', async t => {
password: 'password',
};

const res = await agent.post(url).send(user);
const res = await request(helpers.app).post(url).send(user);

t.is(res.status, 200);
t.is(res.body.username, 'admin');
Expand Down
1 change: 1 addition & 0 deletions test/helpers/_env.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ process.env.SESS_SECRET = 'S4meT3stS3cr3t';
process.env.LOG_LEVEL = 'debug';
process.env.MAILGUN_DOMAIN = 'mg.somedomain.com';
process.env.MAILGUN_KEY = 'key-somerandomhash';
process.env.KEY = 'WengeTestKey';
28 changes: 16 additions & 12 deletions test/helpers/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,28 @@ async function database(server) {
}

/**
* Logs the agent into the application as needed.
* Setups up tests by initializing a server and database connection.
*/
export async function login(agent) {
export async function setup() {
const agent = request.agent(app);

const user = {
username: 'admin',
password: 'password',
};

const res = await agent.post('/auth/login').send(user);
return agent;
}

/**
* Setups up tests by initializing a server and database connection.
*/
export async function setup() {
await database(app);
try {
await database(app);
console.log('before');
await agent.post('/auth/basic').send(user);
console.log('after');
} catch (e) {
throw e;
}

// return the agent for usage in subsequent tests
return request.agent(app);
return agent;
}

/** re-export app for consumption in auth */
export { app };

0 comments on commit 9c390fb

Please sign in to comment.