mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
738 lines
22 KiB
JavaScript
738 lines
22 KiB
JavaScript
const express = require('express')
|
||
const router = express.Router()
|
||
const ldapService = require('../services/ldapService')
|
||
const userService = require('../services/userService')
|
||
const apiKeyService = require('../services/apiKeyService')
|
||
const logger = require('../utils/logger')
|
||
const config = require('../../config/config')
|
||
const inputValidator = require('../utils/inputValidator')
|
||
const { RateLimiterRedis } = require('rate-limiter-flexible')
|
||
const redis = require('../models/redis')
|
||
const { authenticateUser, authenticateUserOrAdmin, requireAdmin } = require('../middleware/auth')
|
||
|
||
// 🚦 配置登录速率限制
|
||
// 只基于IP地址限制,避免攻击者恶意锁定特定账户
|
||
|
||
// 延迟初始化速率限制器,确保 Redis 已连接
|
||
let ipRateLimiter = null
|
||
let strictIpRateLimiter = null
|
||
|
||
// 初始化速率限制器函数
|
||
function initRateLimiters() {
|
||
if (!ipRateLimiter) {
|
||
try {
|
||
const redisClient = redis.getClientSafe()
|
||
|
||
// IP地址速率限制 - 正常限制
|
||
ipRateLimiter = new RateLimiterRedis({
|
||
storeClient: redisClient,
|
||
keyPrefix: 'login_ip_limiter',
|
||
points: 30, // 每个IP允许30次尝试
|
||
duration: 900, // 15分钟窗口期
|
||
blockDuration: 900 // 超限后封禁15分钟
|
||
})
|
||
|
||
// IP地址速率限制 - 严格限制(用于检测暴力破解)
|
||
strictIpRateLimiter = new RateLimiterRedis({
|
||
storeClient: redisClient,
|
||
keyPrefix: 'login_ip_strict',
|
||
points: 100, // 每个IP允许100次尝试
|
||
duration: 3600, // 1小时窗口期
|
||
blockDuration: 3600 // 超限后封禁1小时
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ 初始化速率限制器失败:', error)
|
||
// 速率限制器初始化失败时继续运行,但记录错误
|
||
}
|
||
}
|
||
return { ipRateLimiter, strictIpRateLimiter }
|
||
}
|
||
|
||
// 🔐 用户登录端点
|
||
router.post('/login', async (req, res) => {
|
||
try {
|
||
const { username, password } = req.body
|
||
const clientIp = req.ip || req.connection.remoteAddress || 'unknown'
|
||
|
||
// 初始化速率限制器(如果尚未初始化)
|
||
const limiters = initRateLimiters()
|
||
|
||
// 检查IP速率限制 - 基础限制
|
||
if (limiters.ipRateLimiter) {
|
||
try {
|
||
await limiters.ipRateLimiter.consume(clientIp)
|
||
} catch (rateLimiterRes) {
|
||
const retryAfter = Math.round(rateLimiterRes.msBeforeNext / 1000) || 900
|
||
logger.security(`🚫 Login rate limit exceeded for IP: ${clientIp}`)
|
||
res.set('Retry-After', String(retryAfter))
|
||
return res.status(429).json({
|
||
error: 'Too many requests',
|
||
message: `Too many login attempts from this IP. Please try again later.`
|
||
})
|
||
}
|
||
}
|
||
|
||
// 检查IP速率限制 - 严格限制(防止暴力破解)
|
||
if (limiters.strictIpRateLimiter) {
|
||
try {
|
||
await limiters.strictIpRateLimiter.consume(clientIp)
|
||
} catch (rateLimiterRes) {
|
||
const retryAfter = Math.round(rateLimiterRes.msBeforeNext / 1000) || 3600
|
||
logger.security(`🚫 Strict rate limit exceeded for IP: ${clientIp} - possible brute force`)
|
||
res.set('Retry-After', String(retryAfter))
|
||
return res.status(429).json({
|
||
error: 'Too many requests',
|
||
message: 'Too many login attempts detected. Access temporarily blocked.'
|
||
})
|
||
}
|
||
}
|
||
|
||
if (!username || !password) {
|
||
return res.status(400).json({
|
||
error: 'Missing credentials',
|
||
message: 'Username and password are required'
|
||
})
|
||
}
|
||
|
||
// 验证输入格式
|
||
let validatedUsername
|
||
try {
|
||
validatedUsername = inputValidator.validateUsername(username)
|
||
inputValidator.validatePassword(password)
|
||
} catch (validationError) {
|
||
return res.status(400).json({
|
||
error: 'Invalid input',
|
||
message: validationError.message
|
||
})
|
||
}
|
||
|
||
// 检查用户管理是否启用
|
||
if (!config.userManagement.enabled) {
|
||
return res.status(503).json({
|
||
error: 'Service unavailable',
|
||
message: 'User management is not enabled'
|
||
})
|
||
}
|
||
|
||
// 检查LDAP是否启用
|
||
if (!config.ldap || !config.ldap.enabled) {
|
||
return res.status(503).json({
|
||
error: 'Service unavailable',
|
||
message: 'LDAP authentication is not enabled'
|
||
})
|
||
}
|
||
|
||
// 尝试LDAP认证
|
||
const authResult = await ldapService.authenticateUserCredentials(validatedUsername, password)
|
||
|
||
if (!authResult.success) {
|
||
// 登录失败
|
||
logger.info(`🚫 Failed login attempt for user: ${validatedUsername} from IP: ${clientIp}`)
|
||
return res.status(401).json({
|
||
error: 'Authentication failed',
|
||
message: authResult.message
|
||
})
|
||
}
|
||
|
||
// 登录成功
|
||
logger.info(`✅ User login successful: ${validatedUsername} from IP: ${clientIp}`)
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Login successful',
|
||
user: {
|
||
id: authResult.user.id,
|
||
username: authResult.user.username,
|
||
email: authResult.user.email,
|
||
displayName: authResult.user.displayName,
|
||
firstName: authResult.user.firstName,
|
||
lastName: authResult.user.lastName,
|
||
role: authResult.user.role
|
||
},
|
||
sessionToken: authResult.sessionToken
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ User login error:', error)
|
||
res.status(500).json({
|
||
error: 'Login error',
|
||
message: 'Internal server error during login'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🚪 用户登出端点
|
||
router.post('/logout', authenticateUser, async (req, res) => {
|
||
try {
|
||
await userService.invalidateUserSession(req.user.sessionToken)
|
||
|
||
logger.info(`👋 User logout: ${req.user.username}`)
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Logout successful'
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ User logout error:', error)
|
||
res.status(500).json({
|
||
error: 'Logout error',
|
||
message: 'Internal server error during logout'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 👤 获取当前用户信息
|
||
router.get('/profile', authenticateUser, async (req, res) => {
|
||
try {
|
||
const user = await userService.getUserById(req.user.id)
|
||
if (!user) {
|
||
return res.status(404).json({
|
||
error: 'User not found',
|
||
message: 'User profile not found'
|
||
})
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
user: {
|
||
id: user.id,
|
||
username: user.username,
|
||
email: user.email,
|
||
displayName: user.displayName,
|
||
firstName: user.firstName,
|
||
lastName: user.lastName,
|
||
role: user.role,
|
||
isActive: user.isActive,
|
||
createdAt: user.createdAt,
|
||
lastLoginAt: user.lastLoginAt,
|
||
apiKeyCount: user.apiKeyCount,
|
||
totalUsage: user.totalUsage
|
||
},
|
||
config: {
|
||
maxApiKeysPerUser: config.userManagement.maxApiKeysPerUser
|
||
}
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Get user profile error:', error)
|
||
res.status(500).json({
|
||
error: 'Profile error',
|
||
message: 'Failed to retrieve user profile'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🔑 获取用户的API Keys
|
||
router.get('/api-keys', authenticateUser, async (req, res) => {
|
||
try {
|
||
const { includeDeleted = 'false' } = req.query
|
||
const apiKeys = await apiKeyService.getUserApiKeys(req.user.id, includeDeleted === 'true')
|
||
|
||
// 移除敏感信息并格式化usage数据
|
||
const safeApiKeys = apiKeys.map((key) => {
|
||
// Flatten usage structure for frontend compatibility
|
||
let flatUsage = {
|
||
requests: 0,
|
||
inputTokens: 0,
|
||
outputTokens: 0,
|
||
totalCost: 0
|
||
}
|
||
|
||
if (key.usage && key.usage.total) {
|
||
flatUsage = {
|
||
requests: key.usage.total.requests || 0,
|
||
inputTokens: key.usage.total.inputTokens || 0,
|
||
outputTokens: key.usage.total.outputTokens || 0,
|
||
totalCost: key.totalCost || 0
|
||
}
|
||
}
|
||
|
||
return {
|
||
id: key.id,
|
||
name: key.name,
|
||
description: key.description,
|
||
tokenLimit: key.tokenLimit,
|
||
isActive: key.isActive,
|
||
createdAt: key.createdAt,
|
||
lastUsedAt: key.lastUsedAt,
|
||
expiresAt: key.expiresAt,
|
||
usage: flatUsage,
|
||
dailyCost: key.dailyCost,
|
||
dailyCostLimit: key.dailyCostLimit,
|
||
// 不返回实际的key值,只返回前缀和后几位
|
||
keyPreview: key.key
|
||
? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}`
|
||
: null,
|
||
// Include deletion fields for deleted keys
|
||
isDeleted: key.isDeleted,
|
||
deletedAt: key.deletedAt,
|
||
deletedBy: key.deletedBy,
|
||
deletedByType: key.deletedByType
|
||
}
|
||
})
|
||
|
||
res.json({
|
||
success: true,
|
||
apiKeys: safeApiKeys,
|
||
total: safeApiKeys.length
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Get user API keys error:', error)
|
||
res.status(500).json({
|
||
error: 'API Keys error',
|
||
message: 'Failed to retrieve API keys'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🔑 创建新的API Key
|
||
router.post('/api-keys', authenticateUser, async (req, res) => {
|
||
try {
|
||
const { name, description, tokenLimit, expiresAt, dailyCostLimit } = req.body
|
||
|
||
if (!name || !name.trim()) {
|
||
return res.status(400).json({
|
||
error: 'Missing name',
|
||
message: 'API key name is required'
|
||
})
|
||
}
|
||
|
||
// 检查用户API Key数量限制
|
||
const userApiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
||
if (userApiKeys.length >= config.userManagement.maxApiKeysPerUser) {
|
||
return res.status(400).json({
|
||
error: 'API key limit exceeded',
|
||
message: `You can only have up to ${config.userManagement.maxApiKeysPerUser} API keys`
|
||
})
|
||
}
|
||
|
||
// 创建API Key数据
|
||
const apiKeyData = {
|
||
name: name.trim(),
|
||
description: description?.trim() || '',
|
||
userId: req.user.id,
|
||
userUsername: req.user.username,
|
||
tokenLimit: tokenLimit || null,
|
||
expiresAt: expiresAt || null,
|
||
dailyCostLimit: dailyCostLimit || null,
|
||
createdBy: 'user',
|
||
permissions: ['messages'] // 用户创建的API Key默认只有messages权限
|
||
}
|
||
|
||
const newApiKey = await apiKeyService.createApiKey(apiKeyData)
|
||
|
||
// 更新用户API Key数量
|
||
await userService.updateUserApiKeyCount(req.user.id, userApiKeys.length + 1)
|
||
|
||
logger.info(`🔑 User ${req.user.username} created API key: ${name}`)
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: 'API key created successfully',
|
||
apiKey: {
|
||
id: newApiKey.id,
|
||
name: newApiKey.name,
|
||
description: newApiKey.description,
|
||
key: newApiKey.apiKey, // 只在创建时返回完整key
|
||
tokenLimit: newApiKey.tokenLimit,
|
||
expiresAt: newApiKey.expiresAt,
|
||
dailyCostLimit: newApiKey.dailyCostLimit,
|
||
createdAt: newApiKey.createdAt
|
||
}
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Create user API key error:', error)
|
||
res.status(500).json({
|
||
error: 'API Key creation error',
|
||
message: 'Failed to create API key'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🗑️ 删除API Key
|
||
router.delete('/api-keys/:keyId', authenticateUser, async (req, res) => {
|
||
try {
|
||
const { keyId } = req.params
|
||
|
||
// 检查API Key是否属于当前用户
|
||
const existingKey = await apiKeyService.getApiKeyById(keyId)
|
||
if (!existingKey || existingKey.userId !== req.user.id) {
|
||
return res.status(404).json({
|
||
error: 'API key not found',
|
||
message: 'API key not found or you do not have permission to access it'
|
||
})
|
||
}
|
||
|
||
await apiKeyService.deleteApiKey(keyId, req.user.username, 'user')
|
||
|
||
// 更新用户API Key数量
|
||
const userApiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
||
await userService.updateUserApiKeyCount(req.user.id, userApiKeys.length)
|
||
|
||
logger.info(`🗑️ User ${req.user.username} deleted API key: ${existingKey.name}`)
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'API key deleted successfully'
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Delete user API key error:', error)
|
||
res.status(500).json({
|
||
error: 'API Key deletion error',
|
||
message: 'Failed to delete API key'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 📊 获取用户使用统计
|
||
router.get('/usage-stats', authenticateUser, async (req, res) => {
|
||
try {
|
||
const { period = 'week', model } = req.query
|
||
|
||
// 获取用户的API Keys (including deleted ones for complete usage stats)
|
||
const userApiKeys = await apiKeyService.getUserApiKeys(req.user.id, true)
|
||
const apiKeyIds = userApiKeys.map((key) => key.id)
|
||
|
||
if (apiKeyIds.length === 0) {
|
||
return res.json({
|
||
success: true,
|
||
stats: {
|
||
totalRequests: 0,
|
||
totalInputTokens: 0,
|
||
totalOutputTokens: 0,
|
||
totalCost: 0,
|
||
dailyStats: [],
|
||
modelStats: []
|
||
}
|
||
})
|
||
}
|
||
|
||
// 获取使用统计
|
||
const stats = await apiKeyService.getAggregatedUsageStats(apiKeyIds, { period, model })
|
||
|
||
res.json({
|
||
success: true,
|
||
stats
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Get user usage stats error:', error)
|
||
res.status(500).json({
|
||
error: 'Usage stats error',
|
||
message: 'Failed to retrieve usage statistics'
|
||
})
|
||
}
|
||
})
|
||
|
||
// === 管理员用户管理端点 ===
|
||
|
||
// 📋 获取用户列表(管理员)
|
||
router.get('/', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const { page = 1, limit = 20, role, isActive, search } = req.query
|
||
|
||
const options = {
|
||
page: parseInt(page),
|
||
limit: parseInt(limit),
|
||
role,
|
||
isActive: isActive === 'true' ? true : isActive === 'false' ? false : undefined
|
||
}
|
||
|
||
const result = await userService.getAllUsers(options)
|
||
|
||
// 如果有搜索条件,进行过滤
|
||
let filteredUsers = result.users
|
||
if (search) {
|
||
const searchLower = search.toLowerCase()
|
||
filteredUsers = result.users.filter(
|
||
(user) =>
|
||
user.username.toLowerCase().includes(searchLower) ||
|
||
user.displayName.toLowerCase().includes(searchLower) ||
|
||
user.email.toLowerCase().includes(searchLower)
|
||
)
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
users: filteredUsers,
|
||
pagination: {
|
||
total: result.total,
|
||
page: result.page,
|
||
limit: result.limit,
|
||
totalPages: result.totalPages
|
||
}
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Get users list error:', error)
|
||
res.status(500).json({
|
||
error: 'Users list error',
|
||
message: 'Failed to retrieve users list'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 👤 获取特定用户信息(管理员)
|
||
router.get('/:userId', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const { userId } = req.params
|
||
|
||
const user = await userService.getUserById(userId)
|
||
if (!user) {
|
||
return res.status(404).json({
|
||
error: 'User not found',
|
||
message: 'User not found'
|
||
})
|
||
}
|
||
|
||
// 获取用户的API Keys(包括已删除的以保留统计数据)
|
||
const apiKeys = await apiKeyService.getUserApiKeys(userId, true)
|
||
|
||
res.json({
|
||
success: true,
|
||
user: {
|
||
...user,
|
||
apiKeys: apiKeys.map((key) => {
|
||
// Flatten usage structure for frontend compatibility
|
||
let flatUsage = {
|
||
requests: 0,
|
||
inputTokens: 0,
|
||
outputTokens: 0,
|
||
totalCost: 0
|
||
}
|
||
|
||
if (key.usage && key.usage.total) {
|
||
flatUsage = {
|
||
requests: key.usage.total.requests || 0,
|
||
inputTokens: key.usage.total.inputTokens || 0,
|
||
outputTokens: key.usage.total.outputTokens || 0,
|
||
totalCost: key.totalCost || 0
|
||
}
|
||
}
|
||
|
||
return {
|
||
id: key.id,
|
||
name: key.name,
|
||
description: key.description,
|
||
isActive: key.isActive,
|
||
createdAt: key.createdAt,
|
||
lastUsedAt: key.lastUsedAt,
|
||
usage: flatUsage,
|
||
keyPreview: key.key
|
||
? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}`
|
||
: null
|
||
}
|
||
})
|
||
}
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Get user details error:', error)
|
||
res.status(500).json({
|
||
error: 'User details error',
|
||
message: 'Failed to retrieve user details'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🔄 更新用户状态(管理员)
|
||
router.patch('/:userId/status', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const { userId } = req.params
|
||
const { isActive } = req.body
|
||
|
||
if (typeof isActive !== 'boolean') {
|
||
return res.status(400).json({
|
||
error: 'Invalid status',
|
||
message: 'isActive must be a boolean value'
|
||
})
|
||
}
|
||
|
||
const updatedUser = await userService.updateUserStatus(userId, isActive)
|
||
|
||
const adminUser = req.admin?.username || req.user?.username
|
||
logger.info(
|
||
`🔄 Admin ${adminUser} ${isActive ? 'enabled' : 'disabled'} user: ${updatedUser.username}`
|
||
)
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `User ${isActive ? 'enabled' : 'disabled'} successfully`,
|
||
user: {
|
||
id: updatedUser.id,
|
||
username: updatedUser.username,
|
||
isActive: updatedUser.isActive,
|
||
updatedAt: updatedUser.updatedAt
|
||
}
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Update user status error:', error)
|
||
res.status(500).json({
|
||
error: 'Update status error',
|
||
message: error.message || 'Failed to update user status'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🔄 更新用户角色(管理员)
|
||
router.patch('/:userId/role', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const { userId } = req.params
|
||
const { role } = req.body
|
||
|
||
const validRoles = ['user', 'admin']
|
||
if (!role || !validRoles.includes(role)) {
|
||
return res.status(400).json({
|
||
error: 'Invalid role',
|
||
message: `Role must be one of: ${validRoles.join(', ')}`
|
||
})
|
||
}
|
||
|
||
const updatedUser = await userService.updateUserRole(userId, role)
|
||
|
||
const adminUser = req.admin?.username || req.user?.username
|
||
logger.info(`🔄 Admin ${adminUser} changed user ${updatedUser.username} role to: ${role}`)
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `User role updated to ${role} successfully`,
|
||
user: {
|
||
id: updatedUser.id,
|
||
username: updatedUser.username,
|
||
role: updatedUser.role,
|
||
updatedAt: updatedUser.updatedAt
|
||
}
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Update user role error:', error)
|
||
res.status(500).json({
|
||
error: 'Update role error',
|
||
message: error.message || 'Failed to update user role'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🔑 禁用用户的所有API Keys(管理员)
|
||
router.post('/:userId/disable-keys', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const { userId } = req.params
|
||
|
||
const user = await userService.getUserById(userId)
|
||
if (!user) {
|
||
return res.status(404).json({
|
||
error: 'User not found',
|
||
message: 'User not found'
|
||
})
|
||
}
|
||
|
||
const result = await apiKeyService.disableUserApiKeys(userId)
|
||
|
||
const adminUser = req.admin?.username || req.user?.username
|
||
logger.info(`🔑 Admin ${adminUser} disabled all API keys for user: ${user.username}`)
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `Disabled ${result.count} API keys for user ${user.username}`,
|
||
disabledCount: result.count
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Disable user API keys error:', error)
|
||
res.status(500).json({
|
||
error: 'Disable keys error',
|
||
message: 'Failed to disable user API keys'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 📊 获取用户使用统计(管理员)
|
||
router.get('/:userId/usage-stats', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const { userId } = req.params
|
||
const { period = 'week', model } = req.query
|
||
|
||
const user = await userService.getUserById(userId)
|
||
if (!user) {
|
||
return res.status(404).json({
|
||
error: 'User not found',
|
||
message: 'User not found'
|
||
})
|
||
}
|
||
|
||
// 获取用户的API Keys(包括已删除的以保留统计数据)
|
||
const userApiKeys = await apiKeyService.getUserApiKeys(userId, true)
|
||
const apiKeyIds = userApiKeys.map((key) => key.id)
|
||
|
||
if (apiKeyIds.length === 0) {
|
||
return res.json({
|
||
success: true,
|
||
user: {
|
||
id: user.id,
|
||
username: user.username,
|
||
displayName: user.displayName
|
||
},
|
||
stats: {
|
||
totalRequests: 0,
|
||
totalInputTokens: 0,
|
||
totalOutputTokens: 0,
|
||
totalCost: 0,
|
||
dailyStats: [],
|
||
modelStats: []
|
||
}
|
||
})
|
||
}
|
||
|
||
// 获取使用统计
|
||
const stats = await apiKeyService.getAggregatedUsageStats(apiKeyIds, { period, model })
|
||
|
||
res.json({
|
||
success: true,
|
||
user: {
|
||
id: user.id,
|
||
username: user.username,
|
||
displayName: user.displayName
|
||
},
|
||
stats
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Get user usage stats (admin) error:', error)
|
||
res.status(500).json({
|
||
error: 'Usage stats error',
|
||
message: 'Failed to retrieve user usage statistics'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 📊 获取用户管理统计(管理员)
|
||
router.get('/stats/overview', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const stats = await userService.getUserStats()
|
||
|
||
res.json({
|
||
success: true,
|
||
stats
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ Get user stats overview error:', error)
|
||
res.status(500).json({
|
||
error: 'Stats error',
|
||
message: 'Failed to retrieve user statistics'
|
||
})
|
||
}
|
||
})
|
||
|
||
// 🔧 测试LDAP连接(管理员)
|
||
router.get('/admin/ldap-test', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
||
try {
|
||
const testResult = await ldapService.testConnection()
|
||
|
||
res.json({
|
||
success: true,
|
||
ldapTest: testResult,
|
||
config: ldapService.getConfigInfo()
|
||
})
|
||
} catch (error) {
|
||
logger.error('❌ LDAP test error:', error)
|
||
res.status(500).json({
|
||
error: 'LDAP test error',
|
||
message: 'Failed to test LDAP connection'
|
||
})
|
||
}
|
||
})
|
||
|
||
module.exports = router
|