From d3c86263054bb326de3934c7129d5cb70c73b20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E7=A7=8B=E6=97=AD?= Date: Thu, 23 Feb 2023 14:23:51 +0800 Subject: [PATCH] updateUserInfo & forgetPassword --- src/email/dto/email.dto.ts | 4 +- src/email/email.service.ts | 8 +-- src/users/dto/change-password.dto.ts | 10 ++++ ...password.dto.ts => forget-password.dto.ts} | 2 +- src/users/dto/update-user.dto.ts | 6 ++ src/users/token.controller.ts | 1 + src/users/users.controller.ts | 58 ++++++++++++++++--- src/users/users.service.ts | 29 +++++++++- 8 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 src/users/dto/change-password.dto.ts rename src/users/dto/{update-password.dto.ts => forget-password.dto.ts} (89%) create mode 100644 src/users/dto/update-user.dto.ts diff --git a/src/email/dto/email.dto.ts b/src/email/dto/email.dto.ts index 7199a13..1b3efdf 100644 --- a/src/email/dto/email.dto.ts +++ b/src/email/dto/email.dto.ts @@ -3,8 +3,8 @@ import { IsEmail, IsNotEmpty } from 'class-validator' export enum EmailScene { register = 'register', - updatePassword = 'updatePassword', - updateEmail = 'updateEmail', + forgetPassword = 'forgetPassword', + changeEmail = 'changeEmail', deleteUser = 'deleteUser', } diff --git a/src/email/email.service.ts b/src/email/email.service.ts index 3d70c2b..775bb6d 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -14,8 +14,8 @@ import { PrismaService } from 'nestjs-prisma' export class EmailService { private subjectMap = { [EmailScene.register]: '注册账号', - [EmailScene.updatePassword]: '修改密码', - [EmailScene.updateEmail]: '修改邮箱', + [EmailScene.forgetPassword]: '找回密码', + [EmailScene.changeEmail]: '修改邮箱', [EmailScene.deleteUser]: '删除用户', } constructor( @@ -32,12 +32,12 @@ export class EmailService { }) switch (scene) { case EmailScene.register: - case EmailScene.updateEmail: + case EmailScene.changeEmail: if (user) { throw new ConflictException(`邮箱${email}已注册`) } break - case EmailScene.updatePassword: + case EmailScene.forgetPassword: case EmailScene.deleteUser: if (!user) { throw new NotFoundException(`用户${email}不存在`) diff --git a/src/users/dto/change-password.dto.ts b/src/users/dto/change-password.dto.ts new file mode 100644 index 0000000..4abf9ac --- /dev/null +++ b/src/users/dto/change-password.dto.ts @@ -0,0 +1,10 @@ +import { IsNotEmpty, IsStrongPassword } from 'class-validator' + +export class ChangePassword { + @IsNotEmpty() + oldPassword: string + + @IsNotEmpty() + @IsStrongPassword() + newPassword: string +} diff --git a/src/users/dto/update-password.dto.ts b/src/users/dto/forget-password.dto.ts similarity index 89% rename from src/users/dto/update-password.dto.ts rename to src/users/dto/forget-password.dto.ts index 85ae7d8..f766e99 100644 --- a/src/users/dto/update-password.dto.ts +++ b/src/users/dto/forget-password.dto.ts @@ -1,6 +1,6 @@ import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator' -export class UpdatePassword { +export class ForgetPassword { @IsNotEmpty() @IsEmail() email: string diff --git a/src/users/dto/update-user.dto.ts b/src/users/dto/update-user.dto.ts new file mode 100644 index 0000000..81cebc6 --- /dev/null +++ b/src/users/dto/update-user.dto.ts @@ -0,0 +1,6 @@ +import { IsOptional } from 'class-validator' + +export class UpdateUserDto { + @IsOptional() + username?: string +} diff --git a/src/users/token.controller.ts b/src/users/token.controller.ts index 17ddd3a..8b3e49c 100644 --- a/src/users/token.controller.ts +++ b/src/users/token.controller.ts @@ -9,6 +9,7 @@ import { TokenService } from './token.service' export class TokenController { constructor(private tokenService: TokenService) {} + // TODO: 限制调用频率,避免暴力破解 @ApiOperation({ summary: '登录用户' }) @Post() async login(@Body() user: LoginInputDto) { diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index d4c1003..9840f30 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -3,9 +3,11 @@ import { Get, Post, Delete, + Put, Patch, Body, UseInterceptors, + BadRequestException, } from '@nestjs/common' import { UsersService } from './users.service' import { ApiTags, ApiOperation } from '@nestjs/swagger' @@ -15,10 +17,11 @@ import { PasswordInterceptor } from 'src/common/interceptors/password.intercepto import { PrismaService } from 'nestjs-prisma' import { UserEntity } from './entities/user.entity' import { CreateUserDto } from './dto/create-user.dto' -import { UpdatePassword } from './dto/update-password.dto' +import { ForgetPassword } from './dto/forget-password.dto' import { DeleteUserDto } from './dto/delete-user.dto' +import { ChangePassword } from './dto/change-password.dto' +import { UpdateUserDto } from './dto/update-user.dto' -@ApiTags('User') @Controller('api/users') export class UsersController { constructor( @@ -26,6 +29,7 @@ export class UsersController { private readonly prismaService: PrismaService, ) {} + @ApiTags('Me') @ApiOperation({ summary: '获取用户信息' }) @UseInterceptors(PasswordInterceptor) @NeedAuth() @@ -34,12 +38,32 @@ export class UsersController { return this.prismaService.user.findUniqueOrThrow({ where: { id: userId } }) } + @ApiTags('Me') + @ApiOperation({ summary: '修改用户信息(用户名等)' }) + @UseInterceptors(PasswordInterceptor) + @NeedAuth() + @Patch('me') + async updateUserInfo( + @Body() payload: UpdateUserDto, + @User('userId') userId: string, + ): Promise { + if (Object.keys(payload).length === 0) { + throw new BadRequestException() + } + return this.prismaService.user.update({ + where: { id: userId }, + data: payload, + }) + } + + @ApiTags('User') @ApiOperation({ summary: '注册用户' }) @Post() async register(@Body() userData: CreateUserDto) { return this.userService.register(userData) } + @ApiTags('Me') @NeedAuth() @ApiOperation({ summary: '删除用户' }) @Delete('me') @@ -50,16 +74,32 @@ export class UsersController { return this.userService.deleteUser(userData, userId) } + @ApiTags('Me') + @NeedAuth() @ApiOperation({ summary: '修改密码' }) @UseInterceptors(PasswordInterceptor) @Patch('me/password') - async updatePassord(@Body() payload: UpdatePassword): Promise { - return this.userService.updatePassword(payload) + async changePassword( + @Body() payload: ChangePassword, + @User('userId') userId: string, + ) { + return this.userService.changePasswor(payload, userId) } - // @ApiOperation({ summary: '修改邮箱' }) - // @Patch('me/email') - // async updateEmail(@Body() payload: unknown) { - // return - // } + @ApiTags('User') + @ApiOperation({ summary: '忘记密码' }) + @UseInterceptors(PasswordInterceptor) + @Patch('password') + async forgetPassword(@Body() payload: ForgetPassword): Promise { + return this.userService.forgetPassword(payload) + } + + @ApiTags('Me') + @NeedAuth() + @ApiOperation({ summary: '修改邮箱(TODO)' }) + @UseInterceptors(PasswordInterceptor) + @Patch('me/email') + async updateEmail(@Body() payload: unknown) { + return '修改邮箱' + } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index cc0bc16..9566fed 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -7,8 +7,9 @@ import { CreateUserDto } from 'src/users/dto/create-user.dto' import { EmailSendDto, EmailScene } from 'src/email/dto/email.dto' import { EmailService } from 'src/email/email.service' import { TokenService } from './token.service' -import { UpdatePassword } from './dto/update-password.dto' +import { ForgetPassword } from './dto/forget-password.dto' import { DeleteUserDto } from './dto/delete-user.dto' +import { ChangePassword } from './dto/change-password.dto' @Injectable() export class UsersService { @@ -68,12 +69,27 @@ export class UsersService { }) } - async updatePassword(payload: UpdatePassword) { + async changePasswor(payload: ChangePassword, userId: string) { + const user = await this.prismaService.user.findFirstOrThrow({ + where: { id: userId }, + }) + await this.checkPassword(payload.oldPassword, user.password) + const hashedPassword = await bcrypt.hash( + payload.newPassword, + this.secureConfig.bcryptSaltOrRound, + ) + return this.prismaService.user.update({ + where: { id: userId }, + data: { password: hashedPassword }, + }) + } + + async forgetPassword(payload: ForgetPassword) { await this.verifyEmail( payload.email, payload.token, payload.verifyCode, - EmailScene.updatePassword, + EmailScene.forgetPassword, ) const hashedPassword = await bcrypt.hash( payload.password, @@ -86,6 +102,13 @@ export class UsersService { return user } + private async checkPassword(pwd: string, hashPwd: string) { + const valid = await bcrypt.compare(pwd, hashPwd) + if (!valid) { + throw new ForbiddenException('Invalid password') + } + } + private async verifyEmail( email: string, token: string,