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',
|
||||
resource: 'ADMIN',
|
||||
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
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export const adminController = {
|
||||
action: 'ADMIN_LOGIN_FAILED',
|
||||
resource: 'ADMIN',
|
||||
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
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ export const adminController = {
|
||||
action: 'ADMIN_LOGIN',
|
||||
resource: 'ADMIN',
|
||||
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
|
||||
});
|
||||
|
||||
@@ -129,8 +129,6 @@ export const adminController = {
|
||||
}
|
||||
});
|
||||
|
||||
console.log('统计数据:', { totalUsers, totalAccounts, todayVisits, alerts });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
|
||||
@@ -28,7 +28,7 @@ async function createSession(userId: string, token: string, req: Request) {
|
||||
data: {
|
||||
userId,
|
||||
token,
|
||||
ipAddress: req.ip ?? null,
|
||||
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
|
||||
},
|
||||
@@ -85,7 +85,7 @@ export const authController = {
|
||||
action: 'USER_REGISTERED',
|
||||
resource: 'user',
|
||||
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,
|
||||
}
|
||||
});
|
||||
@@ -103,13 +103,18 @@ export const authController = {
|
||||
|
||||
// 用户登录
|
||||
async login(req: Request, res: Response) {
|
||||
const { username, password } = req.body;
|
||||
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({
|
||||
@@ -119,7 +124,7 @@ export const authController = {
|
||||
resource: 'user',
|
||||
resourceId: null,
|
||||
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,
|
||||
}
|
||||
});
|
||||
@@ -136,7 +141,7 @@ export const authController = {
|
||||
resource: 'user',
|
||||
resourceId: user.id,
|
||||
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,
|
||||
}
|
||||
});
|
||||
@@ -144,7 +149,10 @@ export const authController = {
|
||||
}
|
||||
|
||||
// Verify password
|
||||
console.log('开始验证密码...');
|
||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||
console.log('密码验证结果:', { isValid: isValidPassword });
|
||||
|
||||
if (!isValidPassword) {
|
||||
// 增加登录失败次数
|
||||
const loginAttempts = (user.loginAttempts || 0) + 1;
|
||||
@@ -175,7 +183,7 @@ export const authController = {
|
||||
loginAttempts,
|
||||
isDisabled: userDisabled
|
||||
}),
|
||||
ipAddress: req.ip ?? null,
|
||||
ipAddress: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress || null,
|
||||
userAgent: req.get('User-Agent') ?? null,
|
||||
}
|
||||
});
|
||||
@@ -199,17 +207,22 @@ export const authController = {
|
||||
});
|
||||
|
||||
// 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() }
|
||||
@@ -222,11 +235,12 @@ export const authController = {
|
||||
action: 'USER_LOGIN',
|
||||
resource: 'user',
|
||||
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,
|
||||
}
|
||||
});
|
||||
|
||||
console.log('登录成功,返回响应...');
|
||||
return res.json({
|
||||
message: '登录成功',
|
||||
token,
|
||||
@@ -236,6 +250,10 @@ export const authController = {
|
||||
isAdmin: user.isAdmin,
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('登录过程中发生错误:', error);
|
||||
return res.status(500).json({ error: '服务器内部错误' });
|
||||
}
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
@@ -257,7 +275,7 @@ export const authController = {
|
||||
action: 'USER_LOGOUT',
|
||||
resource: 'user',
|
||||
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,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Request, Response } from 'express';
|
||||
import { prisma } from '../config/database';
|
||||
import { AuthRequest } from '../middleware/auth';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
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({
|
||||
where: { userId: id }
|
||||
|
||||
@@ -20,7 +20,7 @@ const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// 信任代理,确保正确获取客户端IP地址
|
||||
app.set('trust proxy', '154.17.226.99');
|
||||
app.set('trust proxy', true);
|
||||
|
||||
// Security middleware
|
||||
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
|
||||
message: {
|
||||
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);
|
||||
@@ -47,8 +54,14 @@ app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Request logging
|
||||
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}`, {
|
||||
ip: req.ip,
|
||||
ip: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress,
|
||||
userAgent: req.get('User-Agent')
|
||||
});
|
||||
next();
|
||||
|
||||
@@ -29,7 +29,7 @@ export function errorHandler(
|
||||
stack: error.stack,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
ip: (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress,
|
||||
userAgent: req.get('User-Agent')
|
||||
});
|
||||
|
||||
|
||||
@@ -116,7 +116,14 @@ const handleLogin = async () => {
|
||||
|
||||
router.push('/dashboard')
|
||||
} 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) {
|
||||
const errorMessage = error.response?.data?.error || '用户名或密码错误'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { adminAuth } from '@/utils/auth';
|
||||
import type { RouteRecordRaw, NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { adminAuth } from '@/utils/auth'
|
||||
|
||||
const routes = [
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
@@ -57,42 +58,47 @@ const routes = [
|
||||
component: () => import('@/views/NotFound.vue'),
|
||||
meta: { title: '页面未找到' }
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
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;
|
||||
document.title = `${title} - AI`;
|
||||
const title = to.meta.title as string
|
||||
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 (!authStore.isLoggedIn) {
|
||||
next('/');
|
||||
return;
|
||||
next('/')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要管理员认证
|
||||
if (to.meta.requiresAdminAuth) {
|
||||
if (!adminAuth.isLoggedIn()) {
|
||||
next('/admin/login');
|
||||
return;
|
||||
next('/admin/login')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (to.path === '/' && authStore.isLoggedIn) {
|
||||
return next('/dashboard');
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
<h1 class="text-2xl font-bold text-primary-600 dark:text-primary-400">
|
||||
Pandora 管理后台
|
||||
AI 管理后台
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
@@ -378,8 +378,8 @@ const loadAdminInfo = async () => {
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
const response = await adminStore.getStats()
|
||||
if (response && response.stats) {
|
||||
stats.value = response.stats
|
||||
if (response && response.data) {
|
||||
stats.value = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载系统统计失败:', error)
|
||||
@@ -409,12 +409,9 @@ const formatTime = (time: string) => {
|
||||
// 加载最近活动
|
||||
const loadRecentActivities = async () => {
|
||||
try {
|
||||
console.log('开始加载最近活动...')
|
||||
const response = await adminStore.getRecentActivities()
|
||||
console.log('最近活动响应:', response)
|
||||
|
||||
if (response && response.activities) {
|
||||
recentActivities.value = response.activities
|
||||
if (response && response.data.activities) {
|
||||
recentActivities.value = response.data.activities
|
||||
console.log('最近活动加载成功:', recentActivities.value)
|
||||
} else {
|
||||
console.error('最近活动数据格式错误:', response)
|
||||
|
||||
Reference in New Issue
Block a user