feat: implement authenticated user password change functionality, including a new DTO, service method, and API endpoint.
This commit is contained in:
@@ -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
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -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
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
24
src/auth/dto/change-password.dto.ts
Normal file
24
src/auth/dto/change-password.dto.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user