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

Feature/1677 update password policy #1698

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
12,286 changes: 5,990 additions & 6,296 deletions packages/coinstac-api-server/package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions packages/coinstac-api-server/seed/populate.js
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ async function populateUsers() {
[CONSORTIA_IDS[0]]: 'none',
[CONSORTIA_IDS[1]]: 'none',
},
passwordChangedAt: new Date(),
}, password);

await helperFunctions.createUser({
Expand All @@ -886,6 +887,7 @@ async function populateUsers() {
[CONSORTIA_IDS[0]]: 'none',
[CONSORTIA_IDS[1]]: 'none',
},
passwordChangedAt: new Date(),
}, password);

await helperFunctions.createUser({
Expand All @@ -911,6 +913,7 @@ async function populateUsers() {
[CONSORTIA_IDS[0]]: 'none',
[CONSORTIA_IDS[1]]: 'none',
},
passwordChangedAt: new Date(),
}, password);

await helperFunctions.createUser({
Expand All @@ -936,6 +939,7 @@ async function populateUsers() {
[CONSORTIA_IDS[0]]: 'none',
[CONSORTIA_IDS[1]]: 'none',
},
passwordChangedAt: new Date(),
}, password);

await helperFunctions.createUser({
Expand All @@ -961,6 +965,7 @@ async function populateUsers() {
[CONSORTIA_IDS[0]]: 'none',
[CONSORTIA_IDS[1]]: 'none',
},
passwordChangedAt: new Date(),
}, password);

await helperFunctions.createUser({
Expand All @@ -982,6 +987,7 @@ async function populateUsers() {
author: false,
},
},
passwordChangedAt: new Date(),
}, password);

const adminPassword = await helperFunctions.hashPassword(process.argv[3]
Expand All @@ -1007,6 +1013,7 @@ async function populateUsers() {
[CONSORTIA_IDS[0]]: 'none',
[CONSORTIA_IDS[1]]: 'none',
},
passwordChangedAt: new Date(),
}, adminPassword);
}

Expand Down
82 changes: 76 additions & 6 deletions packages/coinstac-api-server/src/auth-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const { eventEmitter, USER_CHANGED } = require('./data/events');

sgMail.setApiKey(process.env.SENDGRID_API_KEY);

const passwordLifeTime = 180;

const audience = 'coinstac';
const issuer = 'coinstac';
const subject = 'coinstac';
Expand Down Expand Up @@ -84,6 +86,7 @@ const helperFunctions = {
pipelines: {},
},
consortiaStatuses: user.consortiaStatuses || {},
passwordChangedAt: new Date(),
};

const db = database.getDbInstance();
Expand Down Expand Up @@ -341,11 +344,21 @@ const helperFunctions = {
req.payload.password, user.passwordHash
);

if (passwordMatch) {
return h.response(transformToClient(user));
if (!passwordMatch) {
return Boom.unauthorized('Incorrect username or password');
}

const { passwordChangedAt } = user;
const currentDate = new Date();
const difference = Math.ceil(
(currentDate.getTime() - passwordChangedAt.getTime()) / (1000 * 3600 * 24)
);

if (difference >= passwordLifeTime) {
return Boom.unauthorized('Password is expired');
}

return Boom.unauthorized('Incorrect username or password');
return h.response(transformToClient(user));
},
/**
* Validate api key used by headless client
Expand Down Expand Up @@ -407,12 +420,42 @@ const helperFunctions = {
}
},
/**
* Reset password
* @param {object} password token for resetting password
* Confirms that submitted token is valid
* @param {object} req request
* @param {object} res response
* @return {object} The requested object
*/
async validateResetPassword(req, h) {
const db = database.getDbInstance();
const user = await db.collection('users').findOne({ username: req.payload.username });

if (!user) {
return Boom.badRequest('Invalid user');
}

const passwordMatch = await helperFunctions.verifyPassword(
req.payload.currentPassword, user.passwordHash
);

if (!passwordMatch) {
return Boom.badRequest('Current Password is not correct');
}

const isPasswordValid = helperFunctions.validatePasswordWithRegEx(req.payload.newPassword);

if (!isPasswordValid) {
return Boom.badRequest('New Password is not valid');
}

return h.response();
},
/**
* Reset forgot password
* @param {object} token token for resetting password
* @param {object} password new password
* @return {object}
*/
async resetPassword(token, password) {
async resetForgotPassword(token, password) {
const db = database.getDbInstance();

const newPassword = await helperFunctions.hashPassword(password);
Expand All @@ -423,6 +466,29 @@ const helperFunctions = {
$set: {
passwordHash: newPassword,
passwordResetToken: '',
passwordChangedAt: new Date(),
},
}, {
returnOriginal: false,
});
},
/**
* Reset password
* @param {object} username username
* @param {object} password new password
* @return {object}
*/
async resetPassword(username, password) {
const db = database.getDbInstance();

const newPassword = await helperFunctions.hashPassword(password);

return db.collection('users').updateOne({
username,
}, {
$set: {
passwordHash: newPassword,
passwordChangedAt: new Date(),
},
}, {
returnOriginal: false,
Expand Down Expand Up @@ -499,6 +565,10 @@ const helperFunctions = {
return Boom.badRequest('invalid user/run combination');
}
},
validatePasswordWithRegEx(password) {
const PASSWORD_PATTERN = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[#?!@$%^&*-]).{8,}$/g;
return PASSWORD_PATTERN.test(password);
},
audience,
issuer,
subject,
Expand Down
21 changes: 19 additions & 2 deletions packages/coinstac-api-server/src/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ module.exports = [
{ method: helperFunctions.validateUniqueUser },
],
handler: async (req, h) => {
const isPasswordValid = helperFunctions.validatePassword(req.payload.password);
if (!isPasswordValid) {
return h.response('Invalid password').code(400);
}
const passwordHash = await helperFunctions.hashPassword(req.payload.password);
const user = await helperFunctions.createUser(req.payload, passwordHash);
const {
Expand Down Expand Up @@ -141,13 +145,26 @@ module.exports = [
},
{
method: 'POST',
path: '/resetPassword',
path: '/resetForgotPassword',
config: {
auth: false,
pre: [{ method: helperFunctions.validateResetToken }],
handler: (req, h) => {
return helperFunctions
.resetPassword(req.payload.token, req.payload.password)
.resetForgotPassword(req.payload.token, req.payload.password)
.then(() => h.response().code(204));
},
},
},
{
method: 'POST',
path: '/resetPassword',
config: {
auth: false,
pre: [{ method: helperFunctions.validateResetPassword }],
handler: (req, h) => {
return helperFunctions
.resetPassword(req.payload.username, req.payload.newPassword)
.then(() => h.response().code(204));
},
},
Expand Down
6 changes: 3 additions & 3 deletions packages/coinstac-api-server/tests/resolvers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ test('decodeToken', (t) => {
t.is(decoded.id, username);
});

test('createPasswordResetToken, savePasswordResetToken, validateResetToken and resetPassword', async (t) => {
test('createPasswordResetToken, savePasswordResetToken, validateResetToken and resetForgotPassword', async (t) => {
/* createPasswordResetToken */
const email = '[email protected]';
const token = helperFunctions.createPasswordResetToken(email);
Expand Down Expand Up @@ -133,9 +133,9 @@ test('createPasswordResetToken, savePasswordResetToken, validateResetToken and r
t.is(error.message, INVALID_EMAIL);
jwt.verify.restore();

/* resetPassword */
/* resetForgotPassword */
const newPassword = 'newPassword';
await helperFunctions.resetPassword(token, newPassword);
await helperFunctions.resetForgotPassword(token, newPassword);
const res = await helperFunctions.getUserDetailsByID(USER_IDS[0]);
const isValid = await helperFunctions.verifyPassword(newPassword, res.passwordHash);
t.true(isValid);
Expand Down
8 changes: 4 additions & 4 deletions packages/coinstac-api-server/tests/routes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ test('sendPasswordResetEmail', async (t) => {
});


test('resetPassword', async (t) => {
const createAccount = find(authRoutes, { path: '/resetPassword' });
test('resetForgotPassword', async (t) => {
const createAccount = find(authRoutes, { path: '/resetForgotPassword' });

const req = {
payload: {
Expand All @@ -235,7 +235,7 @@ test('resetPassword', async (t) => {
},
};

sinon.stub(helperFunctions, 'resetPassword').resolves();
sinon.stub(helperFunctions, 'resetForgotPassword').resolves();

const res = {
response: () => ({
Expand All @@ -247,7 +247,7 @@ test('resetPassword', async (t) => {

await createAccount.config.handler(req, res);

helperFunctions.resetPassword.restore();
helperFunctions.resetForgotPassword.restore();
});

test('version', (t) => {
Expand Down
Loading