format + lint
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import { BadRequestException, Body, Controller, Get, Post, Request, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Post,
|
||||
Request,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
@@ -6,32 +13,31 @@ import { UserSessionService } from 'src/user/services/user-session.service';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly userSessionService: UserSessionService,
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly userSessionService: UserSessionService,
|
||||
) { }
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() loginDto: LoginDto) {
|
||||
switch (loginDto.type) {
|
||||
case 'password':
|
||||
return this.authService.loginWithPassword(loginDto);
|
||||
case 'phone':
|
||||
return this.authService.loginWithPhone(loginDto);
|
||||
case 'email':
|
||||
return this.authService.loginWithEmail(loginDto);
|
||||
default:
|
||||
throw new BadRequestException('服务器错误');
|
||||
}
|
||||
@Post('login')
|
||||
async login(@Body() loginDto: LoginDto) {
|
||||
switch (loginDto.type) {
|
||||
case 'password':
|
||||
return this.authService.loginWithPassword(loginDto);
|
||||
case 'phone':
|
||||
return this.authService.loginWithPhone(loginDto);
|
||||
case 'email':
|
||||
return this.authService.loginWithEmail(loginDto);
|
||||
default:
|
||||
throw new BadRequestException('服务器错误');
|
||||
}
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@Post('logout')
|
||||
async logout(@Request() req) {
|
||||
const { userId, sessionId } = req.user;
|
||||
await this.userSessionService.invalidateSession(userId, sessionId);
|
||||
|
||||
return true;
|
||||
}
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@Post('logout')
|
||||
async logout(@Request() req) {
|
||||
const { userId, sessionId } = req.user;
|
||||
await this.userSessionService.invalidateSession(userId, sessionId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,21 +25,12 @@ import { OptionalAuthGuard } from './strategies/OptionalAuthGuard';
|
||||
signOptions: {
|
||||
expiresIn: configService.get<string>('JWT_EXPIRES_IN', '1d'),
|
||||
},
|
||||
})
|
||||
}),
|
||||
}),
|
||||
VerificationModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
AuthService,
|
||||
JwtStrategy,
|
||||
OptionalAuthGuard,
|
||||
],
|
||||
exports: [
|
||||
PassportModule,
|
||||
JwtStrategy,
|
||||
AuthService,
|
||||
OptionalAuthGuard,
|
||||
]
|
||||
providers: [AuthService, JwtStrategy, OptionalAuthGuard],
|
||||
exports: [PassportModule, JwtStrategy, AuthService, OptionalAuthGuard],
|
||||
})
|
||||
export class AuthModule { }
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -10,136 +10,146 @@ import { VerificationService } from 'src/verification/verification.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly userService: UserService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly userSessionService: UserSessionService,
|
||||
private readonly verificationService: VerificationService,
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private readonly userService: UserService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly userSessionService: UserSessionService,
|
||||
private readonly verificationService: VerificationService,
|
||||
) { }
|
||||
async loginWithPassword(loginDto: LoginDto) {
|
||||
const { account, password } = loginDto;
|
||||
// 依次使用邮箱登录、手机号、账号
|
||||
const user = await this.userService.findOne(
|
||||
[{ email: account }, { phone: account }, { username: account }],
|
||||
{
|
||||
withDeleted: true,
|
||||
},
|
||||
);
|
||||
|
||||
async loginWithPassword(loginDto: LoginDto) {
|
||||
const { account, password } = loginDto;
|
||||
// 依次使用邮箱登录、手机号、账号
|
||||
const user = await this.userService.findOne([
|
||||
{ email: account },
|
||||
{ phone: account },
|
||||
{ username: account },
|
||||
], {
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (user && user.deletedAt !== null) {
|
||||
throw new BadRequestException('该账号注销中');
|
||||
}
|
||||
|
||||
if (user === null || !user.password_hash || !user.salt) {
|
||||
throw new BadRequestException('账户或密码错误');
|
||||
}
|
||||
|
||||
// 判断密码是否正确
|
||||
const hashedPassword = this.hashPassword(password, user.salt);
|
||||
if (hashedPassword !== user.password_hash) {
|
||||
throw new BadRequestException('账户或密码错误');
|
||||
}
|
||||
|
||||
// 登录成功,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
}
|
||||
if (user && user.deletedAt !== null) {
|
||||
throw new BadRequestException('该账号注销中');
|
||||
}
|
||||
|
||||
async loginWithPhone(loginDto: LoginDto) {
|
||||
const { phone, code } = loginDto;
|
||||
// 先判断验证码是否正确
|
||||
const isValid = this.verificationService.verifyPhoneCode(phone, code, 'login');
|
||||
switch (isValid) {
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
throw new BadRequestException('验证码已过期');
|
||||
case -2:
|
||||
throw new BadRequestException('验证码错误');
|
||||
case -3:
|
||||
throw new BadRequestException('验证码已失效');
|
||||
default:
|
||||
throw new BadRequestException('验证码错误');
|
||||
}
|
||||
|
||||
// 判断用户是否存在,若不存在则进行注册
|
||||
let user = await this.userService.findOne({ phone }, { withDeleted: true });
|
||||
if (user && user.deletedAt !== null) {
|
||||
throw new BadRequestException('该账号注销中,请使用其他手机号');
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
// 执行注册操作
|
||||
user = await this.userService.create({ phone: phone });
|
||||
}
|
||||
|
||||
if (!user || !user.userId) {// 注册失败或用户信息错误
|
||||
throw new BadRequestException('请求失败,请稍后再试');
|
||||
}
|
||||
|
||||
// 登录,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
}
|
||||
if (user === null || !user.password_hash || !user.salt) {
|
||||
throw new BadRequestException('账户或密码错误');
|
||||
}
|
||||
|
||||
async loginWithEmail(loginDto: LoginDto) {
|
||||
const { email, code } = loginDto;
|
||||
// 先判断验证码是否正确
|
||||
const isValid = this.verificationService.verifyEmailCode(email, code, 'login');
|
||||
switch (isValid) {
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
throw new BadRequestException('验证码已过期,请重新获取');
|
||||
case -2:
|
||||
throw new BadRequestException('验证码错误');
|
||||
case -3:
|
||||
throw new BadRequestException('验证码已失效,请重新获取');
|
||||
default:
|
||||
throw new BadRequestException('验证码错误,请稍后再试');
|
||||
}
|
||||
|
||||
// 判断用户是否存在,若不存在则进行注册
|
||||
let user = await this.userService.findOne({ email }, { withDeleted: true });
|
||||
if (user && user.deletedAt !== null) {
|
||||
throw new BadRequestException('该账号注销中,请使用其他邮箱');
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
// 执行注册操作
|
||||
user = await this.userService.create({ email: email });
|
||||
}
|
||||
|
||||
if (!user || !user.userId) {// 注册失败或用户信息错误
|
||||
throw new BadRequestException('请求失败,请稍后再试');
|
||||
}
|
||||
|
||||
// 登录,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
}
|
||||
// 判断密码是否正确
|
||||
const hashedPassword = this.hashPassword(password, user.salt);
|
||||
if (hashedPassword !== user.password_hash) {
|
||||
throw new BadRequestException('账户或密码错误');
|
||||
}
|
||||
|
||||
private hashPassword(password: string, salt: string): string {
|
||||
return createHash('sha256').update(`${password}${salt}`).digest('hex');
|
||||
// 登录成功,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
};
|
||||
}
|
||||
|
||||
async loginWithPhone(loginDto: LoginDto) {
|
||||
const { phone, code } = loginDto;
|
||||
// 先判断验证码是否正确
|
||||
const isValid = this.verificationService.verifyPhoneCode(
|
||||
phone,
|
||||
code,
|
||||
'login',
|
||||
);
|
||||
switch (isValid) {
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
throw new BadRequestException('验证码已过期');
|
||||
case -2:
|
||||
throw new BadRequestException('验证码错误');
|
||||
case -3:
|
||||
throw new BadRequestException('验证码已失效');
|
||||
default:
|
||||
throw new BadRequestException('验证码错误');
|
||||
}
|
||||
|
||||
private async generateToken(user: User) {
|
||||
const payload = {
|
||||
userId: user.userId,
|
||||
sessionId: uuidv4(),
|
||||
}
|
||||
|
||||
// 存储
|
||||
await this.userSessionService.createSession(payload.userId, payload.sessionId);
|
||||
|
||||
// 颁发token
|
||||
return this.jwtService.sign(payload);
|
||||
// 判断用户是否存在,若不存在则进行注册
|
||||
let user = await this.userService.findOne({ phone }, { withDeleted: true });
|
||||
if (user && user.deletedAt !== null) {
|
||||
throw new BadRequestException('该账号注销中,请使用其他手机号');
|
||||
}
|
||||
|
||||
}
|
||||
if (!user) {
|
||||
// 执行注册操作
|
||||
user = await this.userService.create({ phone: phone });
|
||||
}
|
||||
|
||||
if (!user || !user.userId) {
|
||||
// 注册失败或用户信息错误
|
||||
throw new BadRequestException('请求失败,请稍后再试');
|
||||
}
|
||||
|
||||
// 登录,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
};
|
||||
}
|
||||
|
||||
async loginWithEmail(loginDto: LoginDto) {
|
||||
const { email, code } = loginDto;
|
||||
// 先判断验证码是否正确
|
||||
const isValid = this.verificationService.verifyEmailCode(
|
||||
email,
|
||||
code,
|
||||
'login',
|
||||
);
|
||||
switch (isValid) {
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
throw new BadRequestException('验证码已过期,请重新获取');
|
||||
case -2:
|
||||
throw new BadRequestException('验证码错误');
|
||||
case -3:
|
||||
throw new BadRequestException('验证码已失效,请重新获取');
|
||||
default:
|
||||
throw new BadRequestException('验证码错误,请稍后再试');
|
||||
}
|
||||
|
||||
// 判断用户是否存在,若不存在则进行注册
|
||||
let user = await this.userService.findOne({ email }, { withDeleted: true });
|
||||
if (user && user.deletedAt !== null) {
|
||||
throw new BadRequestException('该账号注销中,请使用其他邮箱');
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
// 执行注册操作
|
||||
user = await this.userService.create({ email: email });
|
||||
}
|
||||
|
||||
if (!user || !user.userId) {
|
||||
// 注册失败或用户信息错误
|
||||
throw new BadRequestException('请求失败,请稍后再试');
|
||||
}
|
||||
|
||||
// 登录,颁发token
|
||||
return {
|
||||
token: await this.generateToken(user),
|
||||
};
|
||||
}
|
||||
|
||||
private hashPassword(password: string, salt: string): string {
|
||||
return createHash('sha256').update(`${password}${salt}`).digest('hex');
|
||||
}
|
||||
|
||||
private async generateToken(user: User) {
|
||||
const payload = {
|
||||
userId: user.userId,
|
||||
sessionId: uuidv4(),
|
||||
};
|
||||
|
||||
// 存储
|
||||
await this.userSessionService.createSession(
|
||||
payload.userId,
|
||||
payload.sessionId,
|
||||
);
|
||||
|
||||
// 颁发token
|
||||
return this.jwtService.sign(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { IsEnum, IsString, Length, ValidateIf } from 'class-validator';
|
||||
|
||||
export class LoginDto {
|
||||
@IsEnum(['password', 'phone', 'email'], { message: '请求类型错误' })
|
||||
type: 'password' | 'phone' | 'email';
|
||||
@IsEnum(['password', 'phone', 'email'], { message: '请求类型错误' })
|
||||
type: 'password' | 'phone' | 'email';
|
||||
|
||||
@ValidateIf(o => o.type === 'password')
|
||||
@IsString({ message: '账户必须输入' })
|
||||
@Length(1, 254, { message: '账户异常' })// 用户名、邮箱、手机号
|
||||
account?: string;
|
||||
@ValidateIf((o) => o.type === 'password')
|
||||
@IsString({ message: '账户必须输入' })
|
||||
@Length(1, 254, { message: '账户异常' }) // 用户名、邮箱、手机号
|
||||
account?: string;
|
||||
|
||||
@ValidateIf(o => o.type === 'password')
|
||||
@IsString({ message: '密码必须输入' })
|
||||
@Length(6, 32, { message: '密码异常' })// 6-32位
|
||||
password?: string;
|
||||
@ValidateIf((o) => o.type === 'password')
|
||||
@IsString({ message: '密码必须输入' })
|
||||
@Length(6, 32, { message: '密码异常' }) // 6-32位
|
||||
password?: string;
|
||||
|
||||
@ValidateIf(o => o.type === 'phone')
|
||||
@IsString({ message: '手机号必须输入' })
|
||||
@Length(11, 11, { message: '手机号异常' })// 中国大陆,11位数字
|
||||
phone?: string;
|
||||
@ValidateIf((o) => o.type === 'phone')
|
||||
@IsString({ message: '手机号必须输入' })
|
||||
@Length(11, 11, { message: '手机号异常' }) // 中国大陆,11位数字
|
||||
phone?: string;
|
||||
|
||||
@ValidateIf(o => o.type === 'email')
|
||||
@IsString({ message: '邮箱必须输入' })
|
||||
@Length(6, 254, { message: '邮箱异常' })// RFC 5321
|
||||
email?: string;
|
||||
@ValidateIf((o) => o.type === 'email')
|
||||
@IsString({ message: '邮箱必须输入' })
|
||||
@Length(6, 254, { message: '邮箱异常' }) // RFC 5321
|
||||
email?: string;
|
||||
|
||||
@ValidateIf(o => o.type === 'phone' || o.type === 'email')
|
||||
@IsString({ message: '验证码必须输入' })
|
||||
@Length(6, 6, { message: '验证码异常' })// 6位数字
|
||||
code?: string;
|
||||
}
|
||||
@ValidateIf((o) => o.type === 'phone' || o.type === 'email')
|
||||
@IsString({ message: '验证码必须输入' })
|
||||
@Length(6, 6, { message: '验证码异常' }) // 6位数字
|
||||
code?: string;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
|
||||
import { AuthGuard } from "@nestjs/passport";
|
||||
import { Observable, retry } from "rxjs";
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class OptionalAuthGuard extends AuthGuard('jwt') implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
try {
|
||||
await super.canActivate(context);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return true;// 如果验证失败,仍然允许访问
|
||||
}
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
try {
|
||||
await super.canActivate(context);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('OptionalAuthGuard error:', error);
|
||||
return true; // 如果验证失败,仍然允许访问
|
||||
}
|
||||
}
|
||||
|
||||
handleRequest<TUser = any>(err: any, user: any, info: any, context: ExecutionContext, status?: any): TUser {
|
||||
if (err || !user) {
|
||||
return null; // 如果没有用户信息,返回null
|
||||
}
|
||||
return user; // 如果有用户信息,返回用户对象
|
||||
handleRequest<TUser = any>(
|
||||
err: any,
|
||||
user: any,
|
||||
// info: any,
|
||||
// context: ExecutionContext,
|
||||
// status?: any,
|
||||
): TUser {
|
||||
if (err || !user) {
|
||||
return null; // 如果没有用户信息,返回null
|
||||
}
|
||||
}
|
||||
return user; // 如果有用户信息,返回用户对象
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
import { Injectable, UnauthorizedException } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { ExtractJwt, Strategy } from "passport-jwt";
|
||||
import { UserSessionService } from "src/user/services/user-session.service";
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { UserSessionService } from 'src/user/services/user-session.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
private readonly userSessionService: UserSessionService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get<string>('JWT_SECRET', 'tone-page'),
|
||||
})
|
||||
constructor(
|
||||
private readonly userSessionService: UserSessionService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get<string>('JWT_SECRET', 'tone-page'),
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
const { userId, sessionId } = payload ?? {};
|
||||
|
||||
const isValidSession = await this.userSessionService.isSessionValid(
|
||||
userId,
|
||||
sessionId,
|
||||
);
|
||||
if (!isValidSession) {
|
||||
throw new UnauthorizedException('登录凭证已过期,请重新登录');
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
const { userId, sessionId } = payload ?? {};
|
||||
|
||||
const isValidSession = await this.userSessionService.isSessionValid(userId, sessionId);
|
||||
if (!isValidSession) {
|
||||
throw new UnauthorizedException('登录凭证已过期,请重新登录');
|
||||
}
|
||||
|
||||
return {
|
||||
userId,
|
||||
sessionId,
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
userId,
|
||||
sessionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user