重构 API 为 8 领域 + 应用层架构
将后端模块拆分为 domains、applications、shared 三层,结算计算器移入 domain 纯函数目录,API 路径与测试保持不变。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
111
apps/api/src/domains/identity/auth.service.ts
Normal file
111
apps/api/src/domains/identity/auth.service.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Injectable, UnauthorizedException, ForbiddenException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
|
||||
const MAX_LOGIN_FAILS = 5;
|
||||
const LOCK_DURATION_MS = 15 * 60 * 1000;
|
||||
|
||||
export interface JwtPayload {
|
||||
sub: string;
|
||||
username: string;
|
||||
userType: string;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private jwt: JwtService,
|
||||
private config: ConfigService,
|
||||
) {}
|
||||
|
||||
async login(username: string, password: string, portal: 'player' | 'admin' | 'agent') {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { username },
|
||||
include: { auth: true, adminRole: { include: { role: true } } },
|
||||
});
|
||||
|
||||
if (!user || !user.auth) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
const expectedType = portal === 'admin' ? 'ADMIN' : portal === 'agent' ? 'AGENT' : 'PLAYER';
|
||||
if (user.userType !== expectedType) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
if (user.status === 'DISABLED') {
|
||||
throw new ForbiddenException('Account disabled');
|
||||
}
|
||||
|
||||
if (user.auth.lockedUntil && user.auth.lockedUntil > new Date()) {
|
||||
throw new ForbiddenException('Account locked, try again later');
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(password, user.auth.passwordHash);
|
||||
if (!valid) {
|
||||
const failCount = user.auth.loginFailCount + 1;
|
||||
const lockedUntil =
|
||||
failCount >= MAX_LOGIN_FAILS ? new Date(Date.now() + LOCK_DURATION_MS) : null;
|
||||
await this.prisma.userAuth.update({
|
||||
where: { userId: user.id },
|
||||
data: { loginFailCount: failCount, lockedUntil },
|
||||
});
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
await this.prisma.userAuth.update({
|
||||
where: { userId: user.id },
|
||||
data: { loginFailCount: 0, lockedUntil: null, lastLoginAt: new Date() },
|
||||
});
|
||||
|
||||
const expiresIn =
|
||||
portal === 'admin'
|
||||
? this.config.get('JWT_ADMIN_EXPIRES', '2h')
|
||||
: portal === 'agent'
|
||||
? this.config.get('JWT_AGENT_EXPIRES', '8h')
|
||||
: this.config.get('JWT_PLAYER_EXPIRES', '24h');
|
||||
|
||||
const payload: JwtPayload = {
|
||||
sub: user.id.toString(),
|
||||
username: user.username,
|
||||
userType: user.userType,
|
||||
role: user.adminRole?.role?.code,
|
||||
};
|
||||
|
||||
const token = this.jwt.sign(payload, { expiresIn });
|
||||
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
id: user.id.toString(),
|
||||
username: user.username,
|
||||
userType: user.userType,
|
||||
locale: user.locale,
|
||||
role: user.adminRole?.role?.code,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async changePassword(userId: bigint, oldPassword: string, newPassword: string) {
|
||||
const auth = await this.prisma.userAuth.findUnique({ where: { userId } });
|
||||
if (!auth) throw new UnauthorizedException('User not found');
|
||||
|
||||
const valid = await bcrypt.compare(oldPassword, auth.passwordHash);
|
||||
if (!valid) throw new UnauthorizedException('Invalid old password');
|
||||
|
||||
const hash = await bcrypt.hash(newPassword, 10);
|
||||
await this.prisma.userAuth.update({
|
||||
where: { userId },
|
||||
data: { passwordHash: hash },
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, 10);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user