add EmailVeriyGuard
This commit is contained in:
		
							parent
							
								
									19452bb01e
								
							
						
					
					
						commit
						03ad1187ce
					
				| @ -17,3 +17,17 @@ export class EmailSendDto { | |||||||
|   @ApiProperty({ enum: EmailScene, enumName: 'EmailScene' }) |   @ApiProperty({ enum: EmailScene, enumName: 'EmailScene' }) | ||||||
|   scene: EmailScene |   scene: EmailScene | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export class EmailVerifyDto { | ||||||
|  |   @IsNotEmpty() | ||||||
|  |   @IsEmail() | ||||||
|  |   email: string | ||||||
|  | 
 | ||||||
|  |   /** @description 发送邮箱接口返回的Token */ | ||||||
|  |   @IsNotEmpty() | ||||||
|  |   token: string | ||||||
|  | 
 | ||||||
|  |   /** @description 邮箱验证码 */ | ||||||
|  |   @IsNotEmpty() | ||||||
|  |   verifyCode: string | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										50
									
								
								src/email/email-verify.decorator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/email/email-verify.decorator.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<EmailScene>( | ||||||
|  |       EMAIL_SCENE_KEY, | ||||||
|  |       ctx.getHandler(), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     const request = ctx.switchToHttp().getRequest<Request>() | ||||||
|  |     const { token, email, verifyCode } = request.body | ||||||
|  | 
 | ||||||
|  |     const payload = this.jwtService.verify<EmailSendDto>(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), | ||||||
|  |   ) | ||||||
|  | } | ||||||
| @ -2,14 +2,13 @@ import { | |||||||
|   Inject, |   Inject, | ||||||
|   Injectable, |   Injectable, | ||||||
|   ConflictException, |   ConflictException, | ||||||
|   ForbiddenException, |  | ||||||
|   NotFoundException, |   NotFoundException, | ||||||
| } from '@nestjs/common' | } from '@nestjs/common' | ||||||
| import { securityConfig, SecurityConfig } from 'src/common/configs' | import { securityConfig, SecurityConfig } from 'src/common/configs' | ||||||
| import { MailerService } from '@nestjs-modules/mailer' | import { MailerService } from '@nestjs-modules/mailer' | ||||||
| import { JwtService } from '@nestjs/jwt' | import { JwtService } from '@nestjs/jwt' | ||||||
| import { PrismaService } from 'nestjs-prisma' | 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' | import { UserEntity } from 'src/users/entities/user.entity' | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
| @ -60,23 +59,7 @@ export class EmailService { | |||||||
|     return { token, userId: user?.id } |     return { token, userId: user?.id } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // TODO: 做成守卫?
 |   getEmailJwtSecret(verifyCode: string, scene: EmailScene) { | ||||||
|   async verifyEmail(data: { |  | ||||||
|     email: string |  | ||||||
|     token: string |  | ||||||
|     verifyCode: string |  | ||||||
|     scene: EmailScene |  | ||||||
|   }) { |  | ||||||
|     const { email, token, verifyCode, scene } = data |  | ||||||
|     const payload = this.jwtService.verify<EmailSendDto>(token, { |  | ||||||
|       secret: this.getEmailJwtSecret(verifyCode, scene), |  | ||||||
|     }) |  | ||||||
|     if (payload.email !== email || payload.scene !== scene) { |  | ||||||
|       throw new ForbiddenException('请输入正确的邮箱验证码') |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private getEmailJwtSecret(verifyCode: string, scene: EmailScene) { |  | ||||||
|     return this.secureConfig.jwt_access_secret + verifyCode + scene |     return this.secureConfig.jwt_access_secret + verifyCode + scene | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,13 +1,3 @@ | |||||||
| import { IsNotEmpty, IsEmail } from 'class-validator' | import { EmailVerifyDto } from 'src/email/dto/email.dto' | ||||||
| 
 | 
 | ||||||
| export class ChangeEmailDto { | export class ChangeEmailDto extends EmailVerifyDto {} | ||||||
|   @IsNotEmpty() |  | ||||||
|   @IsEmail() |  | ||||||
|   email: string |  | ||||||
| 
 |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   verifyCode: string |  | ||||||
| 
 |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   token: string |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -2,15 +2,11 @@ import { | |||||||
|   IsNotEmpty, |   IsNotEmpty, | ||||||
|   IsOptional, |   IsOptional, | ||||||
|   Length, |   Length, | ||||||
|   IsEmail, |  | ||||||
|   IsStrongPassword, |   IsStrongPassword, | ||||||
| } from 'class-validator' | } from 'class-validator' | ||||||
|  | import { EmailVerifyDto } from 'src/email/dto/email.dto' | ||||||
| 
 | 
 | ||||||
| export class CreateUserDto { | export class CreateUserDto extends EmailVerifyDto { | ||||||
|   @IsNotEmpty() |  | ||||||
|   @IsEmail() |  | ||||||
|   email: string |  | ||||||
| 
 |  | ||||||
|   @IsOptional() |   @IsOptional() | ||||||
|   @Length(5, 20) |   @Length(5, 20) | ||||||
|   username?: string |   username?: string | ||||||
| @ -18,13 +14,4 @@ export class CreateUserDto { | |||||||
|   @IsNotEmpty() |   @IsNotEmpty() | ||||||
|   @IsStrongPassword() |   @IsStrongPassword() | ||||||
|   password: string |   password: string | ||||||
| 
 |  | ||||||
|   /** @description 验证码 */ |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   @Length(6, 6) |  | ||||||
|   verifyCode: string |  | ||||||
| 
 |  | ||||||
|   /** @description 发送邮箱接口返回的Token */ |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   token: string |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,16 +1,7 @@ | |||||||
| import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator' | import { IsNotEmpty, IsStrongPassword } from 'class-validator' | ||||||
| 
 | import { EmailVerifyDto } from 'src/email/dto/email.dto' | ||||||
| export class DeleteUserDto { |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   @IsEmail() |  | ||||||
|   email: string |  | ||||||
| 
 |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   verifyCode: string |  | ||||||
| 
 |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   token: string |  | ||||||
| 
 | 
 | ||||||
|  | export class DeleteUserDto extends EmailVerifyDto { | ||||||
|   @IsNotEmpty() |   @IsNotEmpty() | ||||||
|   @IsStrongPassword() |   @IsStrongPassword() | ||||||
|   password: string |   password: string | ||||||
|  | |||||||
| @ -1,16 +1,7 @@ | |||||||
| import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator' | import { IsNotEmpty, IsStrongPassword } from 'class-validator' | ||||||
| 
 | import { EmailVerifyDto } from 'src/email/dto/email.dto' | ||||||
| export class ResetPassword { |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   @IsEmail() |  | ||||||
|   email: string |  | ||||||
| 
 |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   verifyCode: string |  | ||||||
| 
 |  | ||||||
|   @IsNotEmpty() |  | ||||||
|   token: string |  | ||||||
| 
 | 
 | ||||||
|  | export class ResetPassword extends EmailVerifyDto { | ||||||
|   @IsNotEmpty() |   @IsNotEmpty() | ||||||
|   @IsStrongPassword() |   @IsStrongPassword() | ||||||
|   password: string |   password: string | ||||||
|  | |||||||
| @ -87,7 +87,6 @@ export class MeController { | |||||||
|     @Body() payload: ChangeEmailDto, |     @Body() payload: ChangeEmailDto, | ||||||
|     @User('userId') userId: string, |     @User('userId') userId: string, | ||||||
|   ): Promise<UserEntity> { |   ): Promise<UserEntity> { | ||||||
|     console.log(query) |  | ||||||
|     return this.meService.changeEmail(payload, userId) |     return this.meService.changeEmail(payload, userId) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,29 +9,23 @@ import { PrismaService } from 'nestjs-prisma' | |||||||
| import { JwtService } from '@nestjs/jwt' | import { JwtService } from '@nestjs/jwt' | ||||||
| import { securityConfig, SecurityConfig } from 'src/common/configs' | import { securityConfig, SecurityConfig } from 'src/common/configs' | ||||||
| import { EmailScene } from 'src/email/dto/email.dto' | import { EmailScene } from 'src/email/dto/email.dto' | ||||||
| import { EmailService } from 'src/email/email.service' |  | ||||||
| import { DeleteUserDto } from './dto/delete-user.dto' | import { DeleteUserDto } from './dto/delete-user.dto' | ||||||
| import { ChangePassword } from './dto/change-password.dto' | import { ChangePassword } from './dto/change-password.dto' | ||||||
| import { TokenContnet } from './dto/token.dto' | import { TokenContnet } from './dto/token.dto' | ||||||
| import { ChangeEmailDto } from './dto/change-email.dto' | import { ChangeEmailDto } from './dto/change-email.dto' | ||||||
|  | import { VerifyEmail } from 'src/email/email-verify.decorator' | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
| export class MeService { | export class MeService { | ||||||
|   constructor( |   constructor( | ||||||
|     private jwtService: JwtService, |     private jwtService: JwtService, | ||||||
|     private prismaService: PrismaService, |     private prismaService: PrismaService, | ||||||
|     private emailService: EmailService, |  | ||||||
|     @Inject(securityConfig.KEY) private secureConfig: SecurityConfig, |     @Inject(securityConfig.KEY) private secureConfig: SecurityConfig, | ||||||
|   ) {} |   ) {} | ||||||
| 
 | 
 | ||||||
|  |   @VerifyEmail(EmailScene.deleteUser) | ||||||
|   async deleteUser(userToDelete: DeleteUserDto, userId: string) { |   async deleteUser(userToDelete: DeleteUserDto, userId: string) { | ||||||
|     const { email, token, verifyCode, password } = userToDelete |     const { password } = userToDelete | ||||||
|     await this.emailService.verifyEmail({ |  | ||||||
|       email, |  | ||||||
|       token, |  | ||||||
|       verifyCode, |  | ||||||
|       scene: EmailScene.deleteUser, |  | ||||||
|     }) |  | ||||||
|     const user = await this.prismaService.user.findUniqueOrThrow({ |     const user = await this.prismaService.user.findUniqueOrThrow({ | ||||||
|       where: { email: userToDelete.email }, |       where: { email: userToDelete.email }, | ||||||
|     }) |     }) | ||||||
| @ -63,14 +57,9 @@ export class MeService { | |||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   @VerifyEmail(EmailScene.changeEmail) | ||||||
|   async changeEmail(payload: ChangeEmailDto, userId: string) { |   async changeEmail(payload: ChangeEmailDto, userId: string) { | ||||||
|     const { email, token, verifyCode } = payload |     const { email } = payload | ||||||
|     await this.emailService.verifyEmail({ |  | ||||||
|       email, |  | ||||||
|       token, |  | ||||||
|       verifyCode, |  | ||||||
|       scene: EmailScene.changeEmail, |  | ||||||
|     }) |  | ||||||
|     return this.prismaService.user.update({ |     return this.prismaService.user.update({ | ||||||
|       where: { id: userId }, |       where: { id: userId }, | ||||||
|       data: { email }, |       data: { email }, | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ import { EmailScene } from 'src/email/dto/email.dto' | |||||||
| import { EmailService } from 'src/email/email.service' | import { EmailService } from 'src/email/email.service' | ||||||
| import { ResetPassword } from './dto/reset-password.dto' | import { ResetPassword } from './dto/reset-password.dto' | ||||||
| import { Token, TokenPayload } from './dto/token.dto' | import { Token, TokenPayload } from './dto/token.dto' | ||||||
|  | import { VerifyEmail } from 'src/email/email-verify.decorator' | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
| export class UsersService { | export class UsersService { | ||||||
| @ -18,14 +19,9 @@ export class UsersService { | |||||||
|     @Inject(securityConfig.KEY) private secureConfig: SecurityConfig, |     @Inject(securityConfig.KEY) private secureConfig: SecurityConfig, | ||||||
|   ) {} |   ) {} | ||||||
| 
 | 
 | ||||||
|  |   @VerifyEmail(EmailScene.register) | ||||||
|   async registerByEmail(userToCreate: CreateUserDto) { |   async registerByEmail(userToCreate: CreateUserDto) { | ||||||
|     const { email, token, verifyCode, username, password } = userToCreate |     const { email, username, password } = userToCreate | ||||||
|     await this.emailService.verifyEmail({ |  | ||||||
|       email, |  | ||||||
|       token, |  | ||||||
|       verifyCode, |  | ||||||
|       scene: EmailScene.register, |  | ||||||
|     }) |  | ||||||
|     const hashedPassword = await bcrypt.hash( |     const hashedPassword = await bcrypt.hash( | ||||||
|       password, |       password, | ||||||
|       this.secureConfig.bcryptSaltOrRound, |       this.secureConfig.bcryptSaltOrRound, | ||||||
| @ -50,14 +46,9 @@ export class UsersService { | |||||||
|     return this.generateTokens({ userId: user.id }) |     return this.generateTokens({ userId: user.id }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   @VerifyEmail(EmailScene.forgetPassword) | ||||||
|   async resetPasswordByEmail(data: ResetPassword, userId: string) { |   async resetPasswordByEmail(data: ResetPassword, userId: string) { | ||||||
|     const { email, token, verifyCode, password } = data |     const { password } = data | ||||||
|     await this.emailService.verifyEmail({ |  | ||||||
|       email, |  | ||||||
|       token, |  | ||||||
|       verifyCode, |  | ||||||
|       scene: EmailScene.forgetPassword, |  | ||||||
|     }) |  | ||||||
|     const hashedPassword = await bcrypt.hash( |     const hashedPassword = await bcrypt.hash( | ||||||
|       password, |       password, | ||||||
|       this.secureConfig.bcryptSaltOrRound, |       this.secureConfig.bcryptSaltOrRound, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user