Files
ai/backend/src/controllers/authController.ts
liangweihao 19a8426163 debug: 添加详细的登录调试日志
- 在后端login控制器中添加详细的执行步骤日志
- 添加错误捕获和处理,防止未捕获的异常
- 在前端LoginForm中添加详细的错误日志输出
- 在rate limiter中添加详细的限制日志
- 在请求日志中添加POST请求体信息
- 帮助定位401错误的具体原因

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 09:57:59 +08:00

405 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = "pandora";
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.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || 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 } = 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,
isActive: false, // 新注册用户默认为禁用状态
},
});
// Create audit log
await prisma.auditLog.create({
data: {
userId: user.id,
action: 'USER_REGISTERED',
resource: 'user',
resourceId: user.id,
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
userAgent: req.get('User-Agent') ?? null,
}
});
return res.status(201).json({
message: '注册成功,请等待管理员激活您的账户',
user: {
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
isActive: user.isActive,
}
});
},
// 用户登录
async login(req: Request, res: Response) {
try {
const { username, password } = req.body;
console.log('登录请求:', { username, timestamp: new Date().toISOString() });
// Find user
const user = await prisma.user.findUnique({
where: { username }
});
console.log('查找用户结果:', { found: !!user, isActive: user?.isActive, userId: user?.id });
if (!user) {
// 记录登录失败审计日志
await prisma.auditLog.create({
data: {
userId: null,
action: 'USER_LOGIN_FAILED',
resource: 'user',
resourceId: null,
details: JSON.stringify({ username, reason: '用户不存在' }),
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || 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.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
userAgent: req.get('User-Agent') ?? null,
}
});
return res.status(401).json({ error: '账户已被禁用,请联系管理员激活' });
}
// Verify password
console.log('开始验证密码...');
const isValidPassword = await bcrypt.compare(password, user.password);
console.log('密码验证结果:', { isValid: isValidPassword });
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.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || 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
console.log('生成token...');
const token = generateToken(user.id);
console.log('Token生成成功:', token.substring(0, 20) + '...');
// Delete existing sessions for this user (optional - for single session per user)
console.log('删除用户现有sessions...');
await prisma.session.deleteMany({
where: { userId: user.id }
});
// Create session
console.log('创建新session...');
await createSession(user.id, token, req);
// Update last login
console.log('更新最后登录时间...');
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.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
userAgent: req.get('User-Agent') ?? null,
}
});
console.log('登录成功,返回响应...');
return res.json({
message: '登录成功',
token,
user: {
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
}
});
} catch (error) {
console.error('登录过程中发生错误:', error);
return res.status(500).json({ error: '服务器内部错误' });
}
},
// 用户登出
async logout(req: Request, res: Response) {
const token = req.headers.authorization?.substring(7);
if (token) {
// Delete session
await prisma.session.deleteMany({
where: { token }
});
}
// Create audit log (if we have user info from token)
try {
if (token) {
const decoded = jwt.verify(token, "pandora") as any;
if (decoded && decoded.userId) {
await prisma.auditLog.create({
data: {
userId: decoded.userId,
action: 'USER_LOGOUT',
resource: 'user',
resourceId: decoded.userId,
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
userAgent: req.get('User-Agent') ?? null,
}
});
}
}
} catch (error) {
// Token无效不记录审计日志
console.log('登出时token无效跳过审计日志记录');
}
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,
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, "pandora") 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信息失败' });
}
}
};