From 762b9983fe14064085c93011865f28287404627d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 5 Apr 2025 14:35:25 +0200 Subject: [PATCH 1/6] feat: add update password --- src/controllers/user.controller.js | 57 +++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index dd53f39..054d92a 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,5 +1,9 @@ import prisma from '../config/prismaClient.js'; -import { updateUserAccountValidation } from '../validations/user.validation.js'; +import { comparePassword, hashPassword } from '../utils/password.utils.js'; +import { + updatePasswordValidation, + updateUserAccountValidation, +} from '../validations/user.validation.js'; /* eslint no-undef:off */ export const getAllUsers = async (req, res, next) => { try { @@ -141,3 +145,54 @@ export const updateUserAccount = async (req, res, next) => { next(error); } }; + +export const updateUserPassword = async (req, res, next) => { + try { + // Validate the request body + const { error, value } = updatePasswordValidation(req.body); + if (error) { + return res.status(400).json({ message: error.details[0].message }); + } + + const { id } = req.params; // Extract user ID from request parameters + const { oldPassword, newPassword } = value; + + // Fetch the user by ID + const user = await prisma.user.findUnique({ + where: { id }, + select: { id: true, password: true }, + }); + + if (!user) { + return res.status(404).json({ message: 'User not found' }); + } + + // Compare the old password with the stored password + const isMatch = await comparePassword(oldPassword, user.password); + if (!isMatch) { + return res.status(400).json({ message: 'Incorrect old password' }); + } + + // Ensure the new password is different from the old password + if (oldPassword === newPassword) { + return res + .status(400) + .json({ message: 'New password must be different from the old one' }); + } + + // Hash the new password + const hashedPassword = await hashPassword(newPassword); + + // Update the user's password in the database + await prisma.user.update({ + where: { id }, + data: { password: hashedPassword }, + }); + + // Respond with a success message + return res.status(200).json({ message: 'Password updated successfully' }); + } catch (error) { + // Pass any errors to the error-handling middleware + next(error); + } +}; From b38f83be727b8cec336d21e2af4b915bbb9124fe Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 5 Apr 2025 14:41:33 +0200 Subject: [PATCH 2/6] feat: add update password swagger docs --- src/docs/swagger.json | 103 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 572d546..cb20bef 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -849,6 +849,90 @@ } } } + }, + "/api/users/update-password": { + "put": { + "tags": ["Users"], + "summary": "Update user password", + "description": "Allows a user to update their password by providing the old and new passwords.", + "operationId": "updateUserPassword", + "security": [{ "bearerAuth": [] }], + "requestBody": { + "description": "Password update data", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email", "oldPassword", "newPassword"], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "user@example.com" + }, + "oldPassword": { + "type": "string", + "example": "oldPassword123" + }, + "newPassword": { + "type": "string", + "example": "newPassword456" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password updated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Password updated successfully" + } + } + } + } + } + }, + "400": { + "description": "Bad request - Validation error or incorrect old password", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "User not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } } }, "components": { @@ -1744,6 +1828,25 @@ "example": "+1234567890" } } + }, + "UpdatePasswordRequest": { + "type": "object", + "required": ["email", "oldPassword", "newPassword"], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "user@example.com" + }, + "oldPassword": { + "type": "string", + "example": "oldPassword123" + }, + "newPassword": { + "type": "string", + "example": "newPassword456" + } + } } }, "responses": { From 6cd92105084d8baf94ef919ccdb571f54a259c61 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 5 Apr 2025 14:42:08 +0200 Subject: [PATCH 3/6] feat: add update password validation --- src/validations/user.validation.js | 32 ++++++------------------------ 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/validations/user.validation.js b/src/validations/user.validation.js index 20c9ef4..eb40769 100644 --- a/src/validations/user.validation.js +++ b/src/validations/user.validation.js @@ -29,38 +29,18 @@ export const updateUserAccountValidation = (obj) => { bio: Joi.string().max(1000).messages({ 'string.max': 'Bio cannot exceed 1000 characters', }), - }); + }).options({ allowUnknown: true }); // Allow unknown fields return schema.validate(obj, { abortEarly: false }); }; -export const updateUserPasswordValidation = (obj) => { +export const updatePasswordValidation = (obj) => { const schema = Joi.object({ - currentPassword: Joi.string().required().messages({ - 'any.required': 'Current password is required', + oldPassword: Joi.string().required(), + newPassword: Joi.string().min(8).required().messages({ + 'string.min': 'New password must be at least 8 characters long', }), - newPassword: Joi.string() - .required() - .min(8) - .max(32) - .regex( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, - ) - .messages({ - 'string.empty': 'New password cannot be empty', - 'string.min': 'Password must be at least 8 characters', - 'string.max': 'Password cannot exceed 32 characters', - 'string.pattern.base': - 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character', - }), - confirmPassword: Joi.string() - .valid(Joi.ref('newPassword')) - .required() - .messages({ - 'any.only': 'Passwords do not match', - 'any.required': 'Confirm password is required', - }), }); - return schema.validate(obj, { abortEarly: false }); + return schema.validate(obj); }; From eec6a4022a55d72c3ada9be58f8fbb832092bf47 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 5 Apr 2025 14:43:37 +0200 Subject: [PATCH 4/6] feat: add end points to readme file --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5c735f..02beb45 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,10 @@ base url: `http://localhost:3000` ### User -- **Get all Users**: GET `/api/users` -- **Get a specific Users**: GET `/api/Users/:userId` +- **Get all Users**: GET `/api/users/all` +- **Get a specific User**: GET `/api/users/:id` +- **Update a User**: PUT `/api/users/:id` +- **Update User Password**: PUT `/api/users/update-password/:id` ### Organization From 90af01c73a224392d49c7e8340b857d422967f7a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 5 Apr 2025 14:44:18 +0200 Subject: [PATCH 5/6] feat: add update password route --- src/routes/user.routes.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/routes/user.routes.js b/src/routes/user.routes.js index a15de4e..0c7b153 100644 --- a/src/routes/user.routes.js +++ b/src/routes/user.routes.js @@ -3,6 +3,7 @@ import { getAllUsers, getUserById, updateUserAccount, + updateUserPassword, } from '../controllers/user.controller.js'; import { verifyAccessToken } from '../middlewares/auth.middleware.js'; import { verifyAdminPermission } from '../middlewares/verifyAdminPermission.middleware.js'; @@ -32,4 +33,11 @@ router.put( updateUserAccount, ); +router.put( + '/api/users/update-password/:id', + verifyAccessToken, + verifyUserPermission, + updateUserPassword, +); + export default router; From d294e08792e6d73c5ba08d06e6f3d24b3022176c Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 5 Apr 2025 14:46:33 +0200 Subject: [PATCH 6/6] feat: add update password End point and test it --- src/middlewares/verifyUserPermission.middleware.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/middlewares/verifyUserPermission.middleware.js b/src/middlewares/verifyUserPermission.middleware.js index 5969def..03a4acb 100644 --- a/src/middlewares/verifyUserPermission.middleware.js +++ b/src/middlewares/verifyUserPermission.middleware.js @@ -1,5 +1,9 @@ export const verifyUserPermission = (req, res, next) => { - if (req.params.id === req.user.id || req.user.role === 'ADMIN') { + if ( + req.params.id === req.user.id || + req.user.role === 'ADMIN' || + req.user.email === req.params.email + ) { return next(); } // Otherwise deny access