From 181cb469976916ae49c6f096a3a1cdb18bd14346 Mon Sep 17 00:00:00 2001 From: Osaukhumwen Iyamuosa Date: Thu, 22 Aug 2019 22:03:51 +0100 Subject: [PATCH] feature(change-password):implement change password functionality [Delivers #167942048] --- docs/swagger.yaml | 100 ++++++- src/controllers/AuthController.js | 51 +++- ...20190822133141-create-previous-password.js | 36 +++ src/database/models/previouspassword.js | 42 +++ src/database/models/user.js | 5 + src/database/seeders/20190724090406-user.js | 45 +++ .../20190822135341-previous-password.js | 59 ++++ src/helpers/validator.js | 9 +- src/middlewares/userValidator.js | 5 + src/routes/auth.js | 7 +- src/services/index.js | 4 +- src/services/passwordService.js | 48 ++++ src/services/userService.js | 19 +- tests/auth.spec.js | 261 +++++++++++++----- tests/mockData/userMock.js | 24 ++ 15 files changed, 629 insertions(+), 86 deletions(-) create mode 100644 src/database/migrations/20190822133141-create-previous-password.js create mode 100644 src/database/models/previouspassword.js create mode 100644 src/database/seeders/20190822135341-previous-password.js create mode 100644 src/services/passwordService.js diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 8942b5d..9a96330 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -265,9 +265,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StandardServerResponse' - - description: internal server error + $ref: '#/components/schemas/StandardServerResponse' /genres: post: tags: @@ -367,8 +365,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StandardServerResponse' - + $ref: '#/components/schemas/StandardServerResponse' /auth/forgotpassword: post: tags: @@ -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 diff --git a/src/controllers/AuthController.js b/src/controllers/AuthController.js index 325d8ba..cb9f9e8 100644 --- a/src/controllers/AuthController.js +++ b/src/controllers/AuthController.js @@ -1,4 +1,5 @@ import jwt from 'jsonwebtoken'; +import bcrypt from 'bcrypt'; import helpers from '../helpers'; import services from '../services'; import models from '../database/models'; @@ -6,7 +7,11 @@ 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; /** @@ -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 * @@ -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 }; diff --git a/src/database/migrations/20190822133141-create-previous-password.js b/src/database/migrations/20190822133141-create-previous-password.js new file mode 100644 index 0000000..7a890d1 --- /dev/null +++ b/src/database/migrations/20190822133141-create-previous-password.js @@ -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 }; diff --git a/src/database/models/previouspassword.js b/src/database/models/previouspassword.js new file mode 100644 index 0000000..80c7529 --- /dev/null +++ b/src/database/models/previouspassword.js @@ -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; +}; diff --git a/src/database/models/user.js b/src/database/models/user.js index 095e002..1accc80 100644 --- a/src/database/models/user.js +++ b/src/database/models/user.js @@ -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' diff --git a/src/database/seeders/20190724090406-user.js b/src/database/seeders/20190724090406-user.js index 46061d0..7ee3e32 100644 --- a/src/database/seeders/20190724090406-user.js +++ b/src/database/seeders/20190724090406-user.js @@ -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', diff --git a/src/database/seeders/20190822135341-previous-password.js b/src/database/seeders/20190822135341-previous-password.js new file mode 100644 index 0000000..2aeb7d4 --- /dev/null +++ b/src/database/seeders/20190822135341-previous-password.js @@ -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, {}); diff --git a/src/helpers/validator.js b/src/helpers/validator.js index bac86d1..53d1c7d 100644 --- a/src/helpers/validator.js +++ b/src/helpers/validator.js @@ -31,14 +31,15 @@ const isValidName = field => check(field) .withMessage(`${field} is a required field`); /** + * @param {String} field * @returns {Object} - Express-validator */ -const isValidPassword = () => check('password').isLength({ min: 8 }) - .withMessage('password must be at least 8 characters long').not() +const isValidPassword = (field = 'password') => check(field).isLength({ min: 8 }) + .withMessage(`${field} must be at least 8 characters long`).not() .isEmpty() - .withMessage('password is a required field') + .withMessage(`${field} is a required field`) .isAlphanumeric() - .withMessage('password should contain only numbers and alphabets'); + .withMessage(`${field} should contain only numbers and alphabets`); /** * @param {String} field diff --git a/src/middlewares/userValidator.js b/src/middlewares/userValidator.js index a5c7d4e..a0193ae 100644 --- a/src/middlewares/userValidator.js +++ b/src/middlewares/userValidator.js @@ -38,6 +38,11 @@ const userValidator = { isValidEmail(), validatorError ], + changePassword: [ + isValidPassword('currentPassword'), + isValidPassword('newPassword'), + validatorError + ], }; export default userValidator; diff --git a/src/routes/auth.js b/src/routes/auth.js index 136964f..5979354 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -6,10 +6,15 @@ const auth = express.Router(); const AUTH_URL = '/auth'; const { userValidator, verifyToken } = middlewares; -const { forgotPassword, updateStatus } = AuthController; +const { forgotPassword, updateStatus, changePassword } = AuthController; // forgot password endpoint auth.post(`${AUTH_URL}/forgotpassword`, userValidator.forgotPassword, forgotPassword); + +// change password route +auth.post(`${AUTH_URL}/changepassword`, verifyToken, userValidator.changePassword, changePassword); + +// verifyUser route auth.patch(`${AUTH_URL}/verify/:token`, verifyToken, updateStatus); export default auth; diff --git a/src/services/index.js b/src/services/index.js index 9da6652..7034c5a 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -3,11 +3,13 @@ import userServices from './userService'; import novelServices from './novelService'; import commentServices from './commentService'; import notificationServices from './notification'; +import passwordServices from './passwordService'; export default { sendMail, userServices, novelServices, commentServices, - notificationServices + notificationServices, + passwordServices }; diff --git a/src/services/passwordService.js b/src/services/passwordService.js new file mode 100644 index 0000000..5220ba4 --- /dev/null +++ b/src/services/passwordService.js @@ -0,0 +1,48 @@ +import models from '../database/models'; + +const { PreviousPassword } = models; + +/** + * @description gets the user's previous passwords from the database + * @param {string} userId + * @returns {object} a user object + */ + +const getPreviousPasswords = async (userId) => { + const previousPasswords = await PreviousPassword.findAll({ + where: { userId }, + order: [['passwordCount', 'ASC']], + raw: true + }); + return previousPasswords; +}; + +/** + * @description deletes a user's previous password + * @param {string} id + * @returns {object} a user object + */ +const deletePreviousPassword = async (id) => { + await PreviousPassword.destroy({ + where: { id } + }); +}; + +/** + * @description adds a user's previous password to the table + * @param {string} userId + * @param {string} password + * @param {string} passwordCount + * @returns {object} a user object + */ +const createPreviousPassword = async (userId, password, passwordCount) => { + await PreviousPassword.create({ + userId, + password, + passwordCount + }); +}; + +export default { + getPreviousPasswords, deletePreviousPassword, createPreviousPassword +}; diff --git a/src/services/userService.js b/src/services/userService.js index 577a78c..7f71dca 100644 --- a/src/services/userService.js +++ b/src/services/userService.js @@ -12,12 +12,27 @@ const findUser = async (param) => { const field = (/^[A-Z0-9_.-]+@[A-Z0-9.-]+[A-Z]$/ig.test(param)) ? { email: param } : { id: param }; const user = await User.findOne({ where: field, - attributes: { exclude: ['password'] }, include: [{ model: Role }] }); return user; }; +/** + * @description Updates a user record + * @param {string} newValue + * @param {string} userId + * @returns {object} a user object + */ +const updateUser = async (newValue, userId) => { + const updatedUser = await User.update({ + ...newValue + }, { + where: { id: userId }, + returning: true + }); + return updatedUser; +}; + /** * @description Finds a follower following a user * @param {string} followeeId @@ -31,5 +46,5 @@ const findFollower = async (followeeId, followerId) => { }; export default { - findUser, findFollower + findUser, findFollower, updateUser }; diff --git a/tests/auth.spec.js b/tests/auth.spec.js index e40c019..1cb323e 100644 --- a/tests/auth.spec.js +++ b/tests/auth.spec.js @@ -3,17 +3,38 @@ import chaiHttp from 'chai-http'; import sinon from 'sinon'; import sendgridMail from '@sendgrid/mail'; import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import bcrypt from 'bcrypt'; import server from '../src'; import mockData from './mockData'; chai.use(chaiHttp); const { expect } = chai; +dotenv.config(); + +const { SECRET_KEY } = process.env; + const { userMock } = mockData; -const { forgotPasswordEmail, wrongForgotPasswordEmail } = userMock; +const { + forgotPasswordEmail, wrongForgotPasswordEmail, + wrongCurrentPassword, sameCurentAndNewPassword, + validChangePasswordInput, usingPreviousPassword, + userWithoutPreviousPassword, withoutFivePreviousPassword +} = userMock; const BASE_URL = '/api/v1'; const FORGOT_PASSWORD_URL = `${BASE_URL}/auth/forgotPassword`; +const CHANGE_PASSWORD_URL = `${BASE_URL}/auth/changepassword`; + +// token of loggedIn user Richard Croft in the database +const loggedInUserToken = jwt.sign({ id: '11fb0350-5b46-4ace-9a5b-e3b788167915' }, SECRET_KEY, { expiresIn: '60s' }); + +// token of loggedIn user Williams Brook in the database +const loggedInUser2Token = jwt.sign({ id: '8f3e7eda-090a-4c44-9ffe-58443de5e1f8' }, SECRET_KEY, { expiresIn: '60s' }); + +// token of loggedIn user Bruce Clifford in the database +const loggedInUser3Token = jwt.sign({ id: '8487ef08-2ac2-4387-8bd6-738b12c75dff' }, SECRET_KEY, { expiresIn: '60s' }); describe('AUTH', () => { describe('POST /auth/signup', () => { @@ -47,6 +68,78 @@ describe('AUTH', () => { }); }); + describe('POST /api/users/login', () => { + const loginsignupEndpoint = `${BASE_URL}/users/login`; + const authErrorMessage = 'email or password is incorrect'; + it('should #login a user and #generate jwt', (done) => { + chai + .request(server) + .post(loginsignupEndpoint) + .type('form') + .send(userMock.seededUser1) + .end((err, res) => { + const { user } = res.body; + expect(res).status(200); + expect(user).property('token'); + expect(user).property('email'); + expect(user).property('bio'); + done(err); + }); + }); + it('should return authorized error on incorrect email', (done) => { + const incorrectEmail = { ...userMock.validUser }; + incorrectEmail.email = 'wrong@email.com'; + chai + .request(server) + .post(loginsignupEndpoint) + .type('form') + .send(incorrectEmail) + .end((err, res) => { + expect(res).status(401); + expect(res.body).property('errors').eq(authErrorMessage); + done(err); + }); + }); + it('should return authorized error on incorrect email', (done) => { + const incorrectPassword = { ...userMock.validUser }; + incorrectPassword.password = 'WrongPassword1'; + chai + .request(server) + .post(loginsignupEndpoint) + .type('form') + .send(incorrectPassword) + .end((err, res) => { + expect(res).status(401); + expect(res.body).property('errors').eq(authErrorMessage); + done(err); + }); + }); + it('should return error if password id not correct', (done) => { + chai + .request(server) + .post(loginsignupEndpoint) + .type('form') + .send(userMock.seededUser2) + .end((err, res) => { + expect(res).status(401); + expect(res.body).property('errors').eq('email or password is incorrect'); + done(err); + }); + }); + it('should return error if user is not verified', (done) => { + chai + .request(server) + .post(loginsignupEndpoint) + .type('form') + .send(userMock.validUser2) + .end((err, res) => { + expect(res).status(401); + expect(res.body).property('errors').eq('please verify your email'); + done(err); + }); + }); + }); + // Forgot password route describe('Forgot password', () => { it('should sucessfully return an appropiate message after sending a mail to the user', (done) => { @@ -93,76 +186,102 @@ describe('AUTH', () => { }); }); }); -}); -describe('POST /api/users/login', () => { - const loginsignupEndpoint = `${BASE_URL}/users/login`; - const authErrorMessage = 'email or password is incorrect'; - it('should #login a user and #generate jwt', (done) => { - chai - .request(server) - .post(loginsignupEndpoint) - .type('form') - .send(userMock.seededUser1) - .end((err, res) => { - const { user } = res.body; - expect(res).status(200); - expect(user).property('token'); - expect(user).property('email'); - expect(user).property('bio'); - done(err); - }); - }); - it('should return authorized error on incorrect email', (done) => { - const incorrectEmail = { ...userMock.validUser }; - incorrectEmail.email = 'wrong@email.com'; - chai - .request(server) - .post(loginsignupEndpoint) - .type('form') - .send(incorrectEmail) - .end((err, res) => { - expect(res).status(401); - expect(res.body).property('errors').eq(authErrorMessage); - done(err); - }); - }); - it('should return authorized error on incorrect email', (done) => { - const incorrectPassword = { ...userMock.validUser }; - incorrectPassword.password = 'WrongPassword1'; - chai - .request(server) - .post(loginsignupEndpoint) - .type('form') - .send(incorrectPassword) - .end((err, res) => { - expect(res).status(401); - expect(res.body).property('errors').eq(authErrorMessage); - done(err); - }); - }); - it('should return error if password id not correct', (done) => { - chai - .request(server) - .post(loginsignupEndpoint) - .type('form') - .send(userMock.seededUser2) - .end((err, res) => { - expect(res).status(401); - expect(res.body).property('errors').eq('email or password is incorrect'); - done(err); - }); - }); - it('should return error if user is not verified', (done) => { - chai - .request(server) - .post(loginsignupEndpoint) - .type('form') - .send(userMock.validUser2) - .end((err, res) => { - expect(res).status(401); - expect(res.body).property('errors').eq('please verify your email'); - done(err); - }); + // Change Password route + describe('Change password', () => { + it('should sucessfully change a user\'s password', (done) => { + chai.request(server) + .post(CHANGE_PASSWORD_URL) + .send(validChangePasswordInput) + .set('authorization', loggedInUserToken) + .end((error, response) => { + expect(response).to.have.status(200); + expect(response.body).to.be.an('object'); + expect(response.body.message).to.equal('successfully changed password'); + done(); + }); + }); + + it('should sucessfully change a user\'s password who doesn\'t have a previous password', (done) => { + chai.request(server) + .post(CHANGE_PASSWORD_URL) + .send(userWithoutPreviousPassword) + .set('authorization', loggedInUser2Token) + .end((error, response) => { + expect(response).to.have.status(200); + expect(response.body).to.be.an('object'); + expect(response.body.message).to.equal('successfully changed password'); + done(); + }); + }); + + it('should sucessfully change a user\'s password who doesn\'t have up to 5 previous passwords', (done) => { + chai.request(server) + .post(CHANGE_PASSWORD_URL) + .send(withoutFivePreviousPassword) + .set('authorization', loggedInUser3Token) + .end((error, response) => { + expect(response).to.have.status(200); + expect(response.body).to.be.an('object'); + expect(response.body.message).to.equal('successfully changed password'); + done(); + }); + }); + + it('should return a failure response if the currentPassword supplied is wrong', (done) => { + chai.request(server) + .post(CHANGE_PASSWORD_URL) + .send(wrongCurrentPassword) + .set('authorization', loggedInUserToken) + .end((error, response) => { + expect(response).to.have.status(403); + expect(response.body).to.be.an('object'); + expect(response.body.error).to.equal('wrong password'); + done(); + }); + }); + + it('should return a failure response if newPassword is same as currentPassword', (done) => { + chai.request(server) + .post(CHANGE_PASSWORD_URL) + .send(sameCurentAndNewPassword) + .set('authorization', loggedInUserToken) + .end((error, response) => { + expect(response).to.have.status(409); + expect(response.body).to.be.an('object'); + expect(response.body.error).to.equal('new password cannot be the same the current password'); + done(); + }); + }); + + it('should return a failure response if newPassword is same as any of the last 5 passwords', (done) => { + chai.request(server) + .post(CHANGE_PASSWORD_URL) + .send(usingPreviousPassword) + .set('authorization', loggedInUserToken) + .end((error, response) => { + expect(response).to.have.status(409); + expect(response.body).to.be.an('object'); + expect(response.body.error).to.equal('you cannot use any of your last 5 passwords'); + done(); + }); + }); + + it('should return a failure response if a server error occurs', (done) => { + const stub = sinon.stub(bcrypt, 'compare'); + stub.throws(new Error('error occured!')); + + chai.request(server) + .post(CHANGE_PASSWORD_URL) + .send(validChangePasswordInput) + .set('authorization', loggedInUserToken) + .end((error, response) => { + expect(response).to.have.status(500); + expect(response.body).to.be.an('object'); + expect(response.body.error).to.equal('error occured!'); + stub.restore(); + done(); + }); + }); }); }); diff --git a/tests/mockData/userMock.js b/tests/mockData/userMock.js index 17de538..4d2e80e 100644 --- a/tests/mockData/userMock.js +++ b/tests/mockData/userMock.js @@ -77,6 +77,30 @@ export default { wrongForgotPasswordEmail: { email: 'example@gmail.com' }, + wrongCurrentPassword: { + currentPassword: 'abcdefgh', + newPassword: 'newnewnewnew' + }, + sameCurentAndNewPassword: { + currentPassword: 'newnewnewnew', + newPassword: 'newnewnewnew' + }, + usingPreviousPassword: { + currentPassword: 'newnewnewnew', + newPassword: 'richardCroft' + }, + userWithoutPreviousPassword: { + currentPassword: 'williamsBrook', + newPassword: 'willywilly' + }, + withoutFivePreviousPassword: { + currentPassword: 'bruceClifford', + newPassword: 'bigBruce' + }, + validChangePasswordInput: { + currentPassword: 'richardCroft', + newPassword: 'newnewnewnew' + }, seededUser1: { firstName: 'Eden', lastName: 'Hazard',