diff --git a/src/email/dto/email.dto.ts b/src/email/dto/email.dto.ts index 9de4128..acde50c 100644 --- a/src/email/dto/email.dto.ts +++ b/src/email/dto/email.dto.ts @@ -3,7 +3,8 @@ import { IsEmail, IsNotEmpty } from 'class-validator' export enum EmailScene { register = 'register', - forgetPassword = 'forgetPassword', + updatePassword = 'updatePassword', + updateEmail = 'updateEmail', } export class EmailSendDto { diff --git a/src/email/email.controller.ts b/src/email/email.controller.ts index dc5db09..eab53b5 100644 --- a/src/email/email.controller.ts +++ b/src/email/email.controller.ts @@ -1,22 +1,16 @@ -import { Body, Controller, Query, Post } from '@nestjs/common' +import { Body, Controller, Post } from '@nestjs/common' import { EmailService } from './email.service' -import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger' -import { EmailSendDto, EmailScene } from './dto/email.dto' +import { ApiTags, ApiOperation } from '@nestjs/swagger' +import { EmailSendDto } from './dto/email.dto' @ApiTags('Email') @Controller('api/email') export class EmailController { constructor(private readonly emailService: EmailService) {} - // @ApiOperation({ summary: '测试邮件' }) - // @Post('test') - // async sendEmailTo(@Body() payload: EmailDto) { - // return this.emailService.sendEmailTo(payload.email) - // } - @ApiOperation({ summary: '发送邮箱验证码' }) - @Post('verificationCode') - async getRegisterToken(@Body() payload: EmailSendDto) { - return this.emailService.getRegisterToken(payload.email, payload.scene) + @Post('verifyCode') + async sendEmailCode(@Body() payload: EmailSendDto) { + return this.emailService.sendEmailToken(payload.email, payload.scene) } } diff --git a/src/email/email.service.ts b/src/email/email.service.ts index dd15eb4..2b25a86 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -7,6 +7,11 @@ import { PrismaService } from 'nestjs-prisma' @Injectable() export class EmailService { + private subjectMap = { + [EmailScene.register]: '注册账号', + [EmailScene.updatePassword]: '修改密码', + [EmailScene.updateEmail]: '修改邮箱', + } constructor( private prismaService: PrismaService, private mailerService: MailerService, @@ -15,42 +20,46 @@ export class EmailService { private secureConfig: SecurityConfig, ) {} - async sendEmailTo(email: string) { - return this.mailerService.sendMail({ - to: email, // list of receivers - subject: 'Testing Nest Mailermodule with template ✔', - template: 'index', // The `.pug` or `.hbs` extension is appended automatically. - context: { - // Data to be sent to template engine. - code: 'cf1a3f828287', - username: 'john doe', - }, - }) - } - - async getRegisterToken(email: string, scene: EmailScene) { - const user = await this.prismaService.user.findUnique({ where: { email } }) - if (user) { - throw new ConflictException(`邮箱${email}已注册`) + async sendEmailToken(email: string, scene: EmailScene) { + switch (scene) { + case EmailScene.register: + const user = await this.prismaService.user.findUnique({ + where: { email }, + }) + if (user) { + throw new ConflictException(`邮箱${email}已注册`) + } + break + case EmailScene.updatePassword: + await this.prismaService.user.findUniqueOrThrow({ + where: { email }, + }) + break + case EmailScene.updateEmail: + await this.prismaService.user.findUniqueOrThrow({ + where: { email }, + }) + break } - const verificationCode = this.generateVerificationCode() + + const verifyCode = this.generateVerifyCode() const registerToken = this.jwtService.sign( { email, scene }, - { secret: this.getEmailJwtSecret(verificationCode, scene) }, + { secret: this.getEmailJwtSecret(verifyCode, scene) }, ) await this.mailerService.sendMail({ to: email, - subject: '注册qiuxu.site', - html: `您正在注册qiuxu.site,验证码为 ${verificationCode},30分钟内有效`, + subject: `【qiuxu.site】${this.subjectMap[scene]}`, + html: `您正在qiuxu.site${this.subjectMap[scene]},验证码为 ${verifyCode},30分钟内有效`, }) return registerToken } - getEmailJwtSecret(verificationCode: string, scene: EmailScene) { - return this.secureConfig.jwt_access_secret + verificationCode + scene + getEmailJwtSecret(verifyCode: string, scene: EmailScene) { + return this.secureConfig.jwt_access_secret + verifyCode + scene } - private generateVerificationCode() { + private generateVerifyCode() { return Math.floor(Math.random() * 899999 + 100000).toString() } } diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts index d9f1a7a..95147dc 100644 --- a/src/users/dto/create-user.dto.ts +++ b/src/users/dto/create-user.dto.ts @@ -22,7 +22,7 @@ export class CreateUserDto { /** @description 验证码 */ @IsNotEmpty() @Length(6, 6) - verificationCode: string + verifyCode: string /** @description 发送邮箱接口返回的Token */ @IsNotEmpty() diff --git a/src/users/dto/update-password.dto.ts b/src/users/dto/update-password.dto.ts new file mode 100644 index 0000000..85ae7d8 --- /dev/null +++ b/src/users/dto/update-password.dto.ts @@ -0,0 +1,17 @@ +import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator' + +export class UpdatePassword { + @IsNotEmpty() + @IsEmail() + email: string + + @IsNotEmpty() + verifyCode: string + + @IsNotEmpty() + token: string + + @IsNotEmpty() + @IsStrongPassword() + password: string +} diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 8c13848..9ebce58 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,4 +1,11 @@ -import { Controller, Get, Post, Body, UseInterceptors } from '@nestjs/common' +import { + Controller, + Get, + Post, + Patch, + Body, + UseInterceptors, +} from '@nestjs/common' import { UsersService } from './users.service' import { ApiTags, ApiOperation } from '@nestjs/swagger' import { User } from 'src/common/decorators/user.decorator' @@ -7,6 +14,7 @@ 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' @ApiTags('User') @Controller('api/users') @@ -29,4 +37,17 @@ export class UsersController { async register(@Body() userData: CreateUserDto) { return this.userService.register(userData) } + + @ApiOperation({ summary: '修改密码' }) + @UseInterceptors(PasswordInterceptor) + @Patch('me/password') + async updatePassord(@Body() payload: UpdatePassword): Promise { + return this.userService.updatePassword(payload) + } + + // @ApiOperation({ summary: '修改邮箱' }) + // @Patch('me/email') + // async updateEmail(@Body() payload: unknown) { + // return + // } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 1d41738..3a85ff8 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -6,9 +6,8 @@ import { securityConfig, SecurityConfig } from 'src/common/configs' 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 { Prisma } from '@prisma/client' import { TokenService } from './token.service' - +import { UpdatePassword } from './dto/update-password.dto' @Injectable() export class UsersService { constructor( @@ -24,27 +23,9 @@ export class UsersService { await this.verifyEmail( userToCreate.email, userToCreate.token, - userToCreate.verificationCode, + userToCreate.verifyCode, EmailScene.register, ) - return this.createUser(userToCreate) - } - - private async verifyEmail( - email: string, - token: string, - verificationCode: string, - scene: EmailScene, - ) { - const payload = this.jwtService.verify(token, { - secret: this.emailService.getEmailJwtSecret(verificationCode, scene), - }) - if (payload.email !== email || payload.scene !== scene) { - throw new ForbiddenException('请输入正确的邮箱验证码') - } - } - - private async createUser(userToCreate: Prisma.UserCreateInput) { const hashedPassword = await bcrypt.hash( userToCreate.password, this.secureConfig.bcryptSaltOrRound, @@ -58,4 +39,36 @@ export class UsersService { }) return this.tokenService.generateTokens({ userId: user.id }) } + + async updatePassword(payload: UpdatePassword) { + await this.verifyEmail( + payload.email, + payload.token, + payload.verifyCode, + EmailScene.updatePassword, + ) + const hashedPassword = await bcrypt.hash( + payload.password, + this.secureConfig.bcryptSaltOrRound, + ) + const user = await this.prismaService.user.update({ + where: { email: payload.email }, + data: { password: hashedPassword }, + }) + return user + } + + private async verifyEmail( + email: string, + token: string, + verifyCode: string, + scene: EmailScene, + ) { + const payload = this.jwtService.verify(token, { + secret: this.emailService.getEmailJwtSecret(verifyCode, scene), + }) + if (payload.email !== email || payload.scene !== scene) { + throw new ForbiddenException('请输入正确的邮箱验证码') + } + } }