update password
This commit is contained in:
parent
aa3b2cb817
commit
f1457864df
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
|
||||||
code: 'cf1a3f828287',
|
|
||||||
username: 'john doe',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
async getRegisterToken(email: string, scene: EmailScene) {
|
|
||||||
const user = await this.prismaService.user.findUnique({ where: { email } })
|
|
||||||
if (user) {
|
if (user) {
|
||||||
throw new ConflictException(`邮箱${email}已注册`)
|
throw new ConflictException(`邮箱${email}已注册`)
|
||||||
}
|
}
|
||||||
const verificationCode = this.generateVerificationCode()
|
break
|
||||||
|
case EmailScene.updatePassword:
|
||||||
|
await this.prismaService.user.findUniqueOrThrow({
|
||||||
|
where: { email },
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case EmailScene.updateEmail:
|
||||||
|
await this.prismaService.user.findUniqueOrThrow({
|
||||||
|
where: { email },
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
17
src/users/dto/update-password.dto.ts
Normal file
17
src/users/dto/update-password.dto.ts
Normal 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
|
||||||
|
}
|
@ -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
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -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('请输入正确的邮箱验证码')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user