Skip to content

Commit

Permalink
Merge pull request #15 from andela/feature/162727466/users-should-be-…
Browse files Browse the repository at this point in the history
…able-to-reset-password

#162727466 Users should be able to reset password
  • Loading branch information
Bamidele Daniel committed Jan 16, 2019
2 parents 571bb55 + 2324882 commit beb918c
Show file tree
Hide file tree
Showing 10 changed files with 556 additions and 167 deletions.
302 changes: 151 additions & 151 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"postinstall": "npm run build",
"db:migrate": "sequelize db:migrate",
"db:unmigrate": "sequelize db:migrate:undo:all",
"test": "NODE_ENV=test npm run db:unmigrate && NODE_ENV=test npm run db:migrate && NODE_ENV=test nyc mocha --exit --timeout 100000 --require @babel/register ./test/**/*.js",
"test": "NODE_ENV=test npm run db:unmigrate && NODE_ENV=test npm run db:migrate && NODE_ENV=test nyc mocha --exit --timeout 100000 --require @babel/register ./test/*",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"lint": "./node_modules/.bin/eslint ."
},
Expand Down
112 changes: 104 additions & 8 deletions src/controllers/UsersController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import TokenAuthenticate from '../helpers/TokenAuthenticate';
import Response from '../helpers/response';
import { passwordHash, comparePassword } from '../helpers/passwordHash';
import EmailNotificationAPI from '../helpers/EmailNotificationAPI';
import basePath from '../helpers/basePath';

const { User, Profile } = models;


let response;
const tokenExpireTime = '10hr';
const salt = 10;
/** Users Controller Class */
Expand Down Expand Up @@ -76,22 +76,22 @@ class UsersController {
});
const sendMail = await sendVerificationLink.sendEmail();
if (sendMail !== 'Message sent') {
response = new Response(
const response = new Response(
'Bad request',
400,
'There was a problem sending',
);
return res.status(response.code).json(response);
}
response = new Response(
const response = new Response(
'Ok',
201,
'User created successfully and verification link sent to your Email',
{ token }
);
return res.status(response.code).json(response);
} catch (err) {
response = new Response(
const response = new Response(
'Not ok',
500,
`${err}`,
Expand All @@ -113,15 +113,15 @@ class UsersController {
const { email, password } = req.body;
const user = await User.findOne({ where: { email } });
if (!user) {
response = new Response(
const response = new Response(
'Bad Request',
400,
'Invalid Credentials'
);
return res.status(response.code).json(response);
}
if (!comparePassword(password, user.password)) {
response = new Response(
const response = new Response(
'Bad Request',
400,
'Invalid Credentials'
Expand All @@ -136,22 +136,118 @@ class UsersController {
};
const token = await TokenAuthenticate
.generateToken(userDetails, tokenExpireTime);
response = new Response(
const response = new Response(
'Ok',
200,
'User logged in successfully',
{ token }
);
return res.status(response.code).json(response);
} catch (err) {
response = new Response(
const response = new Response(
'Not ok',
500,
`${err}`,
);
return res.status(response.code).json(response);
}
}

/**
* @static
* @desc POST /api/v1/auth/forgot-password
* @param {object} req
* @param {object} res
* @memberof UsersController
* @returns user reset password token
*/
static async forgotPassword(req, res) {
try {
const { checkEmail } = req;
const { email: recipient, username } = checkEmail;
const userDetails = { email: recipient, username };
const token = await TokenAuthenticate.generateToken(userDetails, '1hr');
const subject = 'Reset Password';
const path = basePath(req);
const linkPath = `${path}/api/v1/auth/forgot-password?token=${token}`;
const message = `<h3>Dear ${username}</h3><br>
<a href='${linkPath}'>
<button style='font-size: 20px; background: orange;'>
Reset Password
</button>
</a><br>
<p>Kindly click on the button above to reset your password.
This link will <strong>expire in 1 hour
</strong></p>
`;
const sendVerificationLink = new EmailNotificationAPI({
recipient,
subject,
message,
});
const sendMail = await sendVerificationLink.sendEmail();
if (sendMail !== 'Message sent') {
const response = new Response(
'Bad request',
400,
'There was a problem sending',
);
return res.status(response.code).json(response);
}
const response = new Response(
'Ok',
200,
'Email sent successfully',
{ token },
);
return res.status(response.code).json(response);
} catch (err) {
const response = new Response(
'Internal server error',
500,
`${err}`,
);
return res.status(response.code).json(response);
}
}

/**
* @static
* @desc POST /api/v1/auth/forgot-password
* @param {object} req
* @param {object} res
* @memberof UsersController
* @returns successful pasword reset
*/
static async completeForgotPassword(req, res) {
const { email } = req;
const { password } = req.body;
const hashPassword = passwordHash(password, 10);

const updatePassword = await User.update({
password: hashPassword,
},
{
where: {
email,
}
});

if (!updatePassword) {
const response = new Response(
'Bad Request',
400,
'Unable to reset password',
);
return res.status(response.code).json(response);
}
const response = new Response(
'Ok',
200,
'Password reset successful',
);
return res.status(response.code).json(response);
}
}

export default UsersController;
49 changes: 49 additions & 0 deletions src/helpers/Validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Validation Helper
*/
class Validation {
/**
* checkEmptyField
* @param {string} field
* @returns {boolean} true|false
*/
static checkEmptyField(field) {
if (!field) {
return false;
}
return true;
}

/**
* checkEmptyFields
* @param {array} fields
* @returns {boolean} true|false
*/
static checkEmptyFields(fields) {
let errors;
let status = true;
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
if (!field.value) {
status = false;
errors = { field: field.name };
}
}
if (status !== true) return errors; return status;
}

/**
* compareValues
* @param {string} field1
* @param {string} field2
* @returns {boolean} true|false
*/
static compareValues(field1, field2) {
if (field1 !== field2) {
return false;
}
return true;
}
}

export default Validation;
6 changes: 6 additions & 0 deletions src/helpers/basePath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const basePath = (req) => {
const path = `${req.protocol}://${req.headers.host}`;
return path;
};

export default basePath;
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ app.use('/api/v1/', routes);

app.all('/', ((req, res) => {
response = new Response(
'ok',
'Ok',
200,
'Welcome to Authors Haven',
{}
Expand All @@ -45,7 +45,7 @@ app.all('/', ((req, res) => {

app.all('/*', ((req, res) => {
response = new Response(
'ok',
'Not Found',
404,
`Specified route does not exist ${req.originalUrl}`,
{}
Expand Down
108 changes: 108 additions & 0 deletions src/middlewares/UserMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Validation from '../helpers/Validation';
import models from '../db/models';
import Response from '../helpers/response';

const { User } = models;

/**
* User Middleware Class
*/
class UserMiddleware {
/**
* VerifyEmail
* @param {object} req
* @param {object} res
* @param {function} next
* @memberof UserMiddleware
* @returns {object} email verification
*/
static async VerifyEmail(req, res, next) {
try {
const { email } = req.body;
const validateEmail = Validation.checkEmptyField(email);
if (validateEmail !== true) {
const response = new Response(
'Bad Request',
400,
'Email field cannot be left empty',
);
return res.status(response.code).json(response);
}
const checkEmail = await User.findOne({
where: {
email,
},
attributes: ['username', 'email'],
});
if (!checkEmail) {
const response = new Response(
'Not Found',
404,
'Email does not exist',
);
return res.status(response.code).json(response);
}
req.checkEmail = checkEmail;
next();
} catch (err) {
const response = new Response(
'Internal server error',
500,
`${err}`,
);
return res.status(response.code).json(response);
}
}

/**
* Validate Password
* @param {object} req
* @param {object} res
* @param {function} next
* @memberof UserMiddleware
* @returns {object} validated password
*/
static async validatePassword(req, res, next) {
try {
const user = req.verifyUser;
const { email } = user;
const { password, confirmPassword } = req.body;

const fields = [
{ name: 'Password', value: password },
{ name: 'Confirm Password', value: confirmPassword }
];

const validate = Validation.checkEmptyFields(fields);

if (validate !== true) {
const response = new Response(
'Bad Request',
400,
`${validate.field} field cannot be empty`,
);
return res.status(response.code).json(response);
}

if (!Validation.compareValues(password, confirmPassword)) {
const response = new Response(
'Bad Request',
400,
'Passwords do not match'
);
return res.status(response.code).json(response);
}
req.email = email;
next();
} catch (err) {
const response = new Response(
'Internal server error',
500,
`${err}`,
);
return res.status(response.code).json(response);
}
}
}

export default UserMiddleware;
14 changes: 14 additions & 0 deletions src/routes/usersRoute.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import express from 'express';
import UserController from '../controllers/UsersController';
import TokenAuthenticate from '../helpers/TokenAuthenticate';
import UserMiddleware from '../middlewares/UserMiddleware';

const authRouter = express.Router();

authRouter.post('/signup', UserController.signUp);

authRouter.post(
'/forgot-password',
UserMiddleware.VerifyEmail,
UserController.forgotPassword
);
authRouter.put(
'/forgot-password',
TokenAuthenticate.tokenVerify,
UserMiddleware.validatePassword,
UserController.completeForgotPassword,
);
authRouter.post('/signin', UserController.signIn);

// eslint-disable-next-line import/prefer-default-export
Expand Down
Loading

0 comments on commit beb918c

Please sign in to comment.