feat: implement authenticated user password change functionality, including a new DTO, service method, and API endpoint.

This commit is contained in:
pradeepkumar
2026-02-06 09:45:01 +05:30
parent 750cb55764
commit 895a106d1b
4 changed files with 113 additions and 0 deletions

View File

@@ -27,6 +27,7 @@ import {
ResetPasswordDto, ResetPasswordDto,
RefreshTokenDto, RefreshTokenDto,
VerifyEmailDto, VerifyEmailDto,
ChangePasswordDto,
} from './dto'; } from './dto';
import { import {
VerifyTwoFactorDto, VerifyTwoFactorDto,
@@ -201,6 +202,36 @@ export class AuthController {
return this.authService.resetPassword(dto); return this.authService.resetPassword(dto);
} }
// ==========================================
// CHANGE PASSWORD
// ==========================================
@Post('change-password')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.OK)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Change password for authenticated user' })
@ApiBody({ type: ChangePasswordDto })
@ApiResponse({
status: 200,
description: 'Password changed successfully',
schema: {
example: {
success: true,
data: {
message: 'Password changed successfully',
},
},
},
})
@ApiResponse({ status: 400, description: 'Current password is incorrect' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async changePassword(
@CurrentUser('id') userId: string,
@Body() dto: ChangePasswordDto,
) {
return this.authService.changePassword(userId, dto);
}
// ========================================== // ==========================================
// VERIFY EMAIL // VERIFY EMAIL
// ========================================== // ==========================================

View File

@@ -22,6 +22,7 @@ import {
ResetPasswordDto, ResetPasswordDto,
RefreshTokenDto, RefreshTokenDto,
VerifyEmailDto, VerifyEmailDto,
ChangePasswordDto,
} from './dto'; } from './dto';
import { UserRole, AuthProvider, UserStatus } from '@prisma/client'; import { UserRole, AuthProvider, UserStatus } from '@prisma/client';
import { TwoFactorService } from './two-factor/two-factor.service'; import { TwoFactorService } from './two-factor/two-factor.service';
@@ -449,6 +450,62 @@ export class AuthService {
} }
} }
// ==========================================
// CHANGE PASSWORD
// ==========================================
async changePassword(userId: string, dto: ChangePasswordDto) {
const user = await this.prisma.user.findUnique({
where: { id: userId },
});
if (!user) {
throw new NotFoundException('User not found');
}
if (!user.password) {
throw new BadRequestException(
'This account uses social login. Password cannot be changed.',
);
}
// Verify current password
const isCurrentPasswordValid = await argon2.verify(
user.password,
dto.currentPassword,
);
if (!isCurrentPasswordValid) {
throw new BadRequestException('Current password is incorrect');
}
// Check if new password is same as current
const isSamePassword = await argon2.verify(user.password, dto.newPassword);
if (isSamePassword) {
throw new BadRequestException(
'New password must be different from current password',
);
}
// Hash new password
const hashedPassword = await argon2.hash(dto.newPassword);
// Update password
await this.prisma.user.update({
where: { id: userId },
data: { password: hashedPassword },
});
// Emit event
this.eventEmitter.emit('password.changed', {
userId: user.id,
email: user.email,
});
return {
message: 'Password changed successfully',
};
}
// ========================================== // ==========================================
// VERIFY EMAIL // VERIFY EMAIL
// ========================================== // ==========================================

View File

@@ -0,0 +1,24 @@
import { IsNotEmpty, IsString, MinLength, Matches } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class ChangePasswordDto {
@ApiProperty({
description: 'Current password',
example: 'CurrentPass123!',
})
@IsNotEmpty({ message: 'Current password is required' })
@IsString()
currentPassword: string;
@ApiProperty({
description: 'New password - minimum 8 characters with uppercase, lowercase, and number',
example: 'NewPass123!',
})
@IsNotEmpty({ message: 'New password is required' })
@IsString()
@MinLength(8, { message: 'Password must be at least 8 characters long' })
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain at least one uppercase letter, one lowercase letter, and one number',
})
newPassword: string;
}

View File

@@ -5,3 +5,4 @@ export * from './forgot-password.dto';
export * from './reset-password.dto'; export * from './reset-password.dto';
export * from './refresh-token.dto'; export * from './refresh-token.dto';
export * from './verify-email.dto'; export * from './verify-email.dto';
export * from './change-password.dto';