Skip to content

Commit

Permalink
feature(change-password):implement change password functionality
Browse files Browse the repository at this point in the history
[Delivers #167942048]
  • Loading branch information
Ukhu committed Aug 23, 2019
1 parent 1f5c883 commit 181cb46
Show file tree
Hide file tree
Showing 15 changed files with 629 additions and 86 deletions.
100 changes: 95 additions & 5 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/StandardServerResponse'

description: internal server error
$ref: '#/components/schemas/StandardServerResponse'
/genres:
post:
tags:
Expand Down Expand Up @@ -367,8 +365,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/StandardServerResponse'

$ref: '#/components/schemas/StandardServerResponse'
/auth/forgotpassword:
post:
tags:
Expand Down Expand Up @@ -402,6 +399,99 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/StandardServerResponse'
/auth/changepassword:
post:
summary: Change password
description: Allows an authenticated user to change their password
requestBody:
content:
application/json:
schema:
type: object
properties:
currentPassword:
type: string
newPassword:
type: string
responses:
201:
description: succesful request
content:
application/json:
schema:
type: object
properties:
message:
type: object
example: successfully changed password
400:
description: bad request
content:
application/json:
schema:
type: object
properties:
errors:
type: array
items:
type: object
properties:
field:
type: string
example: currentPassword
message:
type: string
example: currentPassword is a required field
401:
description: unauthorized access
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: invalid token
403:
description: forbidden access
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: wrong password
404:
description: entity not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: user not found
409:
description: conflict error
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: new password cannot be the same as current password
500:
description: server error
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: error occured
/novels/{slug}/comments:
post:
summary: Creates a comment
Expand Down
51 changes: 49 additions & 2 deletions src/controllers/AuthController.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import helpers from '../helpers';
import services from '../services';
import models from '../database/models';

const {
forgotPasswordMessage, responseMessage, errorResponse, successResponse,
} = helpers;
const { sendMail, userServices: { findUser } } = services;
const {
sendMail,
userServices: { findUser, updateUser },
passwordServices: { getPreviousPasswords, deletePreviousPassword, createPreviousPassword }
} = services;
const { User } = models;

/**
Expand Down Expand Up @@ -36,6 +41,48 @@ const forgotPassword = async (request, response) => {
}
};

/**
* Change password
*
* @param {object} request
* @param {object} response
* @returns {json} - json
*/

const changePassword = async (request, response) => {
const { currentPassword, newPassword } = request.body;
const { id, password } = request.user;
try {
const match = await bcrypt.compare(currentPassword, password);
if (!match) {
return responseMessage(response, 403, { error: 'wrong password' });
}
if (newPassword === currentPassword) {
return responseMessage(response, 409, { error: 'new password cannot be the same the current password' });
}
const previousPasswords = await getPreviousPasswords(id);
let count = 1;
if (previousPasswords.length > 1) {
const isPreviousPassword = previousPasswords
.find(item => bcrypt.compareSync(newPassword, item.password));

if (isPreviousPassword) {
return responseMessage(response, 409, { error: 'you cannot use any of your last 5 passwords' });
}
count = previousPasswords[previousPasswords.length - 1].passwordCount + 1;
if (previousPasswords.length === 5) {
await deletePreviousPassword(previousPasswords[0].id);
}
}
await createPreviousPassword(id, password, count);
const hashedPassword = await bcrypt.hash(newPassword, 10);
await updateUser({ password: hashedPassword }, id);
return responseMessage(response, 200, { message: 'successfully changed password' });
} catch (error) {
responseMessage(response, 500, { error: error.message });
}
};

/**
* Update user verified status
*
Expand Down Expand Up @@ -65,4 +112,4 @@ const updateStatus = async (req, res) => {
return successResponse(res, 200, { message: 'You have sucessfully verified your email' });
};

export default { forgotPassword, updateStatus };
export default { forgotPassword, updateStatus, changePassword };
36 changes: 36 additions & 0 deletions src/database/migrations/20190822133141-create-previous-password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const up = (queryInterface, Sequelize) => queryInterface.createTable('PreviousPasswords', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
userId: {
type: Sequelize.UUID,
onDelete: 'CASCADE',
allowNull: false,
references: {
model: 'Users',
key: 'id'
},
},
password: {
allowNull: false,
type: Sequelize.STRING
},
passwordCount: {
allowNull: false,
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});

const down = queryInterface => queryInterface.dropTable('PreviousPasswords');

export default { up, down };
42 changes: 42 additions & 0 deletions src/database/models/previouspassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export default (Sequelize, DataTypes) => {
const PreviousPassword = Sequelize.define('PreviousPassword', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
userId: {
type: DataTypes.UUID,
onDelete: 'CASCADE',
allowNull: false,
references: {
model: 'Users',
key: 'id'
},
},
password: {
allowNull: false,
type: DataTypes.STRING
},
passwordCount: {
allowNull: false,
type: DataTypes.INTEGER
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
}
}, {});
PreviousPassword.associate = (models) => {
PreviousPassword.hasMany(models.Novel, {
foreignKey: 'authorId',
onDelete: 'CASCADE'
});
};
return PreviousPassword;
};
5 changes: 5 additions & 0 deletions src/database/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ export default (Sequelize, DataTypes) => {
onDelete: 'CASCADE'
});

User.hasMany(models.PreviousPassword, {
foreignKey: 'userId',
onDelete: 'CASCADE'
});

User.hasMany(models.Follower, {
foreignKey: 'followerId',
onDelete: 'CASCADE'
Expand Down
45 changes: 45 additions & 0 deletions src/database/seeders/20190724090406-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,51 @@ export const up = queryInterface => queryInterface.bulkInsert('Users', [{
createdAt: new Date(),
updatedAt: new Date()
},
{
id: '11fb0350-5b46-4ace-9a5b-e3b788167915',
firstName: 'Richard',
lastName: 'Croft',
email: 'richard@gmail.com',
password: bcrypt.hashSync('richardCroft', 10),
bio: 'I am a genius with the pen',
avatarUrl: null,
phoneNo: null,
isVerified: true,
isSubscribed: true,
roleId: 'f2dec928-1ff9-421a-b77e-8998c8e2e720',
createdAt: new Date(),
updatedAt: new Date()
},
{
id: '8f3e7eda-090a-4c44-9ffe-58443de5e1f8',
firstName: 'Williams',
lastName: 'Brook',
email: 'williams@gmail.com',
password: bcrypt.hashSync('williamsBrook', 10),
bio: 'I am a genius with the pen',
avatarUrl: null,
phoneNo: null,
isVerified: true,
isSubscribed: true,
roleId: 'f2dec928-1ff9-421a-b77e-8998c8e2e720',
createdAt: new Date(),
updatedAt: new Date()
},
{
id: '8487ef08-2ac2-4387-8bd6-738b12c75dff',
firstName: 'Bruce',
lastName: 'Clifford',
email: 'bruce@gmail.com',
password: bcrypt.hashSync('bruceClifford', 10),
bio: 'I am a genius with the pen',
avatarUrl: null,
phoneNo: null,
isVerified: true,
isSubscribed: true,
roleId: 'f2dec928-1ff9-421a-b77e-8998c8e2e720',
createdAt: new Date(),
updatedAt: new Date()
},
{
id: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
firstName: 'James',
Expand Down
59 changes: 59 additions & 0 deletions src/database/seeders/20190822135341-previous-password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import bcrypt from 'bcrypt';

export const up = queryInterface => queryInterface.bulkInsert('PreviousPasswords', [{
id: 'd0bd6342-3188-4c49-9980-eb8fe7c77b83',
userId: '11fb0350-5b46-4ace-9a5b-e3b788167915',
password: bcrypt.hashSync('pogba1234', 10),
passwordCount: 1,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: 'd5347c0c-cba2-46a6-abb2-69c68ce928fe',
userId: '11fb0350-5b46-4ace-9a5b-e3b788167915',
password: bcrypt.hashSync('lionel1234', 10),
passwordCount: 2,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: '88f5b93b-b408-454c-bf30-4abb590db6ba',
userId: '11fb0350-5b46-4ace-9a5b-e3b788167915',
password: bcrypt.hashSync('ronaldo1234', 10),
passwordCount: 3,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: 'e87df52f-080b-4d9d-8e35-0a8f645ab390',
userId: '11fb0350-5b46-4ace-9a5b-e3b788167915',
password: bcrypt.hashSync('mason1234', 10),
passwordCount: 4,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: 'b93e45ff-dab8-4f64-894e-862d174f8416',
userId: '11fb0350-5b46-4ace-9a5b-e3b788167915',
password: bcrypt.hashSync('lampard1234', 10),
passwordCount: 5,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: '95dda745-8efa-4342-8ef7-02f80bda1add',
userId: '8487ef08-2ac2-4387-8bd6-738b12c75dff',
password: bcrypt.hashSync('bakayoko1234', 10),
passwordCount: 1,
createdAt: new Date(),
updatedAt: new Date()
},
{
id: '9722da61-1c7a-4041-b934-976cef877ddf',
userId: '8487ef08-2ac2-4387-8bd6-738b12c75dff',
password: bcrypt.hashSync('drinkwater1234', 10),
passwordCount: 2,
createdAt: new Date(),
updatedAt: new Date()
}], {});
export const down = queryInterface => queryInterface.bulkDelete('PreviousPasswords', null, {});
Loading

0 comments on commit 181cb46

Please sign in to comment.