Compare commits
13 Commits
bfb165a9b8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 437522232c | |||
| 14e47bb35e | |||
| 268ee4d055 | |||
| 21f4ff65d9 | |||
| 19a8426163 | |||
| f583f787f0 | |||
| 0edceecfe5 | |||
| a3264be5f6 | |||
| 622a1c1b00 | |||
| ae47f90af1 | |||
| 3375029b1f | |||
| d39efbb62f | |||
| 9bc42496f5 |
@@ -26,7 +26,7 @@ export const adminController = {
|
|||||||
action: 'ADMIN_LOGIN_FAILED',
|
action: 'ADMIN_LOGIN_FAILED',
|
||||||
resource: 'ADMIN',
|
resource: 'ADMIN',
|
||||||
details: JSON.stringify({ username, reason: '管理员不存在' }),
|
details: JSON.stringify({ username, reason: '管理员不存在' }),
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null
|
userAgent: req.get('User-Agent') ?? null
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export const adminController = {
|
|||||||
action: 'ADMIN_LOGIN_FAILED',
|
action: 'ADMIN_LOGIN_FAILED',
|
||||||
resource: 'ADMIN',
|
resource: 'ADMIN',
|
||||||
details: JSON.stringify({ username, reason: '密码错误' }),
|
details: JSON.stringify({ username, reason: '密码错误' }),
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null
|
userAgent: req.get('User-Agent') ?? null
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ export const adminController = {
|
|||||||
action: 'ADMIN_LOGIN',
|
action: 'ADMIN_LOGIN',
|
||||||
resource: 'ADMIN',
|
resource: 'ADMIN',
|
||||||
details: { username: admin.username },
|
details: { username: admin.username },
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null
|
userAgent: req.get('User-Agent') ?? null
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,8 +129,6 @@ export const adminController = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('统计数据:', { totalUsers, totalAccounts, todayVisits, alerts });
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ async function createSession(userId: string, token: string, req: Request) {
|
|||||||
data: {
|
data: {
|
||||||
userId,
|
userId,
|
||||||
token,
|
token,
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null,
|
userAgent: req.get('User-Agent') ?? null,
|
||||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
|
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
|
||||||
},
|
},
|
||||||
@@ -85,7 +85,7 @@ export const authController = {
|
|||||||
action: 'USER_REGISTERED',
|
action: 'USER_REGISTERED',
|
||||||
resource: 'user',
|
resource: 'user',
|
||||||
resourceId: user.id,
|
resourceId: user.id,
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null,
|
userAgent: req.get('User-Agent') ?? null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -103,13 +103,18 @@ export const authController = {
|
|||||||
|
|
||||||
// 用户登录
|
// 用户登录
|
||||||
async login(req: Request, res: Response) {
|
async login(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
console.log('登录请求:', { username, timestamp: new Date().toISOString() });
|
||||||
|
|
||||||
// Find user
|
// Find user
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { username }
|
where: { username }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('查找用户结果:', { found: !!user, isActive: user?.isActive, userId: user?.id });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// 记录登录失败审计日志
|
// 记录登录失败审计日志
|
||||||
await prisma.auditLog.create({
|
await prisma.auditLog.create({
|
||||||
@@ -119,7 +124,7 @@ export const authController = {
|
|||||||
resource: 'user',
|
resource: 'user',
|
||||||
resourceId: null,
|
resourceId: null,
|
||||||
details: JSON.stringify({ username, reason: '用户不存在' }),
|
details: JSON.stringify({ username, reason: '用户不存在' }),
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null,
|
userAgent: req.get('User-Agent') ?? null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -136,7 +141,7 @@ export const authController = {
|
|||||||
resource: 'user',
|
resource: 'user',
|
||||||
resourceId: user.id,
|
resourceId: user.id,
|
||||||
details: JSON.stringify({ username, reason: '账户已被禁用' }),
|
details: JSON.stringify({ username, reason: '账户已被禁用' }),
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null,
|
userAgent: req.get('User-Agent') ?? null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -144,7 +149,10 @@ export const authController = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify password
|
// Verify password
|
||||||
|
console.log('开始验证密码...');
|
||||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||||
|
console.log('密码验证结果:', { isValid: isValidPassword });
|
||||||
|
|
||||||
if (!isValidPassword) {
|
if (!isValidPassword) {
|
||||||
// 增加登录失败次数
|
// 增加登录失败次数
|
||||||
const loginAttempts = (user.loginAttempts || 0) + 1;
|
const loginAttempts = (user.loginAttempts || 0) + 1;
|
||||||
@@ -175,7 +183,7 @@ export const authController = {
|
|||||||
loginAttempts,
|
loginAttempts,
|
||||||
isDisabled: userDisabled
|
isDisabled: userDisabled
|
||||||
}),
|
}),
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null,
|
userAgent: req.get('User-Agent') ?? null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -199,17 +207,22 @@ export const authController = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Generate token
|
// Generate token
|
||||||
|
console.log('生成token...');
|
||||||
const token = generateToken(user.id);
|
const token = generateToken(user.id);
|
||||||
|
console.log('Token生成成功:', token.substring(0, 20) + '...');
|
||||||
|
|
||||||
// Delete existing sessions for this user (optional - for single session per user)
|
// Delete existing sessions for this user (optional - for single session per user)
|
||||||
|
console.log('删除用户现有sessions...');
|
||||||
await prisma.session.deleteMany({
|
await prisma.session.deleteMany({
|
||||||
where: { userId: user.id }
|
where: { userId: user.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create session
|
// Create session
|
||||||
|
console.log('创建新session...');
|
||||||
await createSession(user.id, token, req);
|
await createSession(user.id, token, req);
|
||||||
|
|
||||||
// Update last login
|
// Update last login
|
||||||
|
console.log('更新最后登录时间...');
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: user.id },
|
where: { id: user.id },
|
||||||
data: { lastLoginAt: new Date() }
|
data: { lastLoginAt: new Date() }
|
||||||
@@ -222,11 +235,12 @@ export const authController = {
|
|||||||
action: 'USER_LOGIN',
|
action: 'USER_LOGIN',
|
||||||
resource: 'user',
|
resource: 'user',
|
||||||
resourceId: user.id,
|
resourceId: user.id,
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null,
|
userAgent: req.get('User-Agent') ?? null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('登录成功,返回响应...');
|
||||||
return res.json({
|
return res.json({
|
||||||
message: '登录成功',
|
message: '登录成功',
|
||||||
token,
|
token,
|
||||||
@@ -236,6 +250,10 @@ export const authController = {
|
|||||||
isAdmin: user.isAdmin,
|
isAdmin: user.isAdmin,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('登录过程中发生错误:', error);
|
||||||
|
return res.status(500).json({ error: '服务器内部错误' });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 用户登出
|
// 用户登出
|
||||||
@@ -257,7 +275,7 @@ export const authController = {
|
|||||||
action: 'USER_LOGOUT',
|
action: 'USER_LOGOUT',
|
||||||
resource: 'user',
|
resource: 'user',
|
||||||
resourceId: req.user.id,
|
resourceId: req.user.id,
|
||||||
ipAddress: req.ip ?? null,
|
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||||
userAgent: req.get('User-Agent') ?? null,
|
userAgent: req.get('User-Agent') ?? null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Request, Response } from 'express';
|
|||||||
import { prisma } from '../config/database';
|
import { prisma } from '../config/database';
|
||||||
import { AuthRequest } from '../middleware/auth';
|
import { AuthRequest } from '../middleware/auth';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
export const userController = {
|
export const userController = {
|
||||||
// 获取所有用户 (管理员)
|
// 获取所有用户 (管理员)
|
||||||
@@ -229,7 +230,7 @@ export const userController = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 使用事务来确保数据一致性
|
// 使用事务来确保数据一致性
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||||
// 删除用户现有的所有账号分配
|
// 删除用户现有的所有账号分配
|
||||||
await tx.accountAssignment.deleteMany({
|
await tx.accountAssignment.deleteMany({
|
||||||
where: { userId: id }
|
where: { userId: id }
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const app = express();
|
|||||||
const PORT = process.env.PORT || 3001;
|
const PORT = process.env.PORT || 3001;
|
||||||
|
|
||||||
// 信任代理,确保正确获取客户端IP地址
|
// 信任代理,确保正确获取客户端IP地址
|
||||||
app.set('trust proxy', '154.17.226.99');
|
app.set('trust proxy', true);
|
||||||
|
|
||||||
// Security middleware
|
// Security middleware
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
@@ -37,6 +37,13 @@ const limiter = rateLimit({
|
|||||||
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'), // limit each IP to 100 requests per windowMs
|
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'), // limit each IP to 100 requests per windowMs
|
||||||
message: {
|
message: {
|
||||||
error: 'Too many requests from this IP, please try again later.'
|
error: 'Too many requests from this IP, please try again later.'
|
||||||
|
},
|
||||||
|
// 添加详细日志
|
||||||
|
handler: (req, res) => {
|
||||||
|
console.log('Rate limit exceeded for IP:', req.ip, 'Path:', req.path);
|
||||||
|
res.status(429).json({
|
||||||
|
error: 'Too many requests from this IP, please try again later.'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.use('/api/', limiter);
|
app.use('/api/', limiter);
|
||||||
@@ -47,8 +54,14 @@ app.use(express.urlencoded({ extended: true }));
|
|||||||
|
|
||||||
// Request logging
|
// Request logging
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`, {
|
||||||
|
ip: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress,
|
||||||
|
userAgent: req.get('User-Agent'),
|
||||||
|
body: req.method === 'POST' ? req.body : undefined
|
||||||
|
});
|
||||||
|
|
||||||
logger.info(`${req.method} ${req.path}`, {
|
logger.info(`${req.method} ${req.path}`, {
|
||||||
ip: req.ip,
|
ip: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress,
|
||||||
userAgent: req.get('User-Agent')
|
userAgent: req.get('User-Agent')
|
||||||
});
|
});
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function errorHandler(
|
|||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
url: req.url,
|
url: req.url,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
ip: req.ip,
|
ip: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress,
|
||||||
userAgent: req.get('User-Agent')
|
userAgent: req.get('User-Agent')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,14 @@ const handleLogin = async () => {
|
|||||||
|
|
||||||
router.push('/dashboard')
|
router.push('/dashboard')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(error)
|
console.error('登录失败,详细错误信息:', {
|
||||||
|
status: error.response?.status,
|
||||||
|
statusText: error.response?.statusText,
|
||||||
|
data: error.response?.data,
|
||||||
|
url: error.config?.url,
|
||||||
|
method: error.config?.method,
|
||||||
|
headers: error.config?.headers
|
||||||
|
})
|
||||||
// 根据不同的错误类型显示不同的错误信息
|
// 根据不同的错误类型显示不同的错误信息
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
const errorMessage = error.response?.data?.error || '用户名或密码错误'
|
const errorMessage = error.response?.data?.error || '用户名或密码错误'
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import type { RouteRecordRaw, NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { adminAuth } from '@/utils/auth';
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { adminAuth } from '@/utils/auth'
|
||||||
|
|
||||||
const routes = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
@@ -57,42 +58,47 @@ const routes = [
|
|||||||
component: () => import('@/views/NotFound.vue'),
|
component: () => import('@/views/NotFound.vue'),
|
||||||
meta: { title: '页面未找到' }
|
meta: { title: '页面未找到' }
|
||||||
}
|
}
|
||||||
];
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes,
|
routes
|
||||||
});
|
})
|
||||||
|
|
||||||
// 路由守卫
|
// 路由守卫
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach(async (
|
||||||
|
to: RouteLocationNormalized,
|
||||||
|
_from: RouteLocationNormalized,
|
||||||
|
next: NavigationGuardNext
|
||||||
|
) => {
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
const title = to.meta.title as string;
|
const title = to.meta.title as string
|
||||||
document.title = `${title} - AI`;
|
document.title = `${title} - AI`
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore()
|
||||||
|
// 用户已登录,重定向到dashboard
|
||||||
|
if (to.path === '/' && authStore.isLoggedIn) {
|
||||||
|
next({ name: 'Dashboard' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否需要用户认证
|
// 检查是否需要用户认证
|
||||||
if (to.meta.requiresAuth) {
|
if (to.meta.requiresAuth) {
|
||||||
if (!authStore.isLoggedIn) {
|
if (!authStore.isLoggedIn) {
|
||||||
next('/');
|
next('/')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否需要管理员认证
|
// 检查是否需要管理员认证
|
||||||
if (to.meta.requiresAdminAuth) {
|
if (to.meta.requiresAdminAuth) {
|
||||||
if (!adminAuth.isLoggedIn()) {
|
if (!adminAuth.isLoggedIn()) {
|
||||||
next('/admin/login');
|
next('/admin/login')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to.path === '/' && authStore.isLoggedIn) {
|
next()
|
||||||
return next('/dashboard');
|
})
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
export default router
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="flex justify-between h-16">
|
<div class="flex justify-between h-16">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h1 class="text-2xl font-bold text-primary-600 dark:text-primary-400">
|
<h1 class="text-2xl font-bold text-primary-600 dark:text-primary-400">
|
||||||
Pandora 管理后台
|
AI 管理后台
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
@@ -378,8 +378,8 @@ const loadAdminInfo = async () => {
|
|||||||
const loadStats = async () => {
|
const loadStats = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await adminStore.getStats()
|
const response = await adminStore.getStats()
|
||||||
if (response && response.stats) {
|
if (response && response.data) {
|
||||||
stats.value = response.stats
|
stats.value = response.data
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载系统统计失败:', error)
|
console.error('加载系统统计失败:', error)
|
||||||
@@ -409,12 +409,9 @@ const formatTime = (time: string) => {
|
|||||||
// 加载最近活动
|
// 加载最近活动
|
||||||
const loadRecentActivities = async () => {
|
const loadRecentActivities = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('开始加载最近活动...')
|
|
||||||
const response = await adminStore.getRecentActivities()
|
const response = await adminStore.getRecentActivities()
|
||||||
console.log('最近活动响应:', response)
|
if (response && response.data.activities) {
|
||||||
|
recentActivities.value = response.data.activities
|
||||||
if (response && response.activities) {
|
|
||||||
recentActivities.value = response.activities
|
|
||||||
console.log('最近活动加载成功:', recentActivities.value)
|
console.log('最近活动加载成功:', recentActivities.value)
|
||||||
} else {
|
} else {
|
||||||
console.error('最近活动数据格式错误:', response)
|
console.error('最近活动数据格式错误:', response)
|
||||||
|
|||||||
Reference in New Issue
Block a user