Skip to content

Commit

Permalink
fix merge conflict in develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Dubby20 committed Mar 15, 2019
2 parents dd0780f + a1f7459 commit c5c9f5c
Show file tree
Hide file tree
Showing 17 changed files with 503 additions and 67 deletions.
13 changes: 5 additions & 8 deletions auth/passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,11 @@ export const googleSocial = async (accessToken, refreshToken, profile, done) =>
});
return done(null, user[0].dataValues);
};
passport.use(new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `${process.env.HOST_URL}/api/v1/auth/google/callback`,
}, googleSocial

));
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `${process.env.HOST_URL}/api/v1/auth/google/callback`,
}, googleSocial));

/**
* callback function for facebook strategy
Expand Down
16 changes: 7 additions & 9 deletions controllers/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ export default class ArticleController {
} catch (error) {
return res.status(500).json({
success: false,
error: [error.message]
errors: [error.message]
});
}
}

/**
* @description - Update an article
* @description - Deltes an article
* @static
* @param {Object} req - the request object
* @param {Object} res - the response object
Expand All @@ -84,21 +84,19 @@ export default class ArticleController {
*/
static async deleteArticle(req, res) {
try {
await Article.destroy(
{
where: {
id: req.params.id
}
await Article.destroy({
where: {
id: req.params.id
}
);
});
return res.status(200).json({
success: true,
message: 'Article deleted successfully'
});
} catch (error) {
return res.status(500).json({
success: false,
error: [error.message]
errors: [error.message]
});
}
}
Expand Down
95 changes: 93 additions & 2 deletions controllers/user.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import bcrypt from 'bcrypt';
import dotenv from 'dotenv';
import jwt from 'jsonwebtoken';
import { compareSync } from 'bcrypt';
import shortId from 'shortid';
import sendMail from '../helpers/emails';
import getName from '../helpers/user';
import { User } from '../models';

const { HOST_URL } = process.env;

dotenv.config();
const { JWT_SECRET } = process.env;
const generateToken = (id, expiresIn = '24h') => jwt.sign({ id }, JWT_SECRET, { expiresIn });
Expand Down Expand Up @@ -75,7 +80,7 @@ export default class UserController {
body: { rememberMe, password },
user
} = req;
const passwordMatch = compareSync(password, user.password);
const passwordMatch = bcrypt.compareSync(password, user.password);
if (!passwordMatch) {
return res.status(401).json({
success: false,
Expand Down Expand Up @@ -131,4 +136,90 @@ export default class UserController {
});
});
}

/**
* @description reset user password
* @param {object} req http request object
* @param {object} res http response object
* @returns {object} response
*/
static async requestPasswordReset(req, res) {
const { email } = req.body;
const passwordResetToken = shortId.generate();
try {
// save password reset token to db
await User.update(
{
passwordResetToken,
passwordResetTokenExpires: Date.now() + (60 * 60 * 1000)
},
{ where: { email } }
);
} catch (error) {
return res.status(500).json({
success: false,
errors: [error.message]
});
}

// get user's name and send reset email
const name = await getName(email);
const emailPayload = {
name,
email,
link: `${HOST_URL}/api/v1/resetpassword/${passwordResetToken}`,
subject: 'Reset your password',
message: 'reset your password'
};
sendMail(emailPayload);

return res.status(201).json({
success: true,
message: 'A link to reset your password has been sent to your mail. Please note that the link is only valid for one hour.'
});
}

/**
* @description Check if password token in valid
* @param {object} req http request object
* @param {object} res http response object
* @returns {object} response
*/
static async resetPassword(req, res) {
const { params: { passwordResetToken } } = req;
const { password } = req.body;
const getPasswordResetToken = await User.findOne({ where: { passwordResetToken } });
if (!getPasswordResetToken) {
return res.status(404).json({
success: false,
errors: ['Password reset token not found']
});
}
if (getPasswordResetToken.dataValues.passwordResetTokenExpires < Date.now()) {
return res.status(410).json({
success: false,
errors: ['Your link has expired. Please try to reset password again.']
});
}
try {
// save new password to db and remove password reset token
await User.update(
{
password,
passwordResetToken: ''
},
{ where: { passwordResetToken } }
);
} catch (error) {
return res.status(500).json({
success: false,
errors: [error.message]
});
}

return res.status(201).json({
success: true,
message: 'Password changed successfully.'
});
}
}
102 changes: 98 additions & 4 deletions doc.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,84 @@
}
}
},
"/requestpasswordreset": {
"post": {
"tags": [
"Reset password"
],
"summary": "Send reset link to user email",
"description": "Send reset link to user email",
"consumes": [
"application/x-www-form-urlencoded",
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "Request body",
"description": "Reset user password",
"required": true,
"schema": {
"$ref": "#/definitions/requestPasswordReset"
}
}
],
"responses": {
"201": {
"description": "Save password reset key in database and send link to email"
},
"404": {
"description": "User not registered or verified."
},
"422": {
"description": "Email is invalid"
}
}
}
},
"/resetpassword/{key}": {
"post": {
"tags": [
"Reset password"
],
"summary": "Verify user reset password key and change password",
"description": "Verify user reset password key and change password",
"consumes": [
"application/x-www-form-urlencoded",
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "key",
"description": "key sent with the link to the user email",
"required": true,
"type": "integer",
"format": "int64",
"schema": {
"$ref": "#/definitions/resetPassword"
}
}
],
"responses": {
"200": {
"description": "You can now reset your password."
},
"410": {
"description": "Your link has expired. Please try to reset password again."
},
"404": {
"description": "Password reset token not found."
}
}
}
},
"/articles": {
"post": {
"tags": [
Expand Down Expand Up @@ -410,6 +488,22 @@
}
}
},
"requestPasswordReset": {
"type": "object",
"properties": {
"email": {
"type": "string"
}
}
},
"resetPassword": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
},
"articles": {
"type": "object",
"properties": {
Expand All @@ -423,10 +517,10 @@
"type": "string"
}
}
},
"externalDocs": {
"description": "Find out more about Swagger",
"url": "http://swagger.io"
}
},
"externalDocs": {
"description": "Find out more about Swagger",
"url": "http://swagger.io"
}
}
13 changes: 10 additions & 3 deletions helpers/emails.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@ const transporter = nodemailer.createTransport({
});

const sendMail = (payload) => {
const { email, name, link } = payload;
const {
email,
name,
link,
subject,
message
} = payload;

const mailOptions = {
from: EMAIL_ADDRESS,
to: email,
subject: 'Welcome to Authors Haven',
subject,
html: `<h1>Hi ${name}</h1>
<p>Please click <a href="${link}">here</a> to verify your account.</p>
<p>Please click <a href="${link}">here</a> to ${message}.</p>
`,
};
return transporter.sendMail(mailOptions);
Expand Down
12 changes: 12 additions & 0 deletions helpers/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { User } from '../models';

/**
* @description Return user name
* @param {string} email user email
* @returns {promise} promise object
*/
const getName = async (email) => {
const name = await User.findOne({ where: { email } });
return name.dataValues.name;
};
export default getName;
21 changes: 18 additions & 3 deletions middleware/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,36 @@ export const validateUser = async (req, res, next) => {
if (!article) {
return res.status(404).json({
success: false,
error: 'Article not found'
errors: ['Article not found']
});
}
const checkUser = article.userId === user.id;
if (!checkUser) {
return res.status(401).json({
success: false,
error: 'You are unauthorized to perform this action'
errors: ['You are unauthorized to perform this action']
});
}
return next();
} catch (error) {
return res.status(500).json({
success: false,
error: 'Article does not exist'
errors: ['Article does not exist']
});
}
};
export const validateEmail = [
check('email')
.isEmail()
.withMessage('Email is invalid.')
.custom(value => !/\s/.test(value))
.withMessage('No spaces are allowed in the email.')
];

export const validatePassword = [
check('password')
.isLength({ min: 6 })
.withMessage('Password must be at least 6 characters long.')
.custom(value => !/\s/.test(value))
.withMessage('No spaces are allowed in the password.')
];
Loading

0 comments on commit c5c9f5c

Please sign in to comment.