Compare commits

...

13 Commits

Author SHA1 Message Date
437522232c revert 0edceecfe5
revert 修复登录bug
2025-11-17 15:09:32 +00:00
14e47bb35e revert f583f787f0
revert fix: 修复登录后跳转然后返回登录页的问题

- 移除Dashboard组件中重复的token验证调用
- 优化auth store的initAuth方法,增加详细错误处理和日志
- 改进API响应拦截器,减少对/auth/me请求的自动重定向
- 添加路由守卫调试日志,方便定位路由跳转问题
- 解决登录时的竞态条件和重复验证问题

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:06:52 +00:00
268ee4d055 revert 21f4ff65d9
revert fix: 修复登录后立即验证token失败的问题

- 修改Dashboard组件,对于已登录用户跳过重复的token验证
- 在LoginForm中添加详细的认证状态日志
- 优化initAuth方法,避免对已登录用户的重复验证
- 添加认证状态检查,确保登录成功后再跳转
- 解决登录成功后立即调用/auth/me导致401的时序问题

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:03:18 +00:00
21f4ff65d9 fix: 修复登录后立即验证token失败的问题
- 修改Dashboard组件,对于已登录用户跳过重复的token验证
- 在LoginForm中添加详细的认证状态日志
- 优化initAuth方法,避免对已登录用户的重复验证
- 添加认证状态检查,确保登录成功后再跳转
- 解决登录成功后立即调用/auth/me导致401的时序问题

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 10:02:47 +08:00
19a8426163 debug: 添加详细的登录调试日志
- 在后端login控制器中添加详细的执行步骤日志
- 添加错误捕获和处理,防止未捕获的异常
- 在前端LoginForm中添加详细的错误日志输出
- 在rate limiter中添加详细的限制日志
- 在请求日志中添加POST请求体信息
- 帮助定位401错误的具体原因

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 09:57:59 +08:00
f583f787f0 fix: 修复登录后跳转然后返回登录页的问题
- 移除Dashboard组件中重复的token验证调用
- 优化auth store的initAuth方法,增加详细错误处理和日志
- 改进API响应拦截器,减少对/auth/me请求的自动重定向
- 添加路由守卫调试日志,方便定位路由跳转问题
- 解决登录时的竞态条件和重复验证问题

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 09:43:57 +08:00
0edceecfe5 修复登录bug 2025-08-05 22:42:02 +08:00
a3264be5f6 get real ip 2025-07-22 00:14:21 +08:00
622a1c1b00 fix show data error 2025-07-21 23:53:44 +08:00
ae47f90af1 fix data error 2025-07-21 23:49:10 +08:00
3375029b1f jump to dashboard 2025-07-21 22:59:53 +08:00
d39efbb62f fix tx 2025-07-21 22:56:02 +08:00
9bc42496f5 revert 3e4f9ad660
revert fix
2025-07-21 22:50:36 +08:00
8 changed files with 88 additions and 48 deletions

View File

@@ -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: {

View File

@@ -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) {
const { username, password } = req.body; try {
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,
} }
}); });

View File

@@ -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 }

View File

@@ -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();

View File

@@ -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')
}); });

View File

@@ -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 || '用户名或密码错误'

View File

@@ -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;

View File

@@ -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)