diff --git a/src/email/dto/email.dto.ts b/src/email/dto/email.dto.ts index 1b3efdf..95c3ff8 100644 --- a/src/email/dto/email.dto.ts +++ b/src/email/dto/email.dto.ts @@ -17,3 +17,17 @@ export class EmailSendDto { @ApiProperty({ enum: EmailScene, enumName: 'EmailScene' }) scene: EmailScene } + +export class EmailVerifyDto { + @IsNotEmpty() + @IsEmail() + email: string + + /** @description 发送邮箱接口返回的Token */ + @IsNotEmpty() + token: string + + /** @description 邮箱验证码 */ + @IsNotEmpty() + verifyCode: string +} diff --git a/src/email/email-verify.decorator.ts b/src/email/email-verify.decorator.ts new file mode 100644 index 0000000..9b50d1d --- /dev/null +++ b/src/email/email-verify.decorator.ts @@ -0,0 +1,50 @@ +import { Reflector } from '@nestjs/core' +import { + Injectable, + CanActivate, + ExecutionContext, + applyDecorators, + UseGuards, + SetMetadata, + ForbiddenException, +} from '@nestjs/common' +import { EmailScene, EmailSendDto } from './dto/email.dto' +import { EmailService } from './email.service' +import { JwtService } from '@nestjs/jwt' +import { type Request } from 'express' + +const EMAIL_SCENE_KEY = 'EMAIL_SCENE' + +@Injectable() +class EmailVeriyGuard implements CanActivate { + constructor( + private emailService: EmailService, + private reflector: Reflector, + private jwtService: JwtService, + ) {} + + canActivate(ctx: ExecutionContext) { + const scene = this.reflector.get( + EMAIL_SCENE_KEY, + ctx.getHandler(), + ) + + const request = ctx.switchToHttp().getRequest() + const { token, email, verifyCode } = request.body + + const payload = this.jwtService.verify(token, { + secret: this.emailService.getEmailJwtSecret(verifyCode, scene), + }) + if (payload.email !== email || payload.scene !== scene) { + throw new ForbiddenException('请输入正确的邮箱验证码') + } + return true + } +} + +export function VerifyEmail(scene: EmailScene) { + return applyDecorators( + SetMetadata(EMAIL_SCENE_KEY, scene), + UseGuards(EmailVeriyGuard), + ) +} diff --git a/src/email/email.service.ts b/src/email/email.service.ts index e763495..2f74a22 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -2,14 +2,13 @@ import { Inject, Injectable, ConflictException, - ForbiddenException, NotFoundException, } from '@nestjs/common' import { securityConfig, SecurityConfig } from 'src/common/configs' import { MailerService } from '@nestjs-modules/mailer' import { JwtService } from '@nestjs/jwt' import { PrismaService } from 'nestjs-prisma' -import { EmailSendDto, EmailScene } from 'src/email/dto/email.dto' +import { EmailScene } from './dto/email.dto' import { UserEntity } from 'src/users/entities/user.entity' @Injectable() @@ -60,23 +59,7 @@ export class EmailService { return { token, userId: user?.id } } - // TODO: 做成守卫? - async verifyEmail(data: { - email: string - token: string - verifyCode: string - scene: EmailScene - }) { - const { email, token, verifyCode, scene } = data - const payload = this.jwtService.verify(token, { - secret: this.getEmailJwtSecret(verifyCode, scene), - }) - if (payload.email !== email || payload.scene !== scene) { - throw new ForbiddenException('请输入正确的邮箱验证码') - } - } - - private getEmailJwtSecret(verifyCode: string, scene: EmailScene) { + getEmailJwtSecret(verifyCode: string, scene: EmailScene) { return this.secureConfig.jwt_access_secret + verifyCode + scene } diff --git a/src/users/dto/change-email.dto.ts b/src/users/dto/change-email.dto.ts index 8f6dd9a..c4cf3d6 100644 --- a/src/users/dto/change-email.dto.ts +++ b/src/users/dto/change-email.dto.ts @@ -1,13 +1,3 @@ -import { IsNotEmpty, IsEmail } from 'class-validator' +import { EmailVerifyDto } from 'src/email/dto/email.dto' -export class ChangeEmailDto { - @IsNotEmpty() - @IsEmail() - email: string - - @IsNotEmpty() - verifyCode: string - - @IsNotEmpty() - token: string -} +export class ChangeEmailDto extends EmailVerifyDto {} diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts index 95147dc..791d2e0 100644 --- a/src/users/dto/create-user.dto.ts +++ b/src/users/dto/create-user.dto.ts @@ -2,15 +2,11 @@ import { IsNotEmpty, IsOptional, Length, - IsEmail, IsStrongPassword, } from 'class-validator' +import { EmailVerifyDto } from 'src/email/dto/email.dto' -export class CreateUserDto { - @IsNotEmpty() - @IsEmail() - email: string - +export class CreateUserDto extends EmailVerifyDto { @IsOptional() @Length(5, 20) username?: string @@ -18,13 +14,4 @@ export class CreateUserDto { @IsNotEmpty() @IsStrongPassword() password: string - - /** @description 验证码 */ - @IsNotEmpty() - @Length(6, 6) - verifyCode: string - - /** @description 发送邮箱接口返回的Token */ - @IsNotEmpty() - token: string } diff --git a/src/users/dto/delete-user.dto.ts b/src/users/dto/delete-user.dto.ts index 4d377a8..5ceda9d 100644 --- a/src/users/dto/delete-user.dto.ts +++ b/src/users/dto/delete-user.dto.ts @@ -1,16 +1,7 @@ -import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator' - -export class DeleteUserDto { - @IsNotEmpty() - @IsEmail() - email: string - - @IsNotEmpty() - verifyCode: string - - @IsNotEmpty() - token: string +import { IsNotEmpty, IsStrongPassword } from 'class-validator' +import { EmailVerifyDto } from 'src/email/dto/email.dto' +export class DeleteUserDto extends EmailVerifyDto { @IsNotEmpty() @IsStrongPassword() password: string diff --git a/src/users/dto/reset-password.dto.ts b/src/users/dto/reset-password.dto.ts index 0b6ac38..e35ffc1 100644 --- a/src/users/dto/reset-password.dto.ts +++ b/src/users/dto/reset-password.dto.ts @@ -1,16 +1,7 @@ -import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator' - -export class ResetPassword { - @IsNotEmpty() - @IsEmail() - email: string - - @IsNotEmpty() - verifyCode: string - - @IsNotEmpty() - token: string +import { IsNotEmpty, IsStrongPassword } from 'class-validator' +import { EmailVerifyDto } from 'src/email/dto/email.dto' +export class ResetPassword extends EmailVerifyDto { @IsNotEmpty() @IsStrongPassword() password: string diff --git a/src/users/me.controller.ts b/src/users/me.controller.ts index 9d0db1b..30d6063 100644 --- a/src/users/me.controller.ts +++ b/src/users/me.controller.ts @@ -87,7 +87,6 @@ export class MeController { @Body() payload: ChangeEmailDto, @User('userId') userId: string, ): Promise { - console.log(query) return this.meService.changeEmail(payload, userId) } diff --git a/src/users/me.service.ts b/src/users/me.service.ts index 9074384..92b8a6f 100644 --- a/src/users/me.service.ts +++ b/src/users/me.service.ts @@ -9,29 +9,23 @@ import { PrismaService } from 'nestjs-prisma' import { JwtService } from '@nestjs/jwt' import { securityConfig, SecurityConfig } from 'src/common/configs' import { EmailScene } from 'src/email/dto/email.dto' -import { EmailService } from 'src/email/email.service' import { DeleteUserDto } from './dto/delete-user.dto' import { ChangePassword } from './dto/change-password.dto' import { TokenContnet } from './dto/token.dto' import { ChangeEmailDto } from './dto/change-email.dto' +import { VerifyEmail } from 'src/email/email-verify.decorator' @Injectable() export class MeService { constructor( private jwtService: JwtService, private prismaService: PrismaService, - private emailService: EmailService, @Inject(securityConfig.KEY) private secureConfig: SecurityConfig, ) {} + @VerifyEmail(EmailScene.deleteUser) async deleteUser(userToDelete: DeleteUserDto, userId: string) { - const { email, token, verifyCode, password } = userToDelete - await this.emailService.verifyEmail({ - email, - token, - verifyCode, - scene: EmailScene.deleteUser, - }) + const { password } = userToDelete const user = await this.prismaService.user.findUniqueOrThrow({ where: { email: userToDelete.email }, }) @@ -63,14 +57,9 @@ export class MeService { }) } + @VerifyEmail(EmailScene.changeEmail) async changeEmail(payload: ChangeEmailDto, userId: string) { - const { email, token, verifyCode } = payload - await this.emailService.verifyEmail({ - email, - token, - verifyCode, - scene: EmailScene.changeEmail, - }) + const { email } = payload return this.prismaService.user.update({ where: { id: userId }, data: { email }, diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 99642b8..88b3694 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -8,6 +8,7 @@ import { EmailScene } from 'src/email/dto/email.dto' import { EmailService } from 'src/email/email.service' import { ResetPassword } from './dto/reset-password.dto' import { Token, TokenPayload } from './dto/token.dto' +import { VerifyEmail } from 'src/email/email-verify.decorator' @Injectable() export class UsersService { @@ -18,14 +19,9 @@ export class UsersService { @Inject(securityConfig.KEY) private secureConfig: SecurityConfig, ) {} + @VerifyEmail(EmailScene.register) async registerByEmail(userToCreate: CreateUserDto) { - const { email, token, verifyCode, username, password } = userToCreate - await this.emailService.verifyEmail({ - email, - token, - verifyCode, - scene: EmailScene.register, - }) + const { email, username, password } = userToCreate const hashedPassword = await bcrypt.hash( password, this.secureConfig.bcryptSaltOrRound, @@ -50,14 +46,9 @@ export class UsersService { return this.generateTokens({ userId: user.id }) } + @VerifyEmail(EmailScene.forgetPassword) async resetPasswordByEmail(data: ResetPassword, userId: string) { - const { email, token, verifyCode, password } = data - await this.emailService.verifyEmail({ - email, - token, - verifyCode, - scene: EmailScene.forgetPassword, - }) + const { password } = data const hashedPassword = await bcrypt.hash( password, this.secureConfig.bcryptSaltOrRound,