♻️ merge auth module into users module
This commit is contained in:
parent
c4f6e168e5
commit
aa3b2cb817
@ -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 {}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 {}
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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) => {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
src/users/token.controller.ts
Normal file
24
src/users/token.controller.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
50
src/users/token.service.ts
Normal file
50
src/users/token.service.ts
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {}
|
||||||
|
@ -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 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user