Skip to content

Commit

Permalink
Merge 6f034b8 into 1e59ae0
Browse files Browse the repository at this point in the history
  • Loading branch information
rukundoeric committed Jun 15, 2019
2 parents 1e59ae0 + 6f034b8 commit 5afe2a7
Show file tree
Hide file tree
Showing 11 changed files with 662 additions and 107 deletions.
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
NODE_ENV=
PORT=
DATABASE_URL=
LOCAL_DB_USER=
LOCAL_DB_PASSWORD=
LOCAL_DB_NAME=
DEV_DATABASE_URL=
TEST_DATABASE_URL=
AUTHOSHAVEN_USER=
AUTHOSHAVEN_PASS=
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "babel src --out-dir dist",
"serve": "node dist/index.js",
"start": "node dist/index.js",
"test": "nyc --reporter=html --reporter=text mocha ./test/*.js --exit --require @babel/register --require regenerator-runtime",
"test": "nyc --reporter=html --reporter=text mocha ./test/*.js --timeout 80000 --exit --require @babel/register --require regenerator-runtime",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"test:local": "yarn undo && yarn migrate && yarn seed && yarn test",
"migrate": "sequelize db:migrate",
Expand All @@ -20,14 +20,15 @@
"dependencies": {
"@hapi/joi": "^15.0.3",
"bcrypt": "^3.0.6",
"body-parser": "^1.18.3",
"body-parser": "^1.19.0",
"cors": "^2.8.4",
"dotenv": "^8.0.0",
"ejs": "^2.6.1",
"errorhandler": "^1.5.0",
"express": "^4.16.3",
"express-jwt": "^5.3.1",
"express-session": "^1.15.6",
"handlebars": "^4.1.2",
"joi": "^14.3.1",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.11",
Expand All @@ -40,7 +41,7 @@
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"regenerator-runtime": "^0.13.2",
"request": "^2.87.0",
"request": "^2.88.0",
"sequelize": "^5.8.7",
"slug": "^1.1.0",
"swagger-ui-express": "^4.0.6",
Expand Down
189 changes: 186 additions & 3 deletions src/api/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import db from '../../sequelize/models';
import joi from 'joi';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import nodemailer from 'nodemailer';
import TokenHelper from '../../helpers/Token.helper';
import Mailhelper from '../../helpers/SendMail.helper';
import HashHelper from '../../helpers/hashHelper';
import db from '../../sequelize/models/index';
import templete from '../../helpers/emailTemplete';
import Validation from '../../middleware/resetValidation';

const { User, Blacklist } = db;

dotenv.config();

/**
* @author Elie Mugenzi
* @class AuthController
Expand All @@ -19,7 +27,14 @@ class AuthController {
*/
static async register(req, res) {
const {
firstName, lastName, email, password, username, dob, bio, gender
firstName,
lastName,
email,
password,
username,
dob,
bio,
gender
} = req.body;
if (Object.keys(req.body).length === 0) {
return res.status(400).json({
Expand Down Expand Up @@ -51,7 +66,7 @@ class AuthController {
res.status(201).json({
status: 201,
message: 'We have sent an email to you to verify your account',
token,
token
});
}
}
Expand Down Expand Up @@ -105,6 +120,174 @@ class AuthController {
});
}
}

/**
* signup controller
* @param {Object} res - Response
* @param {Function} next -Next
* @returns {Object} The response object
*/
static async signup(res) {
// TODO: Implement functionality
return res.status(200).send({ status: 200 });
}

/**
* RequestPasswordReset controller
* @param {Object} req - Request
* @param {Object} res - Response
* @param {Function} next -Next
* @returns {Object} The response object
*/
static async RequestPasswordReset(req, res) {
joi
.validate(req.body, Validation.passwordResetShema)
.then(() => {
User.findAll({
where: {
email: req.body.email
}
}).then(async (response) => {
if (response[0]) {
const token = await jwt.sign(
{
userId: response[0].id,
userName: response[0].username,
userEmail: response[0].emial
},
process.env.SECRET_KEY,
{ expiresIn: 60 * 10 }
);

const user = response[0];
const { firstname, lastname, email } = user.dataValues;
const link = `${process.env.BASE_URL}/api/auth/reset/${token}`;
const mail = {
firstname,
lastname,
link,
email
};

const transport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.AUTHOSHAVEN_USER,
pass: process.env.AUTHOSHAVEN_PASS
}
});
const htmlToSend = templete.getPasswordResetTemplete(
mail.firstname,
mail.lastname,
mail.link
);
const mailOptions = {
from: 'Authors Haven',
to: `${mail.email}`,
subject: ' Password Reset',
text: 'Hello there',
html: htmlToSend
};
transport.sendMail(mailOptions, async () => {
res.status(201).send({
status: 201,
data: {
message: `Reset link sent to your email <${mail.email}>`,
email: `${mail.email}`,
token
}
});
});
} else {
res.status(404).send({
status: 404,
data: { message: 'User with that email in not exist' }
});
}
});
})
.catch(error => res.status(400).send({
status: 400,
error: {
message: error.message.replace(/[&/\\#,+()$~%.'":*?<>{}]/g, '')
}
}));
}

/**
* ConfirmPasswordReset controller
* @param {Object} req - Request
* @param {Object} res - Response
* @param {Function} next -Next
* @returns {Object} The response object
*/
static async ConfirmPasswordReset(req, res) {
try {
const { token } = req.params;
const user = await jwt.verify(token, process.env.SECRET_KEY);
const aprvToken = await jwt.sign(
{
userId: user.userId,
userName: user.userName
},
process.env.SECRET_KEY,
{ expiresIn: 60 * 10 }
);
const link = `${process.env.BASE_URL}/api/auth/reset/${aprvToken}`;
res.status(200).send({
status: 200,
data: {
message: 'Below is your reset link',
link,
token: aprvToken
}
});
} catch (error) {
res.status(error.status || 500).send({
status: error.status || 500,
error: { message: 'Token is invalid or expired, Please request another one' }
});
}
}

/**
* ApplyPasswordReset cotroller
* @param {Object} req - Request
* @param {Object} res - Response
* @param {Function} next -Next
* @returns {Object} The response object
*/
static async ApplyPasswordReset(req, res) {
joi
.validate(req.body, Validation.applyPasswordShema)
.then(async () => {
const { aprvToken } = req.params;
const token = await jwt.verify(aprvToken, process.env.SECRET_KEY);
const password = HashHelper.hashPassword(req.body.newpassword);
User.update(
{
password
},
{
where: {
id: token.userId
}
}
)
.then(() => {
res.status(201).send({
status: 201,
data: { message: 'Password changed successful' }
});
});
})
.catch(error => res.status(error.status || 400).send({
status: error.status || 400,
error: {
message: error.message.replace(/[&/\\#,+()$~%.'":*?<>{}]/g, '')
}
}));
}
}

export default AuthController;
15 changes: 13 additions & 2 deletions src/api/routes/authRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@ import Auth from '../../middleware/auth';
import dropToken from '../../middleware/droppedToken';

const authRouter = Router();

const {
RequestPasswordReset,
ConfirmPasswordReset,
ApplyPasswordReset,
register,
verifyAccount,
SignOut
} = authController;
const { usernameExists, emailExists } = userValidation;
const { register, verifyAccount, SignOut } = authController;
const { verifyToken } = Auth;

authRouter.get('/signout', verifyToken, dropToken, SignOut);


authRouter.post('/signup', validateBody('signup'), validateGender, usernameExists, emailExists, register);
authRouter.get('/verify', verifyAccount);

authRouter.post('/reset', RequestPasswordReset);
authRouter.get('/reset/:token', ConfirmPasswordReset);
authRouter.patch('/reset/:aprvToken', ApplyPasswordReset);
export default authRouter;
48 changes: 48 additions & 0 deletions src/helpers/emailTemplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* @class ResetPassword
* @description Authentication based class
* */
class Templete {
/**
* Verify token middleware
* @param {String} firstname - Request
* @param {String} lastname - Response
* @param {String} token -EmailTemplete
* @returns {String} The response String
*/
static getPasswordResetTemplete(firstname, lastname, token) {
return `
<div style="background:#e5eeff;width:100%;padding:20px 0;">
<div style="max-width:760px;margin:0 auto;background:#ffffff">
<div style="background:#266cef;padding:10px;color:#ffffff;text-align:center;font-size:34px">
Authors Haven
</div>
<div style="padding:20px;text-align:left;">
<p>
<h2>Hi ${firstname} ${lastname} </h2></br>
You Recently requested a password reset for your Authors Haven Account, Click the the Button below
to reset it.</br>
<form action="${token}">
<button style="background:#266cef;padding:10px; outline: none; border:0; border-radius:10px; margin:5px;color:#ffffff;text-align:center;font-size:13px">Reset your Password</button>
</form>
</br>
If you did not request a password reset please ignore this email or reply to let us know.
This link will be expired in next 10 minutes.
</p>
<a href="https://andela.com">Visit Andela's website</a>
</div>
<br>
<div style="padding:20px;text-align:left;">
<b>Authors Haven</b>
</div>
</div>
<div style="padding:35px 10px;text-align:center;">
Copyright, 2019<br>
Authors Haven
</div>
</div>
`;
}
}

export default Templete;
8 changes: 4 additions & 4 deletions src/helpers/hashHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import bcrypt from 'bcrypt';
*/
class HashHelper {
/**
* Hashes password
* @param {String} password - Password to hash
* @returns {String} - hashed Password
*/
* Hashes password
* @param {String} password - Password to hash
* @returns {String} - hashed Password
*/
static hashPassword(password) {
return bcrypt.hashSync(password, 8);
}
Expand Down
10 changes: 10 additions & 0 deletions src/middleware/resetValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import joi from 'joi';

export default {
passwordResetShema: joi.object().keys({
email: joi.string().email().required(),
}),
applyPasswordShema: joi.object().keys({
newpassword: joi.string().regex(/^[a-zA-Z]/).min(8).required(),
})
};
Loading

0 comments on commit 5afe2a7

Please sign in to comment.