From a6cddc6318e7cbd6fe58cc5b17a6f7a0f9a5843f Mon Sep 17 00:00:00 2001 From: JakkrapartXD Date: Wed, 14 Jan 2026 14:06:09 +0700 Subject: [PATCH] move chage password to user con --- Backend/src/controllers/AuthController.ts | 27 +------------ Backend/src/controllers/UserController.ts | 27 +++++++++++++ Backend/src/services/auth.service.ts | 28 -------------- Backend/src/services/user.service.ts | 46 ++++++++++++++++++++++- Backend/src/validators/auth.validator.ts | 2 +- Backend/src/validators/user.validator.ts | 12 +++++- 6 files changed, 85 insertions(+), 57 deletions(-) diff --git a/Backend/src/controllers/AuthController.ts b/Backend/src/controllers/AuthController.ts index ef80adbc..fc149fd9 100644 --- a/Backend/src/controllers/AuthController.ts +++ b/Backend/src/controllers/AuthController.ts @@ -8,10 +8,9 @@ import { ResetPasswordRequest, LoginResponse, RegisterResponse, - RefreshTokenResponse, - ChangePassword + RefreshTokenResponse } from '../types/auth.types'; -import { loginSchema, registerSchema, refreshTokenSchema, resetRequestSchema, resetPasswordSchema, changePasswordSchema } from '../validators/auth.validator'; +import { loginSchema, registerSchema, refreshTokenSchema, resetRequestSchema, resetPasswordSchema } from '../validators/auth.validator'; import { ValidationError } from '../middleware/errorHandler'; @Route('api/auth') @@ -172,26 +171,4 @@ export class AuthController { } return await this.authService.resetPassword(body.token, body.password); } - - /** - * Change password - * @summary Change password using old password - * @param body User ID, old password and new password - * @returns Success message - */ - @Post('change-password') - @Security('jwt') - @SuccessResponse('200', 'Password changed successfully') - @Response('401', 'Invalid or expired reset token') - public async changePassword(@Request() request: any, @Body() body: ChangePassword): Promise<{ message: string }> { - const { error } = changePasswordSchema.validate(body); - if (error) { - throw new ValidationError(error.details[0].message); - } - const token = request.headers.authorization?.replace('Bearer ', ''); - if (!token) { - throw new ValidationError('No token provided'); - } - return await this.authService.changePassword(token, body.oldPassword, body.newPassword); - } } diff --git a/Backend/src/controllers/UserController.ts b/Backend/src/controllers/UserController.ts index 1250a03f..8f4cecc0 100644 --- a/Backend/src/controllers/UserController.ts +++ b/Backend/src/controllers/UserController.ts @@ -8,6 +8,7 @@ import { ChangePasswordRequest, ChangePasswordResponse } from '../types/user.types'; +import { ChangePassword } from '../types/auth.types'; import { profileUpdateSchema, changePasswordSchema } from "../validators/user.validator"; import { ValidationError } from '../middleware/errorHandler'; @@ -34,4 +35,30 @@ export class UserController { } return await this.userService.getUserProfile(token); } + + /** + * Change password + * @summary Change user password using old password + * @param request Express request object with JWT token in Authorization header + * @param body Old password and new password + * @returns Success message + */ + @Post('change-password') + @Security('jwt') + @SuccessResponse('200', 'Password changed successfully') + @Response('401', 'Invalid old password or token') + @Response('400', 'Validation error') + public async changePassword(@Request() request: any, @Body() body: ChangePassword): Promise { + const { error } = changePasswordSchema.validate(body); + if (error) { + throw new ValidationError(error.details[0].message); + } + + const token = request.headers.authorization?.replace('Bearer ', ''); + if (!token) { + throw new ValidationError('No token provided'); + } + + return await this.userService.changePassword(token, body.oldPassword, body.newPassword); + } } diff --git a/Backend/src/services/auth.service.ts b/Backend/src/services/auth.service.ts index 9d15bb5d..e19ef2e2 100644 --- a/Backend/src/services/auth.service.ts +++ b/Backend/src/services/auth.service.ts @@ -11,7 +11,6 @@ import { RegisterResponse, RefreshTokenResponse, ResetPasswordResponse, - ChangePasswordResponse, ResetRequestResponse } from '../types/auth.types'; import { UserResponse } from '../types/user.types'; @@ -260,33 +259,6 @@ export class AuthService { } } - async changePassword(token: string, oldPassword: string, newPassword: string): Promise { - try { - const decoded = jwt.verify(token, config.jwt.secret) as { id: number; username: string; email: string; roleCode: string }; - const user = await prisma.user.findUnique({ where: { id: decoded.id } }); - if (!user) throw new UnauthorizedError('User not found'); - - const isPasswordValid = await bcrypt.compare(oldPassword, user.password); - if (!isPasswordValid) throw new UnauthorizedError('Invalid password'); - - const encryptedPassword = await bcrypt.hash(newPassword, 10); - - await prisma.user.update({ - where: { id: user.id }, - data: { password: encryptedPassword } - }); - - logger.info('Password changed successfully', { userId: user.id }); - return { - code: 200, - message: 'Password changed successfully' - }; - } catch (error) { - logger.error('Failed to change password', { error }); - throw error; - } - } - /** * Generate access token (JWT) */ diff --git a/Backend/src/services/user.service.ts b/Backend/src/services/user.service.ts index 6f9ab94f..3c0e75c7 100644 --- a/Backend/src/services/user.service.ts +++ b/Backend/src/services/user.service.ts @@ -3,6 +3,7 @@ import { Prisma } from '@prisma/client'; import { config } from '../config'; import { logger } from '../config/logger'; import jwt from 'jsonwebtoken'; +import bcrypt from 'bcrypt'; import { UserResponse, ProfileResponse, @@ -63,6 +64,50 @@ export class UserService { throw error; } }; + + /** + * Change user password + */ + async changePassword(token: string, oldPassword: string, newPassword: string): Promise { + try { + // Decode JWT token to get user ID + const decoded = jwt.verify(token, config.jwt.secret) as { id: number; username: string; email: string; roleCode: string }; + + const user = await prisma.user.findUnique({ where: { id: decoded.id } }); + if (!user) throw new UnauthorizedError('User not found'); + + // Verify old password + const isPasswordValid = await bcrypt.compare(oldPassword, user.password); + if (!isPasswordValid) throw new UnauthorizedError('Invalid old password'); + + // Hash new password + const encryptedPassword = await bcrypt.hash(newPassword, 10); + + // Update password + await prisma.user.update({ + where: { id: user.id }, + data: { password: encryptedPassword } + }); + + logger.info('Password changed successfully', { userId: user.id }); + return { + code: 200, + message: 'Password changed successfully' + }; + } catch (error) { + if (error instanceof jwt.JsonWebTokenError) { + logger.error('Invalid JWT token:', error); + throw new UnauthorizedError('Invalid token'); + } + if (error instanceof jwt.TokenExpiredError) { + logger.error('JWT token expired:', error); + throw new UnauthorizedError('Token expired'); + } + logger.error('Failed to change password', { error }); + throw error; + } + } + /** * Format user response */ @@ -88,4 +133,3 @@ export class UserService { }; } } - diff --git a/Backend/src/validators/auth.validator.ts b/Backend/src/validators/auth.validator.ts index 5f6a9fce..7341a156 100644 --- a/Backend/src/validators/auth.validator.ts +++ b/Backend/src/validators/auth.validator.ts @@ -120,7 +120,7 @@ export const changePasswordSchema = Joi.object({ export const resetRequestSchema = Joi.object({ email: Joi.string() - .email() + .email({ tlds: { allow: false } }) // Allow any TLD including .local .required() .messages({ 'string.email': 'Please provide a valid email address', diff --git a/Backend/src/validators/user.validator.ts b/Backend/src/validators/user.validator.ts index dc2e5f6f..79396915 100644 --- a/Backend/src/validators/user.validator.ts +++ b/Backend/src/validators/user.validator.ts @@ -22,14 +22,22 @@ export const profileUpdateSchema = Joi.object({ }); export const changePasswordSchema = Joi.object({ - old_password: Joi.string() + oldPassword: Joi.string() + .min(6) + .max(100) .required() .messages({ + 'string.min': 'Old password must be at least 6 characters', + 'string.max': 'Old password must not exceed 100 characters', 'any.required': 'Old password is required' }), - new_password: Joi.string() + newPassword: Joi.string() + .min(6) + .max(100) .required() .messages({ + 'string.min': 'New password must be at least 6 characters', + 'string.max': 'New password must not exceed 100 characters', 'any.required': 'New password is required' }) });