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