diff --git a/src/users/change-password.dto.ts b/src/users/change-password.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..25ff3cc07eef7d4866593b93e390f30e23e5ea1d --- /dev/null +++ b/src/users/change-password.dto.ts @@ -0,0 +1,6 @@ +import { IsNotEmpty } from 'class-validator'; + +export class PasswordChangeDto { + @IsNotEmpty() readonly newPassword: string; + @IsNotEmpty() readonly oldPassword: string; +} diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 8d80697be368cc3c180d8a1c6894c2e1be5d9353..18efa8f93224b9e4615ef5a58f2e3519e558e102 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Get, Param, Post, Query, Req, Request, UseGuards } from '@nestjs/common'; import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { PasswordChangeDto } from './change-password.dto'; import { CreateUserDto } from './create-user.dto'; import { UsersService } from './users.service'; @@ -30,4 +31,17 @@ export class UsersController { public async validateUser(@Param() params, @Query('token') token: string) { return this.usersService.validateUser(params.id, token); } + + @UseGuards(JwtAuthGuard) + @Post('change-password') + @ApiResponse({ status: 201, description: 'Password changed' }) + @ApiResponse({ status: 401, description: 'Invalid password' }) + @ApiResponse({ status: 422, description: 'Weak password' }) + public async changePassword(@Request() req, @Body() passwordChangeDto: PasswordChangeDto) { + return this.usersService.changeUserPassword( + req.user._id, + passwordChangeDto.oldPassword, + passwordChangeDto.newPassword + ); + } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 4ea11e51fc03c9026ee7712013df0ae79bc06a11..c4db445d0543fa225909b8e352b73eea1f04ce27 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -66,7 +66,10 @@ export class UsersService { return this.userModel.findOne({ email: mail }).select('-password').exec(); } - public async findById(id: string): Promise<IUser | undefined> { + public async findById(id: string, passwordQuery?: boolean): Promise<IUser | undefined> { + if (passwordQuery) { + return this.userModel.findById(id).exec(); + } return this.userModel.findById(id).select('-password').exec(); } @@ -130,4 +133,20 @@ export class UsersService { throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); } } + + public async changeUserPassword(userId: string, oldPassword: string, newPassword: string) { + const user = await this.findById(userId, true); + const arePasswordEqual = await this.comparePassword(oldPassword, user.password); + if (!arePasswordEqual) { + throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + } + if (!this.isStrongPassword(newPassword)) { + throw new HttpException( + 'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer', + HttpStatus.UNPROCESSABLE_ENTITY + ); + } + user.password = await this.hashPassword(newPassword); + user.save(); + } }