first commit
This commit is contained in:
545
backend/src/controllers/accountController.ts
Normal file
545
backend/src/controllers/accountController.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
222
backend/src/controllers/adminController.ts
Normal file
222
backend/src/controllers/adminController.ts
Normal 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: '获取最近活动失败'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
57
backend/src/controllers/auditLogController.ts
Normal file
57
backend/src/controllers/auditLogController.ts
Normal 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: '获取审计日志失败'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
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信息失败' });
|
||||
}
|
||||
}
|
||||
};
|
||||
365
backend/src/controllers/userController.ts
Normal file
365
backend/src/controllers/userController.ts
Normal 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
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user