♻️ merge auth module into users module

This commit is contained in:
秦秋旭 2023-02-22 10:41:01 +08:00
parent c4f6e168e5
commit aa3b2cb817
16 changed files with 155 additions and 155 deletions

View File

@ -2,8 +2,8 @@ import { Logger, Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config' import { ConfigModule } from '@nestjs/config'
import { nestConfig, securityConfig, emailConfig } from 'src/common/configs' import { nestConfig, securityConfig, emailConfig } from 'src/common/configs'
import { PrismaModule, loggingMiddleware } from 'nestjs-prisma' import { PrismaModule, loggingMiddleware } from 'nestjs-prisma'
import { JwtAuthStrategy } from './common/guards/jwt-auth.strategy'
import { UsersModule } from './users/users.module' import { UsersModule } from './users/users.module'
import { AuthModule } from './auth/auth.module'
import { EmailModule } from './email/email.module' import { EmailModule } from './email/email.module'
@Module({ @Module({
@ -25,9 +25,8 @@ import { EmailModule } from './email/email.module'
}), }),
UsersModule, UsersModule,
AuthModule,
EmailModule, EmailModule,
], ],
controllers: [], providers: [JwtAuthStrategy],
}) })
export class AppModule {} export class AppModule {}

View File

@ -1,31 +0,0 @@
import { Body, Controller, Post, Put } from '@nestjs/common'
import { AuthService } from './auth.service'
import { CreateUserDto } from 'src/users/dto/create-user.dto'
import { ApiTags, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger'
import { LoginInputDto } from './dto/login-input.dto'
import { TokenRefreshPayload } from './dto/token.dto'
@ApiTags('Auth')
@Controller('api/auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@ApiOperation({ summary: '注册用户返回token' })
@Post('register')
async register(@Body() userData: CreateUserDto) {
return this.authService.register(userData)
}
@ApiOperation({ summary: '登录用户返回token' })
@Post('login')
async login(@Body() user: LoginInputDto) {
return this.authService.login(user.email, user.password)
}
@ApiOperation({ summary: '刷新token' })
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@Put('token')
async refreshToken(@Body() payload: TokenRefreshPayload) {
return this.authService.refreshToken(payload.refreshToken)
}
}

View File

@ -1,12 +0,0 @@
import { Module } from '@nestjs/common'
import { AuthService } from './auth.service'
import { AuthController } from './auth.controller'
import { JwtService } from '@nestjs/jwt'
import { JwtStrategy } from './strategies/jwt.strategy'
import { EmailService } from 'src/email/email.service'
@Module({
controllers: [AuthController],
providers: [AuthService, JwtService, JwtStrategy, EmailService],
})
export class AuthModule {}

View File

@ -1,95 +0,0 @@
import {
Inject,
Injectable,
ForbiddenException,
UnauthorizedException,
} from '@nestjs/common'
import * as bcrypt from 'bcrypt'
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 { CreateUserDto } from 'src/users/dto/create-user.dto'
import { EmailSendDto, EmailScene } from 'src/email/dto/email.dto'
import { EmailService } from 'src/email/email.service'
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
private prismaService: PrismaService,
private emailService: EmailService,
@Inject(securityConfig.KEY)
private secureConfig: SecurityConfig,
) {}
async register(userToCreate: CreateUserDto) {
const tokenPayload = this.jwtService.verify<EmailSendDto>(
userToCreate.token,
{
secret: this.emailService.getEmailJwtSecret(
userToCreate.verificationCode,
EmailScene.register,
),
},
)
if (
tokenPayload.email !== userToCreate.email ||
tokenPayload.scene !== 'register'
) {
throw new ForbiddenException('请输入正确的邮箱')
}
const hashedPassword = await bcrypt.hash(
userToCreate.password,
this.secureConfig.bcryptSaltOrRound,
)
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.prismaService.user.findUniqueOrThrow({
where: { email },
})
const passwordValid = await bcrypt.compare(password, user.password)
if (!passwordValid) {
throw new ForbiddenException('Invalid password')
}
return this.generateTokens({ userId: user.id })
}
async refreshToken(token: string) {
try {
const { userId } = this.jwtService.verify<TokenPayload>(token, {
secret: this.secureConfig.jwt_refresh_secret,
})
return this.generateTokens({ userId })
} catch (e) {
console.error(e)
throw new UnauthorizedException(e.message)
}
}
private generateTokens(payload: TokenPayload): Token {
const accessToken = this.jwtService.sign(payload, {
secret: this.secureConfig.jwt_access_secret,
expiresIn: this.secureConfig.expiresIn,
})
const refreshToken = this.jwtService.sign(payload, {
secret: this.secureConfig.jwt_refresh_secret,
expiresIn: this.secureConfig.refreshIn,
})
return { accessToken, refreshToken }
}
}

View File

@ -1,6 +1,6 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common' import { createParamDecorator, ExecutionContext } from '@nestjs/common'
import { type Request } from 'express' import { type Request } from 'express'
import { TokenPayload } from 'src/auth/dto/token.dto' import { TokenPayload } from 'src/users/dto/token.dto'
export const User = createParamDecorator( export const User = createParamDecorator(
(key: keyof TokenPayload, ctx: ExecutionContext) => { (key: keyof TokenPayload, ctx: ExecutionContext) => {

View File

@ -2,7 +2,7 @@ import {
Catch, Catch,
Logger, Logger,
ArgumentsHost, ArgumentsHost,
ForbiddenException, UnauthorizedException,
} from '@nestjs/common' } from '@nestjs/common'
import { BaseExceptionFilter } from '@nestjs/core' import { BaseExceptionFilter } from '@nestjs/core'
import { JsonWebTokenError } from 'jsonwebtoken' import { JsonWebTokenError } from 'jsonwebtoken'
@ -13,6 +13,6 @@ export class JwtExceptionsFilter extends BaseExceptionFilter {
catch(exception: JsonWebTokenError, host: ArgumentsHost) { catch(exception: JsonWebTokenError, host: ArgumentsHost) {
this.logger.error(exception) this.logger.error(exception)
super.catch(new ForbiddenException(exception.message), host) super.catch(new UnauthorizedException(exception.message), host)
} }
} }

View File

@ -1,11 +1,11 @@
import { Strategy, ExtractJwt } from 'passport-jwt' import { Strategy, ExtractJwt } from 'passport-jwt'
import { PassportStrategy } from '@nestjs/passport' import { PassportStrategy } from '@nestjs/passport'
import { Injectable, Inject } from '@nestjs/common' import { Injectable, Inject } from '@nestjs/common'
import { TokenPayload } from '../dto/token.dto' import { TokenPayload } from 'src/users/dto/token.dto'
import { securityConfig, SecurityConfig } from 'src/common/configs' import { securityConfig, SecurityConfig } from 'src/common/configs'
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) { export class JwtAuthStrategy extends PassportStrategy(Strategy) {
constructor( constructor(
@Inject(securityConfig.KEY) @Inject(securityConfig.KEY)
readonly secureConfig: SecurityConfig, readonly secureConfig: SecurityConfig,

View File

@ -15,7 +15,7 @@ export class EmailController {
// } // }
@ApiOperation({ summary: '发送邮箱验证码' }) @ApiOperation({ summary: '发送邮箱验证码' })
@Post() @Post('verificationCode')
async getRegisterToken(@Body() payload: EmailSendDto) { async getRegisterToken(@Body() payload: EmailSendDto) {
return this.emailService.getRegisterToken(payload.email, payload.scene) return this.emailService.getRegisterToken(payload.email, payload.scene)
} }

View File

@ -33,7 +33,7 @@ export class EmailService {
if (user) { if (user) {
throw new ConflictException(`邮箱${email}已注册`) throw new ConflictException(`邮箱${email}已注册`)
} }
const verificationCode = this.generateRandomNum() const verificationCode = this.generateVerificationCode()
const registerToken = this.jwtService.sign( const registerToken = this.jwtService.sign(
{ email, scene }, { email, scene },
{ secret: this.getEmailJwtSecret(verificationCode, scene) }, { secret: this.getEmailJwtSecret(verificationCode, scene) },
@ -50,7 +50,7 @@ export class EmailService {
return this.secureConfig.jwt_access_secret + verificationCode + scene return this.secureConfig.jwt_access_secret + verificationCode + scene
} }
private generateRandomNum() { private generateVerificationCode() {
return Math.floor(Math.random() * 899999 + 100000).toString() return Math.floor(Math.random() * 899999 + 100000).toString()
} }
} }

View File

@ -0,0 +1,24 @@
import { Controller, Post, Put, Body } from '@nestjs/common'
import { ApiTags, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger'
import { LoginInputDto } from './dto/login-input.dto'
import { TokenRefreshPayload } from './dto/token.dto'
import { TokenService } from './token.service'
@ApiTags('token')
@Controller('api/token')
export class TokenController {
constructor(private tokenService: TokenService) {}
@ApiOperation({ summary: '登录用户' })
@Post()
async login(@Body() user: LoginInputDto) {
return this.tokenService.login(user.email, user.password)
}
@ApiOperation({ summary: '刷新token' })
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@Put()
async refreshToken(@Body() payload: TokenRefreshPayload) {
return this.tokenService.refreshToken(payload.refreshToken)
}
}

View File

@ -0,0 +1,50 @@
import { Inject, Injectable, ForbiddenException } from '@nestjs/common'
import * as bcrypt from 'bcrypt'
import { PrismaService } from 'nestjs-prisma'
import { Token, TokenPayload } from './dto/token.dto'
import { JwtService } from '@nestjs/jwt'
import { securityConfig, SecurityConfig } from 'src/common/configs'
@Injectable()
export class TokenService {
constructor(
private jwtService: JwtService,
private prismaService: PrismaService,
@Inject(securityConfig.KEY)
private secureConfig: SecurityConfig,
) {}
async login(email: string, password: string) {
const user = await this.prismaService.user.findUniqueOrThrow({
where: { email },
})
const passwordValid = await bcrypt.compare(password, user.password)
if (!passwordValid) {
throw new ForbiddenException('Invalid password')
}
return this.generateTokens({ userId: user.id })
}
async refreshToken(token: string) {
const { userId } = this.jwtService.verify<TokenPayload>(token, {
secret: this.secureConfig.jwt_refresh_secret,
})
return this.generateTokens({ userId })
}
generateTokens(payload: TokenPayload): Token {
const accessToken = this.jwtService.sign(payload, {
secret: this.secureConfig.jwt_access_secret,
expiresIn: this.secureConfig.expiresIn,
})
const refreshToken = this.jwtService.sign(payload, {
secret: this.secureConfig.jwt_refresh_secret,
expiresIn: this.secureConfig.refreshIn,
})
return { accessToken, refreshToken }
}
}

View File

@ -1,11 +1,12 @@
import { Controller, Get, UseInterceptors } from '@nestjs/common' import { Controller, Get, Post, Body, UseInterceptors } from '@nestjs/common'
import { UsersService } from './users.service' import { UsersService } from './users.service'
import { ApiOperation, ApiTags } 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'
import { NeedAuth } from 'src/common/decorators/need-auth.decorator' import { NeedAuth } from 'src/common/decorators/need-auth.decorator'
import { PasswordInterceptor } from 'src/common/interceptors/password.interceptor' import { PasswordInterceptor } from 'src/common/interceptors/password.interceptor'
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'
@ApiTags('User') @ApiTags('User')
@Controller('api/users') @Controller('api/users')
@ -22,4 +23,10 @@ export class UsersController {
async getUserInfo(@User('userId') userId: string): Promise<UserEntity> { async getUserInfo(@User('userId') userId: string): Promise<UserEntity> {
return this.prismaService.user.findUniqueOrThrow({ where: { id: userId } }) return this.prismaService.user.findUniqueOrThrow({ where: { id: userId } })
} }
@ApiOperation({ summary: '注册用户' })
@Post()
async register(@Body() userData: CreateUserDto) {
return this.userService.register(userData)
}
} }

View File

@ -1,10 +1,14 @@
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { UsersService } from './users.service' import { UsersService } from './users.service'
import { UsersController } from './users.controller' import { UsersController } from './users.controller'
import { JwtService } from '@nestjs/jwt'
import { EmailService } from 'src/email/email.service'
import { TokenController } from './token.controller'
import { TokenService } from './token.service'
@Module({ @Module({
controllers: [UsersController], controllers: [UsersController, TokenController],
providers: [UsersService], providers: [UsersService, JwtService, EmailService, TokenService],
exports: [UsersService], exports: [UsersService],
}) })
export class UsersModule {} export class UsersModule {}

View File

@ -1,7 +1,61 @@
import { Injectable } from '@nestjs/common' import { Inject, Injectable, ForbiddenException } from '@nestjs/common'
import * as bcrypt from 'bcrypt'
import { PrismaService } from 'nestjs-prisma' import { PrismaService } from 'nestjs-prisma'
import { JwtService } from '@nestjs/jwt'
import { securityConfig, SecurityConfig } from 'src/common/configs'
import { CreateUserDto } from 'src/users/dto/create-user.dto'
import { EmailSendDto, EmailScene } from 'src/email/dto/email.dto'
import { EmailService } from 'src/email/email.service'
import { Prisma } from '@prisma/client'
import { TokenService } from './token.service'
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor(private prismaService: PrismaService) {} constructor(
private jwtService: JwtService,
private prismaService: PrismaService,
private emailService: EmailService,
private tokenService: TokenService,
@Inject(securityConfig.KEY)
private secureConfig: SecurityConfig,
) {}
async register(userToCreate: CreateUserDto) {
await this.verifyEmail(
userToCreate.email,
userToCreate.token,
userToCreate.verificationCode,
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(
userToCreate.password,
this.secureConfig.bcryptSaltOrRound,
)
const user = await this.prismaService.user.create({
data: {
username: userToCreate.username,
email: userToCreate.email,
password: hashedPassword,
},
})
return this.tokenService.generateTokens({ userId: user.id })
}
} }