From 2db40172b2c02d44079e44339223e0781dead6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E7=A7=8B=E6=97=AD?= Date: Tue, 21 Feb 2023 23:15:40 +0800 Subject: [PATCH] register needs to have a email verification code --- nest-cli.json | 7 +++- src/auth/auth.module.ts | 5 +-- src/auth/auth.service.ts | 52 ++++++++++++++++++++----- src/{users => auth}/password.service.ts | 0 src/email/dto/email.dto.ts | 12 +++++- src/email/email.controller.ts | 20 ++++++---- src/email/email.module.ts | 3 +- src/email/email.service.ts | 40 ++++++++++++++++++- src/users/dto/create-user.dto.ts | 9 +++++ src/users/users.module.ts | 5 +-- src/users/users.service.ts | 22 +---------- 11 files changed, 128 insertions(+), 47 deletions(-) rename src/{users => auth}/password.service.ts (100%) diff --git a/nest-cli.json b/nest-cli.json index 5026014..7807190 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -7,6 +7,11 @@ }, "compilerOptions": { "deleteOutDir": true, - "plugins": ["@nestjs/swagger/plugin"] + "plugins": [ + { + "name": "@nestjs/swagger/plugin", + "options": { "introspectComments": true } + } + ] } } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index eaddfe2..0edb061 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,13 +1,12 @@ import { Module } from '@nestjs/common' import { AuthService } from './auth.service' import { AuthController } from './auth.controller' -import { UsersModule } from 'src/users/users.module' +import { PasswordService } from './password.service' import { JwtService } from '@nestjs/jwt' import { JwtStrategy } from './strategies/jwt.strategy' @Module({ controllers: [AuthController], - providers: [AuthService, JwtService, JwtStrategy], - imports: [UsersModule], + providers: [AuthService, JwtService, JwtStrategy, PasswordService], }) export class AuthModule {} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 112de8c..f62c789 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,32 +1,66 @@ import { Inject, Injectable, - BadRequestException, + ForbiddenException, UnauthorizedException, } from '@nestjs/common' -import { PasswordService } from 'src/users/password.service' +import { PasswordService } from './password.service' +import { PrismaService } from 'nestjs-prisma' import { Token, TokenPayload } from './dto/token.dto' import { JwtService } from '@nestjs/jwt' import { securityConfig, SecurityConfig } from 'src/common/configs' -import { UsersService } from 'src/users/users.service' import { CreateUserDto } from 'src/users/dto/create-user.dto' +import { EmailSendDto } from 'src/email/dto/email.dto' + @Injectable() export class AuthService { constructor( private passwordService: PasswordService, private jwtService: JwtService, - private userService: UsersService, + private prismaService: PrismaService, @Inject(securityConfig.KEY) private secureConfig: SecurityConfig, ) {} - async register(payload: CreateUserDto) { - const user = await this.userService.createUser(payload) + async register(userToCreate: CreateUserDto) { + console.log(userToCreate) + try { + const tokenPayload = this.jwtService.verify( + userToCreate.token, + { + secret: + this.secureConfig.jwt_access_secret + + userToCreate.verificationCode + + 'register', + }, + ) + if ( + tokenPayload.email !== userToCreate.email || + tokenPayload.scene !== 'register' + ) { + throw new ForbiddenException('请输入正确的邮箱') + } + } catch (err) { + console.error(err) + throw new ForbiddenException(err.message) + } + const hashedPassword = await this.passwordService.hashPassword( + userToCreate.password, + ) + const user = await this.prismaService.user.create({ + data: { + username: userToCreate.username, + email: userToCreate.email, + password: hashedPassword, + }, + }) return this.generateTokens({ userId: user.id }) } async login(email: string, password: string) { - const user = await this.userService.findUser({ email }) + const user = await this.prismaService.user.findUniqueOrThrow({ + where: { email }, + }) const passwordValid = await this.passwordService.validatePassword( password, @@ -34,7 +68,7 @@ export class AuthService { ) if (!passwordValid) { - throw new BadRequestException('Invalid password') + throw new ForbiddenException('Invalid password') } return this.generateTokens({ userId: user.id }) @@ -42,7 +76,7 @@ export class AuthService { async refreshToken(token: string) { try { - const { userId } = this.jwtService.verify(token, { + const { userId } = this.jwtService.verify(token, { secret: this.secureConfig.jwt_refresh_secret, }) return this.generateTokens({ userId }) diff --git a/src/users/password.service.ts b/src/auth/password.service.ts similarity index 100% rename from src/users/password.service.ts rename to src/auth/password.service.ts diff --git a/src/email/dto/email.dto.ts b/src/email/dto/email.dto.ts index 9757de8..9de4128 100644 --- a/src/email/dto/email.dto.ts +++ b/src/email/dto/email.dto.ts @@ -1,7 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger' import { IsEmail, IsNotEmpty } from 'class-validator' -export class EmailDto { +export enum EmailScene { + register = 'register', + forgetPassword = 'forgetPassword', +} + +export class EmailSendDto { @IsNotEmpty() @IsEmail() email: string + + @IsNotEmpty() + @ApiProperty({ enum: EmailScene, enumName: 'EmailScene' }) + scene: EmailScene } diff --git a/src/email/email.controller.ts b/src/email/email.controller.ts index 0393418..cad8333 100644 --- a/src/email/email.controller.ts +++ b/src/email/email.controller.ts @@ -1,16 +1,22 @@ -import { Body, Controller, Post } from '@nestjs/common' +import { Body, Controller, Query, Post } from '@nestjs/common' import { EmailService } from './email.service' -import { ApiTags, ApiOperation } from '@nestjs/swagger' -import { EmailDto } from './dto/email.dto' +import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger' +import { EmailSendDto, EmailScene } from './dto/email.dto' @ApiTags('Email') @Controller('api/email') export class EmailController { constructor(private readonly emailService: EmailService) {} - @ApiOperation({ summary: '发送邮件' }) - @Post('test') - async sendEmailTo(@Body() payload: EmailDto) { - return this.emailService.sendEmailTo(payload.email) + // @ApiOperation({ summary: '测试邮件' }) + // @Post('test') + // async sendEmailTo(@Body() payload: EmailDto) { + // return this.emailService.sendEmailTo(payload.email) + // } + + @ApiOperation({ summary: '发送邮箱验证码' }) + @Post() + async getRegisterToken(@Body() payload: EmailSendDto) { + return this.emailService.getRegisterToken(payload.email, payload.scene) } } diff --git a/src/email/email.module.ts b/src/email/email.module.ts index fc74af4..439dc1d 100644 --- a/src/email/email.module.ts +++ b/src/email/email.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' import { EmailService } from './email.service' import { EmailController } from './email.controller' import { MailerModule } from '@nestjs-modules/mailer' @@ -31,6 +32,6 @@ import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handleba }), ], controllers: [EmailController], - providers: [EmailService], + providers: [EmailService, JwtService], }) export class EmailModule {} diff --git a/src/email/email.service.ts b/src/email/email.service.ts index 80c2546..4c36d2b 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -1,9 +1,20 @@ -import { Injectable } from '@nestjs/common' +import { ConflictException, Inject, Injectable } from '@nestjs/common' +import { securityConfig, SecurityConfig } from 'src/common/configs' import { MailerService } from '@nestjs-modules/mailer' +import { JwtService } from '@nestjs/jwt' +import { EmailScene } from './dto/email.dto' +import { PrismaService } from 'nestjs-prisma' @Injectable() export class EmailService { - constructor(private mailerService: MailerService) {} + constructor( + private prismaService: PrismaService, + private mailerService: MailerService, + private jwtService: JwtService, + @Inject(securityConfig.KEY) + private secureConfig: SecurityConfig, + ) {} + async sendEmailTo(email: string) { return this.mailerService.sendMail({ to: email, // list of receivers @@ -16,4 +27,29 @@ export class EmailService { }, }) } + + async getRegisterToken(email: string, scene: EmailScene) { + const user = await this.prismaService.user.findUnique({ where: { email } }) + if (user) { + throw new ConflictException(`邮箱${email}已注册`) + } + const verificationCode = this.generateRandomNum() + const registerToken = this.jwtService.sign( + { email, scene }, + { + secret: this.secureConfig.jwt_access_secret + verificationCode + scene, + expiresIn: '30min', + }, + ) + await this.mailerService.sendMail({ + to: email, + subject: '注册qiuxu.site', + html: `您正在注册qiuxu.site,验证码为 ${verificationCode},30分钟内有效`, + }) + return { registerToken, verificationCode } + } + + private generateRandomNum() { + return Math.floor(Math.random() * 899999 + 100000) + } } diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts index 720b864..d9f1a7a 100644 --- a/src/users/dto/create-user.dto.ts +++ b/src/users/dto/create-user.dto.ts @@ -18,4 +18,13 @@ export class CreateUserDto { @IsNotEmpty() @IsStrongPassword() password: string + + /** @description 验证码 */ + @IsNotEmpty() + @Length(6, 6) + verificationCode: string + + /** @description 发送邮箱接口返回的Token */ + @IsNotEmpty() + token: string } diff --git a/src/users/users.module.ts b/src/users/users.module.ts index 68ad0e6..2ca0e52 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common' import { UsersService } from './users.service' import { UsersController } from './users.controller' -import { PasswordService } from './password.service' @Module({ controllers: [UsersController], - providers: [UsersService, PasswordService], - exports: [UsersService, PasswordService], + providers: [UsersService], + exports: [UsersService], }) export class UsersModule {} diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 6699b29..14b390b 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,19 +1,14 @@ import { Injectable, NotFoundException } from '@nestjs/common' import { PrismaService } from 'nestjs-prisma' -import { CreateUserDto } from './dto/create-user.dto' -import { PasswordService } from './password.service' import { Prisma } from '@prisma/client' import { UserEntity } from './entities/user.entity' @Injectable() export class UsersService { - constructor( - private prisma: PrismaService, - private passwordService: PasswordService, - ) {} + constructor(private prismaService: PrismaService) {} async findUser(where: Prisma.UserWhereUniqueInput): Promise { - const user = await this.prisma.user.findUnique({ + const user = await this.prismaService.user.findUnique({ where, }) if (!user) { @@ -21,17 +16,4 @@ export class UsersService { } return user } - - async createUser(payload: CreateUserDto) { - const hashedPassword = await this.passwordService.hashPassword( - payload.password, - ) - const user = await this.prisma.user.create({ - data: { - ...payload, - password: hashedPassword, - }, - }) - return user - } }