refactor: controllers
This commit is contained in:
parent
d3c8626305
commit
a863806111
@ -1,14 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
ConflictException,
|
|
||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
|
ConflictException,
|
||||||
|
ForbiddenException,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common'
|
} from '@nestjs/common'
|
||||||
import { securityConfig, SecurityConfig } from 'src/common/configs'
|
import { securityConfig, SecurityConfig } from 'src/common/configs'
|
||||||
import { MailerService } from '@nestjs-modules/mailer'
|
import { MailerService } from '@nestjs-modules/mailer'
|
||||||
import { JwtService } from '@nestjs/jwt'
|
import { JwtService } from '@nestjs/jwt'
|
||||||
import { EmailScene } from './dto/email.dto'
|
|
||||||
import { PrismaService } from 'nestjs-prisma'
|
import { PrismaService } from 'nestjs-prisma'
|
||||||
|
import { EmailSendDto, EmailScene } from 'src/email/dto/email.dto'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
@ -55,10 +56,26 @@ export class EmailService {
|
|||||||
subject: `【qiuxu.site】${this.subjectMap[scene]}`,
|
subject: `【qiuxu.site】${this.subjectMap[scene]}`,
|
||||||
html: `您正在qiuxu.site${this.subjectMap[scene]},验证码为 <strong>${verifyCode}</strong>,30分钟内有效`,
|
html: `您正在qiuxu.site${this.subjectMap[scene]},验证码为 <strong>${verifyCode}</strong>,30分钟内有效`,
|
||||||
})
|
})
|
||||||
return registerToken
|
return { registerToken, userId: user.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
getEmailJwtSecret(verifyCode: string, scene: EmailScene) {
|
// TODO: 做成守卫?
|
||||||
|
async verifyEmail(data: {
|
||||||
|
email: string
|
||||||
|
token: string
|
||||||
|
verifyCode: string
|
||||||
|
scene: EmailScene
|
||||||
|
}) {
|
||||||
|
const { email, token, verifyCode, scene } = data
|
||||||
|
const payload = this.jwtService.verify<EmailSendDto>(token, {
|
||||||
|
secret: this.getEmailJwtSecret(verifyCode, scene),
|
||||||
|
})
|
||||||
|
if (payload.email !== email || payload.scene !== scene) {
|
||||||
|
throw new ForbiddenException('请输入正确的邮箱验证码')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEmailJwtSecret(verifyCode: string, scene: EmailScene) {
|
||||||
return this.secureConfig.jwt_access_secret + verifyCode + scene
|
return this.secureConfig.jwt_access_secret + verifyCode + scene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator'
|
import { IsEmail, IsNotEmpty, IsStrongPassword } from 'class-validator'
|
||||||
|
|
||||||
export class ForgetPassword {
|
export class ResetPassword {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
email: string
|
email: string
|
93
src/users/me.controller.ts
Normal file
93
src/users/me.controller.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Delete,
|
||||||
|
Patch,
|
||||||
|
Put,
|
||||||
|
Body,
|
||||||
|
UseInterceptors,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { MeService } from './me.service'
|
||||||
|
import { ApiTags, ApiOperation, ApiUnauthorizedResponse } from '@nestjs/swagger'
|
||||||
|
import { User } from 'src/common/decorators/user.decorator'
|
||||||
|
import { NeedAuth } from 'src/common/decorators/need-auth.decorator'
|
||||||
|
import { PasswordInterceptor } from 'src/common/interceptors/password.interceptor'
|
||||||
|
import { PrismaService } from 'nestjs-prisma'
|
||||||
|
import { UserEntity } from './entities/user.entity'
|
||||||
|
import { DeleteUserDto } from './dto/delete-user.dto'
|
||||||
|
import { ChangePassword } from './dto/change-password.dto'
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto'
|
||||||
|
import { TokenRefreshPayload } from './dto/token.dto'
|
||||||
|
|
||||||
|
@Controller('api/users/me')
|
||||||
|
@ApiTags('Me')
|
||||||
|
export class MeController {
|
||||||
|
constructor(
|
||||||
|
private readonly meService: MeService,
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: '获取用户信息' })
|
||||||
|
@UseInterceptors(PasswordInterceptor)
|
||||||
|
@NeedAuth()
|
||||||
|
async getUserInfo(@User('userId') userId: string): Promise<UserEntity> {
|
||||||
|
return this.prismaService.user.findUniqueOrThrow({
|
||||||
|
where: { id: userId },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch()
|
||||||
|
@ApiOperation({ summary: '修改用户信息(用户名等)' })
|
||||||
|
@UseInterceptors(PasswordInterceptor)
|
||||||
|
@NeedAuth()
|
||||||
|
async updateUserInfo(
|
||||||
|
@Body() payload: UpdateUserDto,
|
||||||
|
@User('userId') userId: string,
|
||||||
|
): Promise<UserEntity> {
|
||||||
|
if (Object.keys(payload).length === 0) {
|
||||||
|
throw new BadRequestException()
|
||||||
|
}
|
||||||
|
return this.prismaService.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: payload,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete()
|
||||||
|
@NeedAuth()
|
||||||
|
@ApiOperation({ summary: '删除用户' })
|
||||||
|
async deleteUser(
|
||||||
|
@Body() userData: DeleteUserDto,
|
||||||
|
@User('userId') userId: string,
|
||||||
|
) {
|
||||||
|
return this.meService.deleteUser(userData, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('?field=password')
|
||||||
|
@NeedAuth()
|
||||||
|
@ApiOperation({ summary: '修改密码' })
|
||||||
|
@UseInterceptors(PasswordInterceptor)
|
||||||
|
async changePassword(
|
||||||
|
@Body() payload: ChangePassword,
|
||||||
|
@User('userId') userId: string,
|
||||||
|
) {
|
||||||
|
return this.meService.changePassword(payload, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('?field=email')
|
||||||
|
@NeedAuth()
|
||||||
|
@ApiOperation({ summary: '修改邮箱(TODO)' })
|
||||||
|
@UseInterceptors(PasswordInterceptor)
|
||||||
|
async updateEmail(@Body() payload: unknown) {
|
||||||
|
return '修改邮箱'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('token')
|
||||||
|
@ApiOperation({ summary: '刷新token' })
|
||||||
|
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
|
||||||
|
async updateAccessToken(@Body() payload: TokenRefreshPayload) {
|
||||||
|
return this.meService.updateAccessToken(payload.refreshToken)
|
||||||
|
}
|
||||||
|
}
|
95
src/users/me.service.ts
Normal file
95
src/users/me.service.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import {
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
ForbiddenException,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import * as bcrypt from 'bcrypt'
|
||||||
|
import { PrismaService } from 'nestjs-prisma'
|
||||||
|
import { JwtService } from '@nestjs/jwt'
|
||||||
|
import { securityConfig, SecurityConfig } from 'src/common/configs'
|
||||||
|
import { EmailScene } from 'src/email/dto/email.dto'
|
||||||
|
import { EmailService } from 'src/email/email.service'
|
||||||
|
import { DeleteUserDto } from './dto/delete-user.dto'
|
||||||
|
import { ChangePassword } from './dto/change-password.dto'
|
||||||
|
import { TokenContnet } from './dto/token.dto'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MeService {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private prismaService: PrismaService,
|
||||||
|
private emailService: EmailService,
|
||||||
|
@Inject(securityConfig.KEY) private secureConfig: SecurityConfig,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async deleteUser(userToDelete: DeleteUserDto, userId: string) {
|
||||||
|
const { email, token, verifyCode, password } = userToDelete
|
||||||
|
await this.emailService.verifyEmail({
|
||||||
|
email,
|
||||||
|
token,
|
||||||
|
verifyCode,
|
||||||
|
scene: EmailScene.deleteUser,
|
||||||
|
})
|
||||||
|
const user = await this.prismaService.user.findUniqueOrThrow({
|
||||||
|
where: { email: userToDelete.email },
|
||||||
|
})
|
||||||
|
if (user.id !== userId) {
|
||||||
|
throw new ForbiddenException()
|
||||||
|
}
|
||||||
|
const passwordValid = await bcrypt.compare(password, user.password)
|
||||||
|
if (!passwordValid) {
|
||||||
|
throw new ForbiddenException('Invalid password')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prismaService.user.delete({
|
||||||
|
where: { email: userToDelete.email },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async changePassword(payload: ChangePassword, userId: string) {
|
||||||
|
const user = await this.prismaService.user.findUniqueOrThrow({
|
||||||
|
where: { id: userId },
|
||||||
|
})
|
||||||
|
await this.checkPassword(payload.oldPassword, user.password)
|
||||||
|
const hashedPassword = await bcrypt.hash(
|
||||||
|
payload.newPassword,
|
||||||
|
this.secureConfig.bcryptSaltOrRound,
|
||||||
|
)
|
||||||
|
return this.prismaService.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: { password: hashedPassword },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccessToken(refreshToken: string) {
|
||||||
|
const { userId, iat } = this.jwtService.verify<TokenContnet>(refreshToken, {
|
||||||
|
secret: this.secureConfig.jwt_refresh_secret,
|
||||||
|
})
|
||||||
|
const user = await this.prismaService.user.findUniqueOrThrow({
|
||||||
|
where: { id: userId },
|
||||||
|
})
|
||||||
|
// TODO:不使用updatedAt,而是自定义的一个refreshTime字段
|
||||||
|
if (iat * 1000 < user.updatedAt.getTime()) {
|
||||||
|
throw new UnauthorizedException('token失效,请重新登录')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
userId,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
accessToken: this.jwtService.sign(
|
||||||
|
{ userId },
|
||||||
|
{
|
||||||
|
secret: this.secureConfig.jwt_refresh_secret,
|
||||||
|
expiresIn: this.secureConfig.refreshIn,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkPassword(pwd: string, hashPwd: string) {
|
||||||
|
const valid = await bcrypt.compare(pwd, hashPwd)
|
||||||
|
if (!valid) {
|
||||||
|
throw new ForbiddenException('Invalid password')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
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) {}
|
|
||||||
|
|
||||||
// TODO: 限制调用频率,避免暴力破解
|
|
||||||
@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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'
|
|
||||||
import * as bcrypt from 'bcrypt'
|
|
||||||
import { PrismaService } from 'nestjs-prisma'
|
|
||||||
import { Token, TokenPayload, TokenContnet } 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 UnauthorizedException('Invalid password')
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.generateTokens({ userId: user.id })
|
|
||||||
}
|
|
||||||
|
|
||||||
async refreshToken(token: string) {
|
|
||||||
const { userId, iat } = this.jwtService.verify<TokenContnet>(token, {
|
|
||||||
secret: this.secureConfig.jwt_refresh_secret,
|
|
||||||
})
|
|
||||||
const user = await this.prismaService.user.findUniqueOrThrow({
|
|
||||||
where: { id: userId },
|
|
||||||
})
|
|
||||||
if (iat * 1000 < user.updatedAt.getTime()) {
|
|
||||||
throw new UnauthorizedException('token失效,请重新登录')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
refreshToken: token,
|
|
||||||
accessToken: this.jwtService.sign(
|
|
||||||
{ userId },
|
|
||||||
{
|
|
||||||
secret: this.secureConfig.jwt_refresh_secret,
|
|
||||||
expiresIn: this.secureConfig.refreshIn,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,105 +1,34 @@
|
|||||||
import {
|
import { Controller, Post, Patch, Body, Param } from '@nestjs/common'
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Delete,
|
|
||||||
Put,
|
|
||||||
Patch,
|
|
||||||
Body,
|
|
||||||
UseInterceptors,
|
|
||||||
BadRequestException,
|
|
||||||
} 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 { NeedAuth } from 'src/common/decorators/need-auth.decorator'
|
|
||||||
import { PasswordInterceptor } from 'src/common/interceptors/password.interceptor'
|
|
||||||
import { PrismaService } from 'nestjs-prisma'
|
|
||||||
import { UserEntity } from './entities/user.entity'
|
|
||||||
import { CreateUserDto } from './dto/create-user.dto'
|
import { CreateUserDto } from './dto/create-user.dto'
|
||||||
import { ForgetPassword } from './dto/forget-password.dto'
|
import { LoginInputDto } from './dto/login-input.dto'
|
||||||
import { DeleteUserDto } from './dto/delete-user.dto'
|
import { ResetPassword } from './dto/reset-password.dto'
|
||||||
import { ChangePassword } from './dto/change-password.dto'
|
|
||||||
import { UpdateUserDto } from './dto/update-user.dto'
|
|
||||||
|
|
||||||
@Controller('api/users')
|
@Controller('api/users')
|
||||||
|
@ApiTags('Users')
|
||||||
export class UsersController {
|
export class UsersController {
|
||||||
constructor(
|
constructor(private readonly usersService: UsersService) {}
|
||||||
private readonly userService: UsersService,
|
|
||||||
private readonly prismaService: PrismaService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@ApiTags('Me')
|
|
||||||
@ApiOperation({ summary: '获取用户信息' })
|
|
||||||
@UseInterceptors(PasswordInterceptor)
|
|
||||||
@NeedAuth()
|
|
||||||
@Get('me')
|
|
||||||
async getUserInfo(@User('userId') userId: string): Promise<UserEntity> {
|
|
||||||
return this.prismaService.user.findUniqueOrThrow({ where: { id: userId } })
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiTags('Me')
|
|
||||||
@ApiOperation({ summary: '修改用户信息(用户名等)' })
|
|
||||||
@UseInterceptors(PasswordInterceptor)
|
|
||||||
@NeedAuth()
|
|
||||||
@Patch('me')
|
|
||||||
async updateUserInfo(
|
|
||||||
@Body() payload: UpdateUserDto,
|
|
||||||
@User('userId') userId: string,
|
|
||||||
): Promise<UserEntity> {
|
|
||||||
if (Object.keys(payload).length === 0) {
|
|
||||||
throw new BadRequestException()
|
|
||||||
}
|
|
||||||
return this.prismaService.user.update({
|
|
||||||
where: { id: userId },
|
|
||||||
data: payload,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiTags('User')
|
|
||||||
@ApiOperation({ summary: '注册用户' })
|
|
||||||
@Post()
|
@Post()
|
||||||
async register(@Body() userData: CreateUserDto) {
|
@ApiOperation({ summary: '邮箱注册' })
|
||||||
return this.userService.register(userData)
|
async registerByEmail(@Body() userData: CreateUserDto) {
|
||||||
|
return this.usersService.registerByEmail(userData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiTags('Me')
|
// TODO: 限制调用频率,避免暴力破解
|
||||||
@NeedAuth()
|
@Post('token')
|
||||||
@ApiOperation({ summary: '删除用户' })
|
@ApiOperation({ summary: '邮箱登录' })
|
||||||
@Delete('me')
|
async loginByEmail(@Body() user: LoginInputDto) {
|
||||||
async deleteUser(
|
return this.usersService.loginByEmail(user.email, user.password)
|
||||||
@Body() userData: DeleteUserDto,
|
}
|
||||||
@User('userId') userId: string,
|
|
||||||
|
@Patch(':id/?field=password')
|
||||||
|
@ApiOperation({ summary: '找回密码' })
|
||||||
|
async forgetPassword(
|
||||||
|
@Body() payload: ResetPassword,
|
||||||
|
@Param('id') userId: string,
|
||||||
) {
|
) {
|
||||||
return this.userService.deleteUser(userData, userId)
|
return this.usersService.resetPasswordByEmail(payload, userId)
|
||||||
}
|
|
||||||
|
|
||||||
@ApiTags('Me')
|
|
||||||
@NeedAuth()
|
|
||||||
@ApiOperation({ summary: '修改密码' })
|
|
||||||
@UseInterceptors(PasswordInterceptor)
|
|
||||||
@Patch('me/password')
|
|
||||||
async changePassword(
|
|
||||||
@Body() payload: ChangePassword,
|
|
||||||
@User('userId') userId: string,
|
|
||||||
) {
|
|
||||||
return this.userService.changePasswor(payload, userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiTags('User')
|
|
||||||
@ApiOperation({ summary: '忘记密码' })
|
|
||||||
@UseInterceptors(PasswordInterceptor)
|
|
||||||
@Patch('password')
|
|
||||||
async forgetPassword(@Body() payload: ForgetPassword): Promise<UserEntity> {
|
|
||||||
return this.userService.forgetPassword(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiTags('Me')
|
|
||||||
@NeedAuth()
|
|
||||||
@ApiOperation({ summary: '修改邮箱(TODO)' })
|
|
||||||
@UseInterceptors(PasswordInterceptor)
|
|
||||||
@Patch('me/email')
|
|
||||||
async updateEmail(@Body() payload: unknown) {
|
|
||||||
return '修改邮箱'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@ import { UsersService } from './users.service'
|
|||||||
import { UsersController } from './users.controller'
|
import { UsersController } from './users.controller'
|
||||||
import { JwtService } from '@nestjs/jwt'
|
import { JwtService } from '@nestjs/jwt'
|
||||||
import { EmailService } from 'src/email/email.service'
|
import { EmailService } from 'src/email/email.service'
|
||||||
import { TokenController } from './token.controller'
|
import { MeService } from './me.service'
|
||||||
import { TokenService } from './token.service'
|
import { MeController } from './me.controller'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [UsersController, TokenController],
|
controllers: [UsersController, MeController],
|
||||||
providers: [UsersService, JwtService, EmailService, TokenService],
|
providers: [UsersService, JwtService, EmailService, MeService],
|
||||||
exports: [UsersService],
|
exports: [UsersService],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import { Inject, Injectable, ForbiddenException } from '@nestjs/common'
|
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'
|
||||||
import * as bcrypt from 'bcrypt'
|
import * as bcrypt from 'bcrypt'
|
||||||
import { PrismaService } from 'nestjs-prisma'
|
import { PrismaService } from 'nestjs-prisma'
|
||||||
import { JwtService } from '@nestjs/jwt'
|
import { JwtService } from '@nestjs/jwt'
|
||||||
import { securityConfig, SecurityConfig } from 'src/common/configs'
|
import { securityConfig, type 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 { EmailScene } from 'src/email/dto/email.dto'
|
||||||
import { EmailService } from 'src/email/email.service'
|
import { EmailService } from 'src/email/email.service'
|
||||||
import { TokenService } from './token.service'
|
import { ResetPassword } from './dto/reset-password.dto'
|
||||||
import { ForgetPassword } from './dto/forget-password.dto'
|
import { Token, TokenPayload } from './dto/token.dto'
|
||||||
import { DeleteUserDto } from './dto/delete-user.dto'
|
|
||||||
import { ChangePassword } from './dto/change-password.dto'
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
@ -17,109 +15,70 @@ export class UsersService {
|
|||||||
private jwtService: JwtService,
|
private jwtService: JwtService,
|
||||||
private prismaService: PrismaService,
|
private prismaService: PrismaService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private tokenService: TokenService,
|
@Inject(securityConfig.KEY) private secureConfig: SecurityConfig,
|
||||||
@Inject(securityConfig.KEY)
|
|
||||||
private secureConfig: SecurityConfig,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async register(userToCreate: CreateUserDto) {
|
async registerByEmail(userToCreate: CreateUserDto) {
|
||||||
await this.verifyEmail(
|
const { email, token, verifyCode, username, password } = userToCreate
|
||||||
userToCreate.email,
|
await this.emailService.verifyEmail({
|
||||||
userToCreate.token,
|
email,
|
||||||
userToCreate.verifyCode,
|
token,
|
||||||
EmailScene.register,
|
verifyCode,
|
||||||
)
|
scene: EmailScene.register,
|
||||||
|
})
|
||||||
const hashedPassword = await bcrypt.hash(
|
const hashedPassword = await bcrypt.hash(
|
||||||
userToCreate.password,
|
password,
|
||||||
this.secureConfig.bcryptSaltOrRound,
|
this.secureConfig.bcryptSaltOrRound,
|
||||||
)
|
)
|
||||||
const user = await this.prismaService.user.create({
|
const user = await this.prismaService.user.create({
|
||||||
data: {
|
data: { username, email, password: hashedPassword },
|
||||||
username: userToCreate.username,
|
|
||||||
email: userToCreate.email,
|
|
||||||
password: hashedPassword,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
return this.tokenService.generateTokens({ userId: user.id })
|
return this.generateTokens({ userId: user.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteUser(userToDelete: DeleteUserDto, userId: string) {
|
async loginByEmail(email: string, password: string) {
|
||||||
await this.verifyEmail(
|
|
||||||
userToDelete.email,
|
|
||||||
userToDelete.token,
|
|
||||||
userToDelete.verifyCode,
|
|
||||||
EmailScene.deleteUser,
|
|
||||||
)
|
|
||||||
const user = await this.prismaService.user.findUniqueOrThrow({
|
const user = await this.prismaService.user.findUniqueOrThrow({
|
||||||
where: { email: userToDelete.email },
|
where: { email },
|
||||||
})
|
})
|
||||||
if (user.id !== userId) {
|
|
||||||
throw new ForbiddenException()
|
const passwordValid = await bcrypt.compare(password, user.password)
|
||||||
}
|
|
||||||
const passwordValid = await bcrypt.compare(
|
|
||||||
user.password,
|
|
||||||
userToDelete.password,
|
|
||||||
)
|
|
||||||
if (!passwordValid) {
|
if (!passwordValid) {
|
||||||
throw new ForbiddenException('Invalid password')
|
throw new UnauthorizedException('Invalid password')
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prismaService.user.delete({
|
return this.generateTokens({ userId: user.id })
|
||||||
where: { email: userToDelete.email },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePasswor(payload: ChangePassword, userId: string) {
|
async resetPasswordByEmail(data: ResetPassword, userId: string) {
|
||||||
const user = await this.prismaService.user.findFirstOrThrow({
|
const { email, token, verifyCode, password } = data
|
||||||
where: { id: userId },
|
await this.emailService.verifyEmail({
|
||||||
|
email,
|
||||||
|
token,
|
||||||
|
verifyCode,
|
||||||
|
scene: EmailScene.forgetPassword,
|
||||||
})
|
})
|
||||||
await this.checkPassword(payload.oldPassword, user.password)
|
|
||||||
const hashedPassword = await bcrypt.hash(
|
const hashedPassword = await bcrypt.hash(
|
||||||
payload.newPassword,
|
password,
|
||||||
this.secureConfig.bcryptSaltOrRound,
|
|
||||||
)
|
|
||||||
return this.prismaService.user.update({
|
|
||||||
where: { id: userId },
|
|
||||||
data: { password: hashedPassword },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async forgetPassword(payload: ForgetPassword) {
|
|
||||||
await this.verifyEmail(
|
|
||||||
payload.email,
|
|
||||||
payload.token,
|
|
||||||
payload.verifyCode,
|
|
||||||
EmailScene.forgetPassword,
|
|
||||||
)
|
|
||||||
const hashedPassword = await bcrypt.hash(
|
|
||||||
payload.password,
|
|
||||||
this.secureConfig.bcryptSaltOrRound,
|
this.secureConfig.bcryptSaltOrRound,
|
||||||
)
|
)
|
||||||
const user = await this.prismaService.user.update({
|
const user = await this.prismaService.user.update({
|
||||||
where: { email: payload.email },
|
where: { id: userId },
|
||||||
data: { password: hashedPassword },
|
data: { password: hashedPassword },
|
||||||
})
|
})
|
||||||
return user
|
return this.generateTokens({ userId: user.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkPassword(pwd: string, hashPwd: string) {
|
private generateTokens(payload: TokenPayload): Token {
|
||||||
const valid = await bcrypt.compare(pwd, hashPwd)
|
const accessToken = this.jwtService.sign(payload, {
|
||||||
if (!valid) {
|
secret: this.secureConfig.jwt_access_secret,
|
||||||
throw new ForbiddenException('Invalid password')
|
expiresIn: this.secureConfig.expiresIn,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
const refreshToken = this.jwtService.sign(payload, {
|
||||||
throw new ForbiddenException('请输入正确的邮箱验证码')
|
secret: this.secureConfig.jwt_refresh_secret,
|
||||||
}
|
expiresIn: this.secureConfig.refreshIn,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { accessToken, refreshToken, ...payload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user