first commit

This commit is contained in:
2025-07-08 00:52:10 +08:00
commit aa2416c5d6
69 changed files with 16628 additions and 0 deletions

View File

@@ -0,0 +1,545 @@
import { Request, Response } from 'express';
import { prisma } from '../config/database';
import { AuthRequest } from '../middleware/authMiddleware';
export const accountController = {
// 获取所有账号 (管理员)
async getAllAccounts(req: Request, res: Response) {
const accounts = await prisma.websiteAccount.findMany({
include: {
accountAssignments: {
include: {
user: {
select: {
id: true,
username: true,
}
}
}
}
}
});
return res.json({
accounts: accounts.map((account: any) => ({
id: account.id,
website: account.website,
username: account.accountName,
token: account.token,
isActive: account.isActive,
createdAt: account.createdAt,
updatedAt: account.updatedAt,
assignedUsers: account.accountAssignments.map((aa: any) => aa.user)
}))
});
},
// 根据ID获取账号 (管理员)
async getAccountById(req: Request, res: Response) {
const { id } = req.params;
if (!id) {
return res.status(400).json({ error: '账号ID是必需的' });
}
const account = await prisma.websiteAccount.findUnique({
where: { id },
include: {
accountAssignments: {
include: {
user: {
select: {
id: true,
username: true,
firstName: true,
lastName: true,
}
}
}
}
}
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
return res.json({
account: {
id: account.id,
website: account.website,
username: account.accountName,
token: account.token,
isActive: account.isActive,
createdAt: account.createdAt,
updatedAt: account.updatedAt,
assignedUsers: account.accountAssignments.map((aa: any) => aa.user)
}
});
},
// 创建新账号 (管理员)
async createAccount(req: Request, res: Response) {
const { website, accountName, token, isActive, maxUsers } = req.body;
if (!website || !accountName || !token) {
return res.status(400).json({ error: '网站、账号名称和token是必需的' });
}
// 检查账号是否已存在
const existingAccount = await prisma.websiteAccount.findFirst({
where: {
website,
accountName
}
});
if (existingAccount) {
return res.status(400).json({ error: '账号已存在' });
}
const account = await prisma.websiteAccount.create({
data: {
website,
accountName,
token,
isActive: isActive !== undefined ? isActive : true,
maxUsers: maxUsers || 1,
currentUsers: 0,
}
});
return res.status(201).json({
message: '账号创建成功',
account: {
id: account.id,
website: account.website,
username: account.accountName,
token: account.token,
isActive: account.isActive,
createdAt: account.createdAt,
updatedAt: account.updatedAt
}
});
},
// 更新账号 (管理员)
async updateAccount(req: Request, res: Response) {
const { id } = req.params;
const { website, accountName, token, maxUsers, isActive } = req.body;
if (!id) {
return res.status(400).json({ error: '账号ID是必需的' });
}
const account = await prisma.websiteAccount.findUnique({
where: { id }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 检查是否与其他账号重复(排除当前账号)
if (website && accountName) {
const existingAccount = await prisma.websiteAccount.findFirst({
where: {
website,
accountName,
id: { not: id }
}
});
if (existingAccount) {
return res.status(400).json({ error: '该网站和账号名称组合已存在' });
}
}
const updatedAccount = await prisma.websiteAccount.update({
where: { id },
data: {
website,
accountName,
token,
maxUsers,
isActive,
}
});
return res.json({
message: '账号更新成功',
account: {
id: updatedAccount.id,
website: updatedAccount.website,
username: updatedAccount.accountName,
token: updatedAccount.token,
isActive: updatedAccount.isActive,
createdAt: updatedAccount.createdAt,
updatedAt: updatedAccount.updatedAt
}
});
},
// 删除账号 (管理员)
async deleteAccount(req: Request, res: Response) {
const { id } = req.params;
if (!id) {
return res.status(400).json({ error: '账号ID是必需的' });
}
const account = await prisma.websiteAccount.findUnique({
where: { id }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 删除账号相关的所有分配
await prisma.$transaction([
prisma.accountAssignment.deleteMany({ where: { accountId: id } }),
prisma.websiteAccount.delete({ where: { id } })
]);
return res.json({ message: '账号删除成功' });
},
// 获取用户的已分配账号
async getUserAccounts(req: AuthRequest, res: Response) {
if (!req.user) {
return res.status(401).json({ error: '未授权' });
}
const assignments = await prisma.accountAssignment.findMany({
where: {
userId: req.user.id,
isActive: true,
OR: [
{ expiresAt: null },
{ expiresAt: { gt: new Date() } }
]
},
include: {
account: {
select: {
id: true,
website: true,
accountName: true,
token: true,
isActive: true,
createdAt: true,
updatedAt: true,
}
}
}
});
return res.json({
accounts: assignments.map((aa: any) => ({
id: aa.account.id,
website: aa.account.website,
username: aa.account.accountName,
token: aa.account.token,
isActive: aa.account.isActive,
createdAt: aa.account.createdAt,
updatedAt: aa.account.updatedAt,
assignedAt: aa.assignedAt,
expiresAt: aa.expiresAt,
}))
});
},
// 分配账号给用户 (管理员)
async assignAccount(req: Request, res: Response) {
const { id } = req.params;
const { userId, expiresAt } = req.body;
if (!id || !userId) {
return res.status(400).json({ error: '账号ID和用户ID都是必需的' });
}
// 检查账号是否存在
const account = await prisma.websiteAccount.findUnique({
where: { id }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 检查用户是否存在
const user = await prisma.user.findUnique({
where: { id: userId }
});
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
// 检查账号是否已满
if (account.currentUsers >= account.maxUsers) {
return res.status(400).json({ error: '账号已达到最大用户数' });
}
// 检查是否已经分配
const existingAssignment = await prisma.accountAssignment.findFirst({
where: {
userId,
accountId: id,
isActive: true
}
});
if (existingAssignment) {
return res.status(400).json({ error: '用户已分配此账号' });
}
// 创建分配
const assignment = await prisma.accountAssignment.create({
data: {
userId,
accountId: id,
expiresAt: expiresAt ? new Date(expiresAt) : null,
isActive: true,
}
});
// 更新账号当前用户数
await prisma.websiteAccount.update({
where: { id },
data: {
currentUsers: {
increment: 1
}
}
});
return res.json({
message: '账号分配成功',
assignment
});
},
// 取消账号分配 (管理员)
async unassignAccount(req: Request, res: Response) {
const { id, userId } = req.params;
if (!id || !userId) {
return res.status(400).json({ error: '账号ID和用户ID都是必需的' });
}
const assignment = await prisma.accountAssignment.findFirst({
where: {
userId,
accountId: id,
isActive: true
}
});
if (!assignment) {
return res.status(404).json({ error: '分配不存在' });
}
// 删除分配
await prisma.accountAssignment.delete({
where: { id: assignment.id }
});
// 更新账号当前用户数
await prisma.websiteAccount.update({
where: { id },
data: {
currentUsers: {
decrement: 1
}
}
});
return res.json({ message: '账号分配已取消' });
},
// 网站登录
async loginToWebsite(req: Request, res: Response) {
const { accountId } = req.params;
const { userId } = req.body;
if (!accountId || !userId) {
return res.status(400).json({ error: '账号ID和用户ID是必需的' });
}
try {
// 获取账号信息
const account = await prisma.websiteAccount.findUnique({
where: { id: accountId }
});
if (!account) {
return res.status(404).json({ error: '账号不存在' });
}
// 检查用户是否有权限访问该账号
console.log('检查用户权限:', { accountId, userId });
const assignment = await prisma.accountAssignment.findFirst({
where: {
accountId,
userId,
isActive: true,
OR: [
{ expiresAt: null },
{ expiresAt: { gt: new Date() } }
]
}
});
console.log('权限检查结果:', {
assignment: assignment ? 'found' : 'not found',
accountId,
userId,
currentTime: new Date()
});
if (!assignment) {
// 获取更多调试信息
const allAssignments = await prisma.accountAssignment.findMany({
where: { accountId, userId }
});
console.log('该用户的所有分配记录:', allAssignments);
return res.status(403).json({ error: '您没有权限访问该账号' });
}
let loginUrl = '';
// 根据网站类型处理登录
switch (account.website) {
case 'claude':
loginUrl = await handleClaudeLogin(account.token, userId);
break;
case 'chatgpt':
loginUrl = await handleChatGPTLogin(account.token, userId);
break;
case 'grok':
loginUrl = await handleGrokLogin(account.token, userId);
break;
default:
return res.status(400).json({ error: '不支持的网站类型' });
}
return res.json({
success: true,
loginUrl,
website: account.website,
accountName: account.accountName
});
} catch (error) {
console.error('网站登录失败:', error);
return res.status(500).json({ error: '登录失败,请稍后重试' });
}
}
};
// Claude 登录处理
async function handleClaudeLogin(token: string, userName: string): Promise<string> {
try {
const baseUrl = process.env.CLAUDE_TARGET_URL || 'https://chat.micar9.com:8443';
console.log('Claude登录处理:', { token, userName });
// 第一步获取oauth token
const oauthResponse = await fetch(`${baseUrl}/manage-api/auth/oauth_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_key: token,
unique_name: userName
})
});
if (!oauthResponse.ok) {
throw new Error(`OAuth token 请求失败: ${oauthResponse.status}`);
}
const oauthData = await oauthResponse.json() as { login_url?: string };
if (!oauthData.login_url) {
throw new Error('未获取到登录URL');
}
return oauthData.login_url;
} catch (error) {
console.error('Claude登录处理失败:', error);
throw error;
}
}
// ChatGPT 登录处理
async function handleChatGPTLogin(token: string, userName: string): Promise<string> {
try {
const baseUrl = process.env.CLAUDE_TARGET_URL || 'http://127.0.0.1:8181';
const response = await fetch(`${baseUrl}/api/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ADMIN_PASSWORD || 'admin'}`
},
body: JSON.stringify({
access_token: token,
user_name: userName,
isolated_session: true,
limits: []
})
});
if (!response.ok) {
throw new Error(`ChatGPT登录请求失败: ${response.status}`);
}
const data = await response.json() as { login_url?: string };
if (!data.login_url) {
throw new Error('未获取到登录URL');
}
return data.login_url;
} catch (error) {
console.error('ChatGPT登录处理失败:', error);
throw error;
}
}
// Grok 登录处理
async function handleGrokLogin(token: string, userName: string): Promise<string> {
try {
const baseUrl = process.env.CLAUDE_TARGET_URL || 'https://grok-mirror.micar9.com:8443';
const response = await fetch(`${baseUrl}/api/login-v2`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_name: userName,
sso_token: token
})
});
if (!response.ok) {
throw new Error(`Grok登录请求失败: ${response.status}`);
}
const data = await response.json() as { login_url?: string };
if (!data.login_url) {
throw new Error('未获取到登录URL');
}
return data.login_url;
} catch (error) {
console.error('Grok登录处理失败:', error);
throw error;
}
}

View File

@@ -0,0 +1,222 @@
import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { prisma } from '../config/database';
import { logger } from '../utils/logger';
import { createAuditLog } from '../utils/audit';
export const adminController = {
// 管理员登录
async login(req: Request, res: Response): Promise<void> {
try {
const { username, password } = req.body;
// 验证管理员凭据
const admin = await prisma.user.findFirst({
where: {
username,
isAdmin: true
}
});
if (!admin) {
// 记录管理员登录失败审计日志
await createAuditLog({
userId: null,
action: 'ADMIN_LOGIN_FAILED',
resource: 'ADMIN',
details: JSON.stringify({ username, reason: '管理员不存在' }),
ipAddress: req.ip ?? null,
userAgent: req.get('User-Agent') ?? null
});
res.status(401).json({
success: false,
message: '管理员凭据无效'
});
return;
}
// 验证密码
const isValidPassword = await bcrypt.compare(password, admin.password);
if (!isValidPassword) {
// 记录管理员登录失败审计日志
await createAuditLog({
userId: admin.id,
action: 'ADMIN_LOGIN_FAILED',
resource: 'ADMIN',
details: JSON.stringify({ username, reason: '密码错误' }),
ipAddress: req.ip ?? null,
userAgent: req.get('User-Agent') ?? null
});
res.status(401).json({
success: false,
message: '管理员凭据无效'
});
return;
}
// 生成JWT token
const token = jwt.sign(
{
userId: admin.id,
username: admin.username,
role: admin.isAdmin ? 'admin' : 'user'
},
process.env.JWT_SECRET!,
{ expiresIn: '24h' }
);
// 创建审计日志
await createAuditLog({
userId: admin.id,
action: 'ADMIN_LOGIN',
resource: 'ADMIN',
details: { username: admin.username },
ipAddress: req.ip ?? null,
userAgent: req.get('User-Agent') ?? null
});
res.json({
success: true,
message: '管理员登录成功',
token,
admin: {
id: admin.id,
username: admin.username,
role: admin.isAdmin ? 'admin' : 'user'
}
});
} catch (error) {
logger.error('Admin login error:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
},
// 获取统计数据
async getStats(req: Request, res: Response): Promise<void> {
try {
// 获取用户总数
const totalUsers = await prisma.user.count({
where: { isAdmin: false }
});
// 获取账号总数
const totalAccounts = await prisma.websiteAccount.count();
// 获取今日访问数(基于会话)
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayVisits = await prisma.session.count({
where: {
createdAt: {
gte: today
}
}
});
// 获取系统告警数(基于审计日志中的错误)
const alerts = await prisma.auditLog.count({
where: {
action: { contains: 'ERROR' },
createdAt: {
gte: new Date(Date.now() - 24 * 60 * 60 * 1000) // 最近24小时
}
}
});
console.log('统计数据:', { totalUsers, totalAccounts, todayVisits, alerts });
res.json({
success: true,
data: {
totalUsers,
totalAccounts,
todayVisits,
alerts
}
});
} catch (error) {
logger.error('Get stats error:', error);
res.status(500).json({
success: false,
message: '获取统计数据失败'
});
}
},
// 获取最近活动
async getRecentActivities(req: Request, res: Response): Promise<void> {
try {
const activities = await prisma.auditLog.findMany({
take: 10,
orderBy: {
createdAt: 'desc'
},
include: {
user: {
select: {
id: true,
username: true
}
}
}
});
const formattedActivities = activities.map((activity: any) => {
let description = '';
if (activity.action === 'USER_LOGIN_FAILED' || activity.action === 'ADMIN_LOGIN_FAILED') {
// 解析失败原因
let reason = '登录失败';
let username = '未知用户';
if (activity.details) {
try {
const details = JSON.parse(activity.details);
reason = details.reason || '登录失败';
username = details.username || '未知用户';
} catch (e) {
console.error('解析活动详情失败:', e);
}
}
description = `${username} ${reason}`;
} else if (activity.user) {
description = `${activity.user.username} ${activity.action}`;
} else {
description = `系统 ${activity.action}`;
}
return {
id: activity.id,
description,
time: activity.createdAt,
details: activity.details,
ipAddress: activity.ipAddress,
userAgent: activity.userAgent,
action: activity.action
};
});
console.log('最近活动:', formattedActivities);
res.json({
success: true,
data: {
activities: formattedActivities
}
});
} catch (error) {
logger.error('Get recent activities error:', error);
res.status(500).json({
success: false,
message: '获取最近活动失败'
});
}
}
};

View File

@@ -0,0 +1,57 @@
import { Request, Response } from 'express';
import { prisma } from '../config/database';
import { logger } from '../utils/logger';
export const auditLogController = {
// 获取审计日志
async getAuditLogs(req: Request, res: Response): Promise<void> {
try {
const { page = 1, limit = 10, userId, action, resource } = req.query;
const skip = (Number(page) - 1) * Number(limit);
const where: any = {};
if (userId) where.userId = String(userId);
if (action) where.action = { contains: String(action) };
if (resource) where.resource = { contains: String(resource) };
const [logs, total] = await Promise.all([
prisma.auditLog.findMany({
where,
skip,
take: Number(limit),
orderBy: {
createdAt: 'desc'
},
include: {
user: {
select: {
id: true,
username: true
}
}
}
}),
prisma.auditLog.count({ where })
]);
res.json({
success: true,
data: {
logs,
pagination: {
page: Number(page),
limit: Number(limit),
total,
totalPages: Math.ceil(total / Number(limit))
}
}
});
} catch (error) {
logger.error('Get audit logs error:', error);
res.status(500).json({
success: false,
message: '获取审计日志失败'
});
}
}
};

View 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信息失败' });
}
}
};

View File

@@ -0,0 +1,365 @@
import { Request, Response } from 'express';
import { prisma } from '../config/database';
import { AuthRequest } from '../middleware/auth';
import bcrypt from 'bcryptjs';
export const userController = {
// 获取所有用户 (管理员)
async getAllUsers(req: Request, res: Response) {
const { page = 1, limit = 10, search = '', role = '' } = req.query;
const pageNum = parseInt(page as string) || 1;
const limitNum = parseInt(limit as string) || 10;
const skip = (pageNum - 1) * limitNum;
// 构建查询条件
const where: any = {};
if (search) {
where.OR = [
{ username: { contains: search as string, mode: 'insensitive' } },
{ firstName: { contains: search as string, mode: 'insensitive' } },
{ lastName: { contains: search as string, mode: 'insensitive' } }
];
}
if (role) {
if (role === 'admin') {
where.isAdmin = true;
} else if (role === 'user') {
where.isAdmin = false;
}
}
// 获取总数
const total = await prisma.user.count({ where });
// 获取用户列表
const users = await prisma.user.findMany({
where,
select: {
id: true,
username: true,
firstName: true,
lastName: true,
isAdmin: true,
isActive: true,
totpEnabled: true,
lastLoginAt: true,
createdAt: true,
accountAssignments: {
select: {
accountId: true
}
}
},
skip,
take: limitNum,
orderBy: { createdAt: 'desc' }
});
// 计算分页信息
const totalPages = Math.ceil(total / limitNum);
return res.json({
users: users.map((user: any) => ({
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
role: user.isAdmin ? 'admin' : 'user',
isAdmin: user.isAdmin,
isActive: user.isActive,
totpEnabled: user.totpEnabled,
lastLoginAt: user.lastLoginAt,
createdAt: user.createdAt,
accounts: user.accountAssignments.map((assignment: any) => assignment.accountId)
})),
pagination: {
page: pageNum,
limit: limitNum,
total,
totalPages
}
});
},
// 根据ID获取用户信息
async getUserById(req: AuthRequest, res: Response) {
const { id } = req.params;
if (!id) {
return res.status(400).json({ error: '用户ID是必需的' });
}
// 检查权限:只能查看自己的信息或管理员可以查看所有
if (req.user?.id !== id && req.user?.role !== 'admin') {
return res.status(403).json({ error: '权限不足' });
}
const user = await prisma.user.findUnique({
where: { id },
select: {
id: true,
username: true,
firstName: true,
lastName: true,
isAdmin: true,
isActive: true,
totpEnabled: true,
lastLoginAt: true,
createdAt: true,
}
});
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
return res.json({
user: {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
isAdmin: user.isAdmin,
isActive: user.isActive,
totpEnabled: user.totpEnabled,
lastLoginAt: user.lastLoginAt,
createdAt: user.createdAt
}
});
},
// 更新用户信息
async updateUser(req: AuthRequest, res: Response) {
const { id } = req.params;
const { username, role, firstName, lastName, isActive, loginAttempts } = req.body;
console.log('收到更新请求:', req.body);
if (!id) {
return res.status(400).json({ error: '用户ID是必需的' });
}
// 检查权限:只能更新自己的信息或管理员可以更新所有
if (req.user?.id !== id && req.user?.role !== 'admin') {
return res.status(403).json({ error: '权限不足' });
}
// 构建更新数据
const updateData: any = {};
// 只有管理员可以修改这些字段
if (req.user?.role === 'admin') {
if (username !== undefined) updateData.username = username;
if (role !== undefined) updateData.isAdmin = role === 'admin';
if (typeof isActive === 'boolean') updateData.isActive = isActive;
}
// 普通用户可以修改这些字段
if (firstName !== undefined) updateData.firstName = firstName;
if (lastName !== undefined) updateData.lastName = lastName;
// 新增:处理密码修改
if (req.body.password && typeof req.body.password === 'string' && req.body.password.trim() !== '') {
console.log('正在处理密码更新');
const hashedPassword = await bcrypt.hash(req.body.password, 12);
updateData.password = hashedPassword;
console.log('密码已加密');
}
updateData.loginAttempts = 0
console.log('最终更新数据:', updateData);
// 检查用户名是否已存在(排除当前用户)
if (username) {
const existingUser = await prisma.user.findFirst({
where: {
username,
id: { not: id }
}
});
if (existingUser) {
return res.status(400).json({ error: '用户名已存在' });
}
}
const user = await prisma.user.update({
where: { id },
data: updateData,
select: {
id: true,
username: true,
firstName: true,
lastName: true,
isAdmin: true,
isActive: true,
totpEnabled: true,
lastLoginAt: true,
createdAt: true,
password: true,
loginAttempts: true
}
});
return res.json({
user: {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
isAdmin: user.isAdmin,
isActive: user.isActive,
totpEnabled: user.totpEnabled,
lastLoginAt: user.lastLoginAt,
createdAt: user.createdAt,
password: user.password,
loginAttempts: loginAttempts
}
});
},
// 更新用户账号权限
async updateUserAccounts(req: AuthRequest, res: Response) {
const { id } = req.params;
const { accountIds } = req.body;
if (!id) {
return res.status(400).json({ error: '用户ID是必需的' });
}
if (!Array.isArray(accountIds)) {
return res.status(400).json({ error: 'accountIds必须是数组' });
}
// 检查用户是否存在
const user = await prisma.user.findUnique({
where: { id }
});
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
// 检查所有账号是否存在
const accounts = await prisma.websiteAccount.findMany({
where: {
id: { in: accountIds }
}
});
if (accounts.length !== accountIds.length) {
return res.status(400).json({ error: '部分账号不存在' });
}
// 使用事务来确保数据一致性
await prisma.$transaction(async (tx) => {
// 删除用户现有的所有账号分配
await tx.accountAssignment.deleteMany({
where: { userId: id }
});
// 创建新的账号分配
if (accountIds.length > 0) {
await tx.accountAssignment.createMany({
data: accountIds.map(accountId => ({
userId: id,
accountId: accountId,
isActive: true
}))
});
}
});
return res.json({
message: '用户账号权限更新成功',
accountIds
});
},
// 删除用户 (管理员)
async deleteUser(req: Request, res: Response) {
const { id } = req.params;
if (!id) {
return res.status(400).json({ error: '用户ID是必需的' });
}
const user = await prisma.user.findUnique({
where: { id }
});
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
// 删除用户相关的所有数据
await prisma.$transaction([
prisma.session.deleteMany({ where: { userId: id } }),
prisma.accountAssignment.deleteMany({ where: { userId: id } }),
prisma.auditLog.deleteMany({ where: { userId: id } }),
prisma.user.delete({ where: { id } })
]);
return res.json({ message: '用户删除成功' });
},
// 创建用户 (管理员)
async createUser(req: Request, res: Response) {
const { username, password, role } = req.body;
if (!username || !password) {
return res.status(400).json({ error: '用户名和密码是必需的' });
}
// 检查用户是否已存在
const existingUser = await prisma.user.findUnique({
where: { username }
});
if (existingUser) {
return res.status(400).json({ error: '用户名已存在' });
}
// 加密密码
const hashedPassword = await bcrypt.hash(password, 12);
// 创建用户,默认为禁用状态
const user = await prisma.user.create({
data: {
username,
password: hashedPassword,
isAdmin: role === 'admin',
isActive: false, // 新创建的用户默认为禁用状态
},
select: {
id: true,
username: true,
firstName: true,
lastName: true,
isAdmin: true,
isActive: true,
totpEnabled: true,
lastLoginAt: true,
createdAt: true,
}
});
return res.status(201).json({
message: '用户创建成功,需要激活后才能登录',
user: {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
role: user.isAdmin ? 'admin' : 'user',
isAdmin: user.isAdmin,
isActive: user.isActive,
totpEnabled: user.totpEnabled,
lastLoginAt: user.lastLoginAt,
createdAt: user.createdAt
}
});
},
};