update password

This commit is contained in:
秦秋旭 2023-02-22 16:09:31 +08:00
parent aa3b2cb817
commit f1457864df
7 changed files with 115 additions and 60 deletions

View File

@ -3,7 +3,8 @@ import { IsEmail, IsNotEmpty } from 'class-validator'
export enum EmailScene { export enum EmailScene {
register = 'register', register = 'register',
forgetPassword = 'forgetPassword', updatePassword = 'updatePassword',
updateEmail = 'updateEmail',
} }
export class EmailSendDto { export class EmailSendDto {

View File

@ -1,22 +1,16 @@
import { Body, Controller, Query, Post } from '@nestjs/common' import { Body, Controller, Post } from '@nestjs/common'
import { EmailService } from './email.service' import { EmailService } from './email.service'
import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger' import { ApiTags, ApiOperation } from '@nestjs/swagger'
import { EmailSendDto, EmailScene } from './dto/email.dto' import { EmailSendDto } from './dto/email.dto'
@ApiTags('Email') @ApiTags('Email')
@Controller('api/email') @Controller('api/email')
export class EmailController { export class EmailController {
constructor(private readonly emailService: EmailService) {} constructor(private readonly emailService: EmailService) {}
// @ApiOperation({ summary: '测试邮件' })
// @Post('test')
// async sendEmailTo(@Body() payload: EmailDto) {
// return this.emailService.sendEmailTo(payload.email)
// }
@ApiOperation({ summary: '发送邮箱验证码' }) @ApiOperation({ summary: '发送邮箱验证码' })
@Post('verificationCode') @Post('verifyCode')
async getRegisterToken(@Body() payload: EmailSendDto) { async sendEmailCode(@Body() payload: EmailSendDto) {
return this.emailService.getRegisterToken(payload.email, payload.scene) return this.emailService.sendEmailToken(payload.email, payload.scene)
} }
} }

View File

@ -7,6 +7,11 @@ import { PrismaService } from 'nestjs-prisma'
@Injectable() @Injectable()
export class EmailService { export class EmailService {
private subjectMap = {
[EmailScene.register]: '注册账号',
[EmailScene.updatePassword]: '修改密码',
[EmailScene.updateEmail]: '修改邮箱',
}
constructor( constructor(
private prismaService: PrismaService, private prismaService: PrismaService,
private mailerService: MailerService, private mailerService: MailerService,
@ -15,42 +20,46 @@ export class EmailService {
private secureConfig: SecurityConfig, private secureConfig: SecurityConfig,
) {} ) {}
async sendEmailTo(email: string) { async sendEmailToken(email: string, scene: EmailScene) {
return this.mailerService.sendMail({ switch (scene) {
to: email, // list of receivers case EmailScene.register:
subject: 'Testing Nest Mailermodule with template ✔', const user = await this.prismaService.user.findUnique({
template: 'index', // The `.pug` or `.hbs` extension is appended automatically. where: { email },
context: { })
// Data to be sent to template engine. if (user) {
code: 'cf1a3f828287', throw new ConflictException(`邮箱${email}已注册`)
username: 'john doe', }
}, break
}) case EmailScene.updatePassword:
} await this.prismaService.user.findUniqueOrThrow({
where: { email },
async getRegisterToken(email: string, scene: EmailScene) { })
const user = await this.prismaService.user.findUnique({ where: { email } }) break
if (user) { case EmailScene.updateEmail:
throw new ConflictException(`邮箱${email}已注册`) await this.prismaService.user.findUniqueOrThrow({
where: { email },
})
break
} }
const verificationCode = this.generateVerificationCode()
const verifyCode = this.generateVerifyCode()
const registerToken = this.jwtService.sign( const registerToken = this.jwtService.sign(
{ email, scene }, { email, scene },
{ secret: this.getEmailJwtSecret(verificationCode, scene) }, { secret: this.getEmailJwtSecret(verifyCode, scene) },
) )
await this.mailerService.sendMail({ await this.mailerService.sendMail({
to: email, to: email,
subject: '注册qiuxu.site', subject: `【qiuxu.site】${this.subjectMap[scene]}`,
html: `您正在注册qiuxu.site验证码为 <strong>${verificationCode}</strong>30分钟内有效`, html: `您正在qiuxu.site${this.subjectMap[scene]},验证码为 <strong>${verifyCode}</strong>30分钟内有效`,
}) })
return registerToken return registerToken
} }
getEmailJwtSecret(verificationCode: string, scene: EmailScene) { getEmailJwtSecret(verifyCode: string, scene: EmailScene) {
return this.secureConfig.jwt_access_secret + verificationCode + scene return this.secureConfig.jwt_access_secret + verifyCode + scene
} }
private generateVerificationCode() { private generateVerifyCode() {
return Math.floor(Math.random() * 899999 + 100000).toString() return Math.floor(Math.random() * 899999 + 100000).toString()
} }
} }

View File

@ -22,7 +22,7 @@ export class CreateUserDto {
/** @description 验证码 */ /** @description 验证码 */
@IsNotEmpty() @IsNotEmpty()
@Length(6, 6) @Length(6, 6)
verificationCode: string verifyCode: string
/** @description 发送邮箱接口返回的Token */ /** @description 发送邮箱接口返回的Token */
@IsNotEmpty() @IsNotEmpty()

View File

@ -0,0 +1,17 @@
import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator'
export class UpdatePassword {
@IsNotEmpty()
@IsEmail()
email: string
@IsNotEmpty()
verifyCode: string
@IsNotEmpty()
token: string
@IsNotEmpty()
@IsStrongPassword()
password: string
}

View File

@ -1,4 +1,11 @@
import { Controller, Get, Post, Body, UseInterceptors } from '@nestjs/common' import {
Controller,
Get,
Post,
Patch,
Body,
UseInterceptors,
} from '@nestjs/common'
import { UsersService } from './users.service' import { UsersService } from './users.service'
import { ApiTags, ApiOperation } from '@nestjs/swagger' import { ApiTags, ApiOperation } from '@nestjs/swagger'
import { User } from 'src/common/decorators/user.decorator' import { User } from 'src/common/decorators/user.decorator'
@ -7,6 +14,7 @@ import { PasswordInterceptor } from 'src/common/interceptors/password.intercepto
import { PrismaService } from 'nestjs-prisma' import { PrismaService } from 'nestjs-prisma'
import { UserEntity } from './entities/user.entity' import { UserEntity } from './entities/user.entity'
import { CreateUserDto } from './dto/create-user.dto' import { CreateUserDto } from './dto/create-user.dto'
import { UpdatePassword } from './dto/update-password.dto'
@ApiTags('User') @ApiTags('User')
@Controller('api/users') @Controller('api/users')
@ -29,4 +37,17 @@ export class UsersController {
async register(@Body() userData: CreateUserDto) { async register(@Body() userData: CreateUserDto) {
return this.userService.register(userData) return this.userService.register(userData)
} }
@ApiOperation({ summary: '修改密码' })
@UseInterceptors(PasswordInterceptor)
@Patch('me/password')
async updatePassord(@Body() payload: UpdatePassword): Promise<UserEntity> {
return this.userService.updatePassword(payload)
}
// @ApiOperation({ summary: '修改邮箱' })
// @Patch('me/email')
// async updateEmail(@Body() payload: unknown) {
// return
// }
} }

View File

@ -6,9 +6,8 @@ import { securityConfig, SecurityConfig } from 'src/common/configs'
import { CreateUserDto } from 'src/users/dto/create-user.dto' import { CreateUserDto } from 'src/users/dto/create-user.dto'
import { EmailSendDto, EmailScene } from 'src/email/dto/email.dto' import { EmailSendDto, EmailScene } from 'src/email/dto/email.dto'
import { EmailService } from 'src/email/email.service' import { EmailService } from 'src/email/email.service'
import { Prisma } from '@prisma/client'
import { TokenService } from './token.service' import { TokenService } from './token.service'
import { UpdatePassword } from './dto/update-password.dto'
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor( constructor(
@ -24,27 +23,9 @@ export class UsersService {
await this.verifyEmail( await this.verifyEmail(
userToCreate.email, userToCreate.email,
userToCreate.token, userToCreate.token,
userToCreate.verificationCode, userToCreate.verifyCode,
EmailScene.register, EmailScene.register,
) )
return this.createUser(userToCreate)
}
private async verifyEmail(
email: string,
token: string,
verificationCode: string,
scene: EmailScene,
) {
const payload = this.jwtService.verify<EmailSendDto>(token, {
secret: this.emailService.getEmailJwtSecret(verificationCode, scene),
})
if (payload.email !== email || payload.scene !== scene) {
throw new ForbiddenException('请输入正确的邮箱验证码')
}
}
private async createUser(userToCreate: Prisma.UserCreateInput) {
const hashedPassword = await bcrypt.hash( const hashedPassword = await bcrypt.hash(
userToCreate.password, userToCreate.password,
this.secureConfig.bcryptSaltOrRound, this.secureConfig.bcryptSaltOrRound,
@ -58,4 +39,36 @@ export class UsersService {
}) })
return this.tokenService.generateTokens({ userId: user.id }) return this.tokenService.generateTokens({ userId: user.id })
} }
async updatePassword(payload: UpdatePassword) {
await this.verifyEmail(
payload.email,
payload.token,
payload.verifyCode,
EmailScene.updatePassword,
)
const hashedPassword = await bcrypt.hash(
payload.password,
this.secureConfig.bcryptSaltOrRound,
)
const user = await this.prismaService.user.update({
where: { email: payload.email },
data: { password: hashedPassword },
})
return user
}
private async verifyEmail(
email: string,
token: string,
verifyCode: string,
scene: EmailScene,
) {
const payload = this.jwtService.verify<EmailSendDto>(token, {
secret: this.emailService.getEmailJwtSecret(verifyCode, scene),
})
if (payload.email !== email || payload.scene !== scene) {
throw new ForbiddenException('请输入正确的邮箱验证码')
}
}
} }