Skip to content

Commit

Permalink
feature(reset-password): create password reset logic
Browse files Browse the repository at this point in the history
- If user provides password, set it as his account password and email
user, else set a random password and email user

[Finishes #165839919]
  • Loading branch information
chidimo committed May 23, 2019
1 parent 833ca49 commit 6f3ce50
Show file tree
Hide file tree
Showing 13 changed files with 341 additions and 94 deletions.
2 changes: 1 addition & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ app.use(express.static(path.join(__dirname, 'public')));
app.use(validator());
app.use(cors('*'));

app.use('/', indexRouter);
app.use('/api/v1', indexRouter);

// catch 404 and forward to error handler
// app.use((req, res, next) => {
Expand Down
46 changes: 45 additions & 1 deletion controllers/UsersController.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,57 @@
import generatePassword from 'password-generator';
import Model from '../models/Model';
import { InternalServerError } from '../utils/errorHandlers';
import {
get_existing_user, check_user_exists, update_if_exists
get_existing_user,
check_user_exists,
update_if_exists,
check_password,
update_pass,
sendPassword
} from './helpers/AuthController';
import { aws_signed_url, } from './helpers/UsersController';

const users_model = new Model('users');

const UsersController = {
reset_password: async (req, res) => {
const { email } = req.params;
const { current_password, confirm_new, new_pass } = req.body;

const remember_password = (
(current_password !== '') &&
(new_pass !== '') &&
(confirm_new !== '')
);

const clause = `WHERE email='${email}'`;
try {
const exists = await check_user_exists(users_model, clause, res);
if (exists) {
if (remember_password) {
const knows_pass = await check_password(
users_model, email, current_password, res);

if (knows_pass) {
await update_pass(users_model, new_pass, clause, res);
sendPassword(email, new_pass);
return res.status(204)
.json({ message: 'Password has been emailed to you.' });
}
return res.status(404)
.json({ error: 'You entered an incorrect password' });
}
const new_password = generatePassword();
await update_pass(users_model, new_password, clause, res);
sendPassword(email, new_password);
return res.status(204).json({ message: 'Password has been emailed to you.' });
}
return res.status(404)
.json({ error: `User with email ${email} not found` });
}
catch (e) { return; }
},

confirm_account: async (req, res) => {
try {

Expand Down
25 changes: 22 additions & 3 deletions controllers/helpers/AuthController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import titlecase from 'titlecase';

import { InternalServerError } from '../../utils/errorHandlers';
import sendEmail from '../../utils/sendEmail';
import { async } from 'rxjs/internal/scheduler/async';
import hashPassword from '../../utils/hashPassword';

export const sendSignUpMessage = (user, req) => {
const path = `/users/${user.id}/account-confirmation`;
Expand All @@ -22,6 +22,18 @@ export const sendSignUpMessage = (user, req) => {
return;
};

export const sendPassword = (email, new_password) => {
const template_data = {
new_password,
};
const data = {
email,
template_name: 'new_password',
};
sendEmail(data, template_data);
return;
};

export const check_user_exists = async (model_instance, clause, res) => {
try {
const { rows } = await model_instance.select(
Expand All @@ -46,13 +58,12 @@ export const check_password = async (model_instance, email, password, res) => {

export const add_user_to_db = async (model_instance, req, res) => {
const { email, password, firstname, lastname } = req.body;
const hashedPassword = bcrypt.hashSync(password, 8);

try {
return await model_instance.insert_with_return(
'(email, firstname, lastname, password)',
`'${email}', '${firstname}', '${lastname}',
'${hashedPassword}'`
'${hashPassword(password)}'`
);
}
catch (e) { return InternalServerError(res, e);}
Expand Down Expand Up @@ -86,3 +97,11 @@ export const update_if_exists = async (model_instance,
}
catch (e) { return; }
};

export const update_pass = async (model_instance, password, clause, res) => {
try {
await model_instance.update(
`password='${hashPassword(password)}'`, clause);
}
catch (e) { return InternalServerError(res, e); }
};
64 changes: 45 additions & 19 deletions middleware/validators.users.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import { body } from 'express-validator/check';
import { sanitizeBody } from 'express-validator/filter';
import validate_error_or_next from './validate_error_or_next';
import titlecase from 'titlecase';

const validate_password = field => (
body(field)
.not().isEmpty().withMessage('Password is required')
.isLength({ min: 8 }).trim()
.withMessage('Password must be at least 8 characters')
.isLength({ max: 16 })
.withMessage('Password must be at most 16 characters')
.isAlphanumeric().withMessage('Password must be alphanumeric')
);

const validate_name = field => (
body(field)
.not().isEmpty().withMessage(`${titlecase(field)} is required`)
);

const UsersValidators = {
validateNames: [
body('firstname')
.not().isEmpty().withMessage('First name is required'),
body('lastname')
.not().isEmpty().withMessage('Last name is required'),
validate_name('firstname'),
validate_name('lastname'),
sanitizeBody('firstname').trim().escape(),
sanitizeBody('lastname').trim().escape()

],

emailValidator: [
Expand All @@ -23,13 +36,7 @@ const UsersValidators = {
],

passwordValidator: [
body('password')
.not().isEmpty().withMessage('Password is required')
.isLength({ min: 8 }).trim()
.withMessage('Password must be at least 8 characters')
.isLength({ max: 16 })
.withMessage('Password must be at most 16 characters')
.isAlphanumeric().withMessage('Password must be alphanumeric'),
validate_password('password'),
sanitizeBody('password').trim().escape(),
sanitizeBody('confirm_password').trim().escape(),
validate_error_or_next
Expand All @@ -48,19 +55,38 @@ const UsersValidators = {
validate_error_or_next
],

newPasswordValidator: [
validate_password('current_password')
.optional({ checkFalsy: true })
.custom((value, { req }) => {
if (req.body.new_pass !== req.body.confirm_new) {
throw new Error(
'Password confirmation does not match password'
);
}
if (!req.body.new_pass) {
throw new Error('Please enter a new password');
}
else return value;
}),
validate_password('new_pass')
.optional({ checkFalsy: true }),
validate_password('confirm_new')
.optional({ checkFalsy: true }),
validate_error_or_next
],

updateProfileValidator: [
body('firstname')
.not().isEmpty().withMessage('First name cannot be empty'),
body('lastname')
.not().isEmpty().withMessage('Last namecannot be empty'),
validate_name('firstname'),
validate_name('lastname'),
body('phone')
.not().isEmpty().withMessage('Phone number cannot be empty')
.not().isEmpty().withMessage('Phone number is required')
.matches(/^0\d{10}$/).withMessage(
'Wrong number format: E.G. 07012345678'),
body('home')
.not().isEmpty().withMessage('Home address cannot be empty'),
.not().isEmpty().withMessage('Home address is required'),
body('office')
.not().isEmpty().withMessage('Office address cannot be empty'),
.not().isEmpty().withMessage('Office address is required'),

sanitizeBody('firstname').trim().escape(),
sanitizeBody('lastname').trim().escape(),
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"http-errors": "~1.6.2",
"jsonwebtoken": "^8.5.1",
"morgan": "~1.9.0",
"password-generator": "^2.2.0",
"pg": "^7.11.0",
"titlecase": "^1.1.3",
"underscore": "^1.9.1"
Expand Down
9 changes: 8 additions & 1 deletion routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ router.post('/auth/signup',
);

router.post('/auth/signin',
UsersValidators.emailValidator,
UsersValidators.passwordValidator,
AuthenticationMiddleware.generateToken,
AuthController.signin
Expand All @@ -41,8 +42,14 @@ router.get('/users/:id/photo/upload/',
router.patch('/users/:id/photo/update',
UsersController.update_photo_url
);
router.post('/users/:email/reset_password',
UsersValidators.newPasswordValidator,
UsersController.reset_password
);

router.get('/loans', LoansController.get_all_loans);
router.get('/loans',
AuthenticationMiddleware.verifyToken,
LoansController.get_all_loans);
router.get('/loans/:id', LoansController.get_loan);
router.get(
'/loans?status=approved&repaid=false', LoansController.get_all_loans);
Expand Down
1 change: 1 addition & 0 deletions settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const Settings = {
signatureVersion: 'v4'
},
skipTokenVerification: () => (process.env.NODE_ENV === 'test'),
skipEmailSend: () => (process.env.NODE_ENV === 'test'),
};

dev_logger(Settings);
Expand Down
Loading

0 comments on commit 6f3ce50

Please sign in to comment.