add EmailVeriyGuard

This commit is contained in:
秦秋旭 2023-02-24 00:03:19 +08:00
parent 19452bb01e
commit 03ad1187ce
10 changed files with 86 additions and 101 deletions

View File

@ -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
}

View 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),
)
}

View File

@ -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
}

View File

@ -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 {}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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 },

View File

@ -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,