add EmailVeriyGuard
This commit is contained in:
		
							parent
							
								
									19452bb01e
								
							
						
					
					
						commit
						03ad1187ce
					
				| @ -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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										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, | ||||
|   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<EmailSendDto>(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 | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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 {} | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -87,7 +87,6 @@ export class MeController { | ||||
|     @Body() payload: ChangeEmailDto, | ||||
|     @User('userId') userId: string, | ||||
|   ): Promise<UserEntity> { | ||||
|     console.log(query) | ||||
|     return this.meService.changeEmail(payload, userId) | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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 }, | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user