first commit
This commit is contained in:
387
backend/src/controllers/authController.ts
Normal file
387
backend/src/controllers/authController.ts
Normal file
@@ -0,0 +1,387 @@
|
||||
import { Request, Response } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { prisma } from '../config/database';
|
||||
import { logger } from '../utils/logger';
|
||||
import { AuthRequest } from '../middleware/authMiddleware';
|
||||
import type { Secret, SignOptions } from 'jsonwebtoken';
|
||||
|
||||
// Generate JWT token
|
||||
function generateToken(userId: string): string {
|
||||
const secret = process.env.JWT_SECRET;
|
||||
if (!secret) {
|
||||
throw new Error('JWT_SECRET is not configured');
|
||||
}
|
||||
const expiresIn = process.env.JWT_EXPIRES_IN || '7d';
|
||||
return jwt.sign(
|
||||
{ userId },
|
||||
secret,
|
||||
{ expiresIn: expiresIn as any }
|
||||
);
|
||||
}
|
||||
|
||||
// Create session
|
||||
async function createSession(userId: string, token: string, req: Request) {
|
||||
console.log('创建session:', { userId, token: token.substring(0, 20) + '...' })
|
||||
|
||||
const session = await prisma.session.create({
|
||||
data: {
|
||||
userId,
|
||||
token,
|
||||
ipAddress: req.ip ?? null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Session创建成功:', {
|
||||
sessionId: session.id,
|
||||
expiresAt: session.expiresAt,
|
||||
currentTime: new Date()
|
||||
})
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export const authController = {
|
||||
// 用户注册
|
||||
async register(req: Request, res: Response) {
|
||||
const { username, password, confirmPassword, firstName, lastName } = req.body;
|
||||
|
||||
// 验证密码确认
|
||||
if (password !== confirmPassword) {
|
||||
return res.status(400).json({
|
||||
error: '密码和确认密码不匹配'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { username }
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
error: '用户名已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, parseInt(process.env.BCRYPT_ROUNDS || '12'));
|
||||
|
||||
// Create user with isActive set to false by default
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
password: hashedPassword,
|
||||
firstName,
|
||||
lastName,
|
||||
isActive: false, // 新注册用户默认为禁用状态
|
||||
},
|
||||
});
|
||||
|
||||
// Create audit log
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: 'USER_REGISTERED',
|
||||
resource: 'user',
|
||||
resourceId: user.id,
|
||||
ipAddress: req.ip ?? null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
message: '注册成功,请等待管理员激活您的账户',
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
isAdmin: user.isAdmin,
|
||||
isActive: user.isActive,
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 用户登录
|
||||
async login(req: Request, res: Response) {
|
||||
const { username, password } = req.body;
|
||||
|
||||
// Find user
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { username }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// 记录登录失败审计日志
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: null,
|
||||
action: 'USER_LOGIN_FAILED',
|
||||
resource: 'user',
|
||||
resourceId: null,
|
||||
details: JSON.stringify({ username, reason: '用户不存在' }),
|
||||
ipAddress: req.ip ?? null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
}
|
||||
});
|
||||
return res.status(401).json({ error: '用户不存在' });
|
||||
}
|
||||
|
||||
// Check if user is active
|
||||
if (!user.isActive) {
|
||||
// 记录登录失败审计日志
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: 'USER_LOGIN_FAILED',
|
||||
resource: 'user',
|
||||
resourceId: user.id,
|
||||
details: JSON.stringify({ username, reason: '账户已被禁用' }),
|
||||
ipAddress: req.ip ?? null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
}
|
||||
});
|
||||
return res.status(401).json({ error: '账户已被禁用,请联系管理员激活' });
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||
if (!isValidPassword) {
|
||||
// 增加登录失败次数
|
||||
const loginAttempts = (user.loginAttempts || 0) + 1;
|
||||
const updateData: any = { loginAttempts };
|
||||
let userDisabled = false;
|
||||
|
||||
// 如果失败次数达到5次,禁用账户
|
||||
if (loginAttempts >= 5) {
|
||||
updateData.isActive = false;
|
||||
userDisabled = true;
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: updateData
|
||||
});
|
||||
|
||||
// 记录登录失败审计日志
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: 'USER_LOGIN_FAILED',
|
||||
resource: 'user',
|
||||
resourceId: user.id,
|
||||
details: JSON.stringify({
|
||||
username,
|
||||
reason: userDisabled ? '密码错误且账户已被禁用' : '密码错误',
|
||||
loginAttempts,
|
||||
isDisabled: userDisabled
|
||||
}),
|
||||
ipAddress: req.ip ?? null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
}
|
||||
});
|
||||
|
||||
if (userDisabled) {
|
||||
return res.status(401).json({ error: '登录失败次数过多,账户已被禁用,请联系管理员' });
|
||||
}
|
||||
return res.status(401).json({
|
||||
error: '用户名或密码错误',
|
||||
remainingAttempts: 5 - loginAttempts
|
||||
});
|
||||
}
|
||||
|
||||
// 登录成功,重置登录失败次数和锁定时间
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
loginAttempts: 0,
|
||||
lockedUntil: null
|
||||
}
|
||||
});
|
||||
|
||||
// Generate token
|
||||
const token = generateToken(user.id);
|
||||
|
||||
// Delete existing sessions for this user (optional - for single session per user)
|
||||
await prisma.session.deleteMany({
|
||||
where: { userId: user.id }
|
||||
});
|
||||
|
||||
// Create session
|
||||
await createSession(user.id, token, req);
|
||||
|
||||
// Update last login
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { lastLoginAt: new Date() }
|
||||
});
|
||||
|
||||
// Create audit log
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: 'USER_LOGIN',
|
||||
resource: 'user',
|
||||
resourceId: user.id,
|
||||
ipAddress: req.ip ?? null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
}
|
||||
});
|
||||
|
||||
return res.json({
|
||||
message: '登录成功',
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
isAdmin: user.isAdmin,
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
async logout(req: AuthRequest, res: Response) {
|
||||
const token = req.headers.authorization?.substring(7);
|
||||
|
||||
if (token) {
|
||||
// Delete session
|
||||
await prisma.session.deleteMany({
|
||||
where: { token }
|
||||
});
|
||||
}
|
||||
|
||||
// Create audit log
|
||||
if (req.user) {
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: req.user.id,
|
||||
action: 'USER_LOGOUT',
|
||||
resource: 'user',
|
||||
resourceId: req.user.id,
|
||||
ipAddress: req.ip ?? null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ message: '登出成功' });
|
||||
},
|
||||
|
||||
// 获取当前用户信息
|
||||
async getCurrentUser(req: AuthRequest, res: Response) {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: '未授权' });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: req.user.id },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
isAdmin: true,
|
||||
isActive: true,
|
||||
lastLoginAt: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: '用户不存在' });
|
||||
}
|
||||
|
||||
return res.json({
|
||||
user: user
|
||||
});
|
||||
},
|
||||
|
||||
// 刷新token
|
||||
async refreshToken(req: Request, res: Response) {
|
||||
const { refreshToken } = req.body;
|
||||
|
||||
if (!refreshToken) {
|
||||
return res.status(400).json({ error: '刷新令牌是必需的' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET!) as any;
|
||||
|
||||
const session = await prisma.session.findFirst({
|
||||
where: {
|
||||
token: refreshToken,
|
||||
expiresAt: {
|
||||
gt: new Date()
|
||||
}
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
isAdmin: true,
|
||||
isActive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!session || !session.user || !session.user.isActive) {
|
||||
return res.status(401).json({ error: '无效的刷新令牌' });
|
||||
}
|
||||
|
||||
// Generate new token
|
||||
const newToken = generateToken(session.user.id);
|
||||
|
||||
// Update session
|
||||
await prisma.session.update({
|
||||
where: { id: session.id },
|
||||
data: {
|
||||
token: newToken,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||
}
|
||||
});
|
||||
|
||||
return res.json({
|
||||
token: newToken,
|
||||
user: session.user
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: '无效的刷新令牌' });
|
||||
}
|
||||
},
|
||||
|
||||
// 调试端点 - 检查用户session状态
|
||||
async debugSession(req: AuthRequest, res: Response) {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: '未授权' });
|
||||
}
|
||||
|
||||
try {
|
||||
const sessions = await prisma.session.findMany({
|
||||
where: { userId: req.user.id },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
return res.json({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
sessions: sessions.map((s: any) => ({
|
||||
id: s.id,
|
||||
token: s.token.substring(0, 20) + '...',
|
||||
expiresAt: s.expiresAt,
|
||||
createdAt: s.createdAt,
|
||||
isExpired: s.expiresAt < new Date()
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: '获取session信息失败' });
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user