mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
fix: user stats
This commit is contained in:
@@ -490,7 +490,9 @@ const authenticateUser = async (req, res, next) => {
|
|||||||
|
|
||||||
// 检查用户是否被禁用
|
// 检查用户是否被禁用
|
||||||
if (!user.isActive) {
|
if (!user.isActive) {
|
||||||
logger.security(`🔒 Disabled user login attempt: ${user.username} from ${req.ip || 'unknown'}`)
|
logger.security(
|
||||||
|
`🔒 Disabled user login attempt: ${user.username} from ${req.ip || 'unknown'}`
|
||||||
|
)
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
error: 'Account disabled',
|
error: 'Account disabled',
|
||||||
message: 'Your account has been disabled. Please contact administrator.'
|
message: 'Your account has been disabled. Please contact administrator.'
|
||||||
@@ -506,7 +508,7 @@ const authenticateUser = async (req, res, next) => {
|
|||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
sessionToken: sessionToken,
|
sessionToken,
|
||||||
sessionCreatedAt: session.createdAt
|
sessionCreatedAt: session.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +608,6 @@ const authenticateUserOrAdmin = async (req, res, next) => {
|
|||||||
error: 'Authentication required',
|
error: 'Authentication required',
|
||||||
message: 'Please login as user or admin to access this resource'
|
message: 'Please login as user or admin to access this resource'
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const authDuration = Date.now() - startTime
|
const authDuration = Date.now() - startTime
|
||||||
logger.error(`❌ User/Admin authentication error (${authDuration}ms):`, {
|
logger.error(`❌ User/Admin authentication error (${authDuration}ms):`, {
|
||||||
@@ -624,34 +625,34 @@ const authenticateUserOrAdmin = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🛡️ 权限检查中间件
|
// 🛡️ 权限检查中间件
|
||||||
const requireRole = (allowedRoles) => {
|
const requireRole = (allowedRoles) => (req, res, next) => {
|
||||||
return (req, res, next) => {
|
// 管理员始终有权限
|
||||||
// 管理员始终有权限
|
if (req.admin) {
|
||||||
if (req.admin) {
|
return next()
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查用户角色
|
|
||||||
if (req.user) {
|
|
||||||
const userRole = req.user.role
|
|
||||||
const allowed = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles]
|
|
||||||
|
|
||||||
if (allowed.includes(userRole)) {
|
|
||||||
return next()
|
|
||||||
} else {
|
|
||||||
logger.security(`🚫 Access denied for user ${req.user.username} (role: ${userRole}) to ${req.originalUrl}`)
|
|
||||||
return res.status(403).json({
|
|
||||||
error: 'Insufficient permissions',
|
|
||||||
message: `This resource requires one of the following roles: ${allowed.join(', ')}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(401).json({
|
|
||||||
error: 'Authentication required',
|
|
||||||
message: 'Please login to access this resource'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查用户角色
|
||||||
|
if (req.user) {
|
||||||
|
const userRole = req.user.role
|
||||||
|
const allowed = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles]
|
||||||
|
|
||||||
|
if (allowed.includes(userRole)) {
|
||||||
|
return next()
|
||||||
|
} else {
|
||||||
|
logger.security(
|
||||||
|
`🚫 Access denied for user ${req.user.username} (role: ${userRole}) to ${req.originalUrl}`
|
||||||
|
)
|
||||||
|
return res.status(403).json({
|
||||||
|
error: 'Insufficient permissions',
|
||||||
|
message: `This resource requires one of the following roles: ${allowed.join(', ')}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'Authentication required',
|
||||||
|
message: 'Please login to access this resource'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔒 管理员权限检查中间件
|
// 🔒 管理员权限检查中间件
|
||||||
@@ -665,7 +666,9 @@ const requireAdmin = (req, res, next) => {
|
|||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.security(`🚫 Admin access denied for ${req.user?.username || 'unknown'} from ${req.ip || 'unknown'}`)
|
logger.security(
|
||||||
|
`🚫 Admin access denied for ${req.user?.username || 'unknown'} from ${req.ip || 'unknown'}`
|
||||||
|
)
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
error: 'Admin access required',
|
error: 'Admin access required',
|
||||||
message: 'This resource requires administrator privileges'
|
message: 'This resource requires administrator privileges'
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ router.post('/login', async (req, res) => {
|
|||||||
},
|
},
|
||||||
sessionToken: authResult.sessionToken
|
sessionToken: authResult.sessionToken
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ User login error:', error)
|
logger.error('❌ User login error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -87,7 +86,6 @@ router.post('/logout', authenticateUser, async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
message: 'Logout successful'
|
message: 'Logout successful'
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ User logout error:', error)
|
logger.error('❌ User logout error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -125,7 +123,6 @@ router.get('/profile', authenticateUser, async (req, res) => {
|
|||||||
totalUsage: user.totalUsage
|
totalUsage: user.totalUsage
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Get user profile error:', error)
|
logger.error('❌ Get user profile error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -141,7 +138,7 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
|
|||||||
const apiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
const apiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
||||||
|
|
||||||
// 移除敏感信息
|
// 移除敏感信息
|
||||||
const safeApiKeys = apiKeys.map(key => ({
|
const safeApiKeys = apiKeys.map((key) => ({
|
||||||
id: key.id,
|
id: key.id,
|
||||||
name: key.name,
|
name: key.name,
|
||||||
description: key.description,
|
description: key.description,
|
||||||
@@ -154,7 +151,9 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
|
|||||||
dailyCost: key.dailyCost,
|
dailyCost: key.dailyCost,
|
||||||
dailyCostLimit: key.dailyCostLimit,
|
dailyCostLimit: key.dailyCostLimit,
|
||||||
// 不返回实际的key值,只返回前缀和后几位
|
// 不返回实际的key值,只返回前缀和后几位
|
||||||
keyPreview: key.key ? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}` : null
|
keyPreview: key.key
|
||||||
|
? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}`
|
||||||
|
: null
|
||||||
}))
|
}))
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -162,7 +161,6 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
|
|||||||
apiKeys: safeApiKeys,
|
apiKeys: safeApiKeys,
|
||||||
total: safeApiKeys.length
|
total: safeApiKeys.length
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Get user API keys error:', error)
|
logger.error('❌ Get user API keys error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -227,7 +225,6 @@ router.post('/api-keys', authenticateUser, async (req, res) => {
|
|||||||
createdAt: newApiKey.createdAt
|
createdAt: newApiKey.createdAt
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Create user API key error:', error)
|
logger.error('❌ Create user API key error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -265,7 +262,6 @@ router.post('/api-keys/:keyId/regenerate', authenticateUser, async (req, res) =>
|
|||||||
updatedAt: newKey.updatedAt
|
updatedAt: newKey.updatedAt
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Regenerate user API key error:', error)
|
logger.error('❌ Regenerate user API key error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -301,7 +297,6 @@ router.delete('/api-keys/:keyId', authenticateUser, async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
message: 'API key deleted successfully'
|
message: 'API key deleted successfully'
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Delete user API key error:', error)
|
logger.error('❌ Delete user API key error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -318,7 +313,7 @@ router.get('/usage-stats', authenticateUser, async (req, res) => {
|
|||||||
|
|
||||||
// 获取用户的API Keys
|
// 获取用户的API Keys
|
||||||
const userApiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
const userApiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
||||||
const apiKeyIds = userApiKeys.map(key => key.id)
|
const apiKeyIds = userApiKeys.map((key) => key.id)
|
||||||
|
|
||||||
if (apiKeyIds.length === 0) {
|
if (apiKeyIds.length === 0) {
|
||||||
return res.json({
|
return res.json({
|
||||||
@@ -341,7 +336,6 @@ router.get('/usage-stats', authenticateUser, async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
stats
|
stats
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Get user usage stats error:', error)
|
logger.error('❌ Get user usage stats error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -371,10 +365,11 @@ router.get('/', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
|||||||
let filteredUsers = result.users
|
let filteredUsers = result.users
|
||||||
if (search) {
|
if (search) {
|
||||||
const searchLower = search.toLowerCase()
|
const searchLower = search.toLowerCase()
|
||||||
filteredUsers = result.users.filter(user =>
|
filteredUsers = result.users.filter(
|
||||||
user.username.toLowerCase().includes(searchLower) ||
|
(user) =>
|
||||||
user.displayName.toLowerCase().includes(searchLower) ||
|
user.username.toLowerCase().includes(searchLower) ||
|
||||||
user.email.toLowerCase().includes(searchLower)
|
user.displayName.toLowerCase().includes(searchLower) ||
|
||||||
|
user.email.toLowerCase().includes(searchLower)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +383,6 @@ router.get('/', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
|
|||||||
totalPages: result.totalPages
|
totalPages: result.totalPages
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Get users list error:', error)
|
logger.error('❌ Get users list error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -418,7 +412,7 @@ router.get('/:userId', authenticateUserOrAdmin, requireAdmin, async (req, res) =
|
|||||||
success: true,
|
success: true,
|
||||||
user: {
|
user: {
|
||||||
...user,
|
...user,
|
||||||
apiKeys: apiKeys.map(key => ({
|
apiKeys: apiKeys.map((key) => ({
|
||||||
id: key.id,
|
id: key.id,
|
||||||
name: key.name,
|
name: key.name,
|
||||||
description: key.description,
|
description: key.description,
|
||||||
@@ -426,11 +420,12 @@ router.get('/:userId', authenticateUserOrAdmin, requireAdmin, async (req, res) =
|
|||||||
createdAt: key.createdAt,
|
createdAt: key.createdAt,
|
||||||
lastUsedAt: key.lastUsedAt,
|
lastUsedAt: key.lastUsedAt,
|
||||||
usage: key.usage,
|
usage: key.usage,
|
||||||
keyPreview: key.key ? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}` : null
|
keyPreview: key.key
|
||||||
|
? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}`
|
||||||
|
: null
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Get user details error:', error)
|
logger.error('❌ Get user details error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -456,7 +451,9 @@ router.patch('/:userId/status', authenticateUserOrAdmin, requireAdmin, async (re
|
|||||||
const updatedUser = await userService.updateUserStatus(userId, isActive)
|
const updatedUser = await userService.updateUserStatus(userId, isActive)
|
||||||
|
|
||||||
const adminUser = req.admin?.username || req.user?.username
|
const adminUser = req.admin?.username || req.user?.username
|
||||||
logger.info(`🔄 Admin ${adminUser} ${isActive ? 'enabled' : 'disabled'} user: ${updatedUser.username}`)
|
logger.info(
|
||||||
|
`🔄 Admin ${adminUser} ${isActive ? 'enabled' : 'disabled'} user: ${updatedUser.username}`
|
||||||
|
)
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -468,7 +465,6 @@ router.patch('/:userId/status', authenticateUserOrAdmin, requireAdmin, async (re
|
|||||||
updatedAt: updatedUser.updatedAt
|
updatedAt: updatedUser.updatedAt
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Update user status error:', error)
|
logger.error('❌ Update user status error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -507,7 +503,6 @@ router.patch('/:userId/role', authenticateUserOrAdmin, requireAdmin, async (req,
|
|||||||
updatedAt: updatedUser.updatedAt
|
updatedAt: updatedUser.updatedAt
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Update user role error:', error)
|
logger.error('❌ Update user role error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -540,7 +535,6 @@ router.post('/:userId/disable-keys', authenticateUserOrAdmin, requireAdmin, asyn
|
|||||||
message: `Disabled ${result.count} API keys for user ${user.username}`,
|
message: `Disabled ${result.count} API keys for user ${user.username}`,
|
||||||
disabledCount: result.count
|
disabledCount: result.count
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Disable user API keys error:', error)
|
logger.error('❌ Disable user API keys error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -566,7 +560,7 @@ router.get('/:userId/usage-stats', authenticateUserOrAdmin, requireAdmin, async
|
|||||||
|
|
||||||
// 获取用户的API Keys
|
// 获取用户的API Keys
|
||||||
const userApiKeys = await apiKeyService.getUserApiKeys(userId)
|
const userApiKeys = await apiKeyService.getUserApiKeys(userId)
|
||||||
const apiKeyIds = userApiKeys.map(key => key.id)
|
const apiKeyIds = userApiKeys.map((key) => key.id)
|
||||||
|
|
||||||
if (apiKeyIds.length === 0) {
|
if (apiKeyIds.length === 0) {
|
||||||
return res.json({
|
return res.json({
|
||||||
@@ -599,7 +593,6 @@ router.get('/:userId/usage-stats', authenticateUserOrAdmin, requireAdmin, async
|
|||||||
},
|
},
|
||||||
stats
|
stats
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Get user usage stats (admin) error:', error)
|
logger.error('❌ Get user usage stats (admin) error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -618,7 +611,6 @@ router.get('/stats/overview', authenticateUserOrAdmin, requireAdmin, async (req,
|
|||||||
success: true,
|
success: true,
|
||||||
stats
|
stats
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Get user stats overview error:', error)
|
logger.error('❌ Get user stats overview error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -638,7 +630,6 @@ router.get('/admin/ldap-test', authenticateUserOrAdmin, requireAdmin, async (req
|
|||||||
ldapTest: testResult,
|
ldapTest: testResult,
|
||||||
config: ldapService.getConfigInfo()
|
config: ldapService.getConfigInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ LDAP test error:', error)
|
logger.error('❌ LDAP test error:', error)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
|
|||||||
@@ -492,8 +492,8 @@ class ApiKeyService {
|
|||||||
try {
|
try {
|
||||||
const allKeys = await redis.getAllApiKeys()
|
const allKeys = await redis.getAllApiKeys()
|
||||||
return allKeys
|
return allKeys
|
||||||
.filter(key => key.userId === userId)
|
.filter((key) => key.userId === userId)
|
||||||
.map(key => ({
|
.map((key) => ({
|
||||||
id: key.id,
|
id: key.id,
|
||||||
name: key.name,
|
name: key.name,
|
||||||
description: key.description,
|
description: key.description,
|
||||||
@@ -520,7 +520,9 @@ class ApiKeyService {
|
|||||||
async getApiKeyById(keyId, userId = null) {
|
async getApiKeyById(keyId, userId = null) {
|
||||||
try {
|
try {
|
||||||
const keyData = await redis.getApiKey(keyId)
|
const keyData = await redis.getApiKey(keyId)
|
||||||
if (!keyData) return null
|
if (!keyData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// 如果指定了用户ID,检查权限
|
// 如果指定了用户ID,检查权限
|
||||||
if (userId && keyData.userId !== userId) {
|
if (userId && keyData.userId !== userId) {
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ class LdapService {
|
|||||||
errors.push('LDAP bind DN is not configured or invalid')
|
errors.push('LDAP bind DN is not configured or invalid')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.config.server.bindCredentials || typeof this.config.server.bindCredentials !== 'string') {
|
if (
|
||||||
|
!this.config.server.bindCredentials ||
|
||||||
|
typeof this.config.server.bindCredentials !== 'string'
|
||||||
|
) {
|
||||||
errors.push('LDAP bind credentials are not configured or invalid')
|
errors.push('LDAP bind credentials are not configured or invalid')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,8 +187,8 @@ class LdapService {
|
|||||||
async bindClient(client) {
|
async bindClient(client) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 验证绑定凭据
|
// 验证绑定凭据
|
||||||
const bindDN = this.config.server.bindDN
|
const { bindDN } = this.config.server
|
||||||
const bindCredentials = this.config.server.bindCredentials
|
const { bindCredentials } = this.config.server
|
||||||
|
|
||||||
if (!bindDN || typeof bindDN !== 'string') {
|
if (!bindDN || typeof bindDN !== 'string') {
|
||||||
const error = new Error('LDAP bind DN is not configured or invalid')
|
const error = new Error('LDAP bind DN is not configured or invalid')
|
||||||
@@ -256,7 +259,9 @@ class LdapService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
res.on('end', (result) => {
|
res.on('end', (result) => {
|
||||||
logger.debug(`✅ LDAP search completed. Status: ${result.status}, Found ${entries.length} entries`)
|
logger.debug(
|
||||||
|
`✅ LDAP search completed. Status: ${result.status}, Found ${entries.length} entries`
|
||||||
|
)
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
resolve(null)
|
resolve(null)
|
||||||
@@ -329,7 +334,7 @@ class LdapService {
|
|||||||
|
|
||||||
// 创建属性映射
|
// 创建属性映射
|
||||||
const attrMap = {}
|
const attrMap = {}
|
||||||
attributes.forEach(attr => {
|
attributes.forEach((attr) => {
|
||||||
const name = attr.type || attr.name
|
const name = attr.type || attr.name
|
||||||
const values = Array.isArray(attr.values) ? attr.values : [attr.values]
|
const values = Array.isArray(attr.values) ? attr.values : [attr.values]
|
||||||
attrMap[name] = values.length === 1 ? values[0] : values
|
attrMap[name] = values.length === 1 ? values[0] : values
|
||||||
@@ -386,7 +391,10 @@ class LdapService {
|
|||||||
throw new Error('LDAP bind DN is not configured')
|
throw new Error('LDAP bind DN is not configured')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.config.server.bindCredentials || typeof this.config.server.bindCredentials !== 'string') {
|
if (
|
||||||
|
!this.config.server.bindCredentials ||
|
||||||
|
typeof this.config.server.bindCredentials !== 'string'
|
||||||
|
) {
|
||||||
throw new Error('LDAP bind credentials are not configured')
|
throw new Error('LDAP bind credentials are not configured')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,7 +478,6 @@ class LdapService {
|
|||||||
sessionToken,
|
sessionToken,
|
||||||
message: 'Authentication successful'
|
message: 'Authentication successful'
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ LDAP authentication error:', error)
|
logger.error('❌ LDAP authentication error:', error)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
const redis = require('../models/redis')
|
const redis = require('../models/redis')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const bcrypt = require('bcryptjs')
|
|
||||||
const logger = require('../utils/logger')
|
const logger = require('../utils/logger')
|
||||||
const config = require('../../config/config')
|
const config = require('../../config/config')
|
||||||
|
|
||||||
@@ -88,7 +87,9 @@ class UserService {
|
|||||||
async getUserByUsername(username) {
|
async getUserByUsername(username) {
|
||||||
try {
|
try {
|
||||||
const userId = await redis.get(`${this.usernamePrefix}${username}`)
|
const userId = await redis.get(`${this.usernamePrefix}${username}`)
|
||||||
if (!userId) return null
|
if (!userId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const userData = await redis.get(`${this.userPrefix}${userId}`)
|
const userData = await redis.get(`${this.userPrefix}${userId}`)
|
||||||
return userData ? JSON.parse(userData) : null
|
return userData ? JSON.parse(userData) : null
|
||||||
@@ -99,16 +100,100 @@ class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 👤 通过ID获取用户
|
// 👤 通过ID获取用户
|
||||||
async getUserById(userId) {
|
async getUserById(userId, calculateUsage = true) {
|
||||||
try {
|
try {
|
||||||
const userData = await redis.get(`${this.userPrefix}${userId}`)
|
const userData = await redis.get(`${this.userPrefix}${userId}`)
|
||||||
return userData ? JSON.parse(userData) : null
|
if (!userData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = JSON.parse(userData)
|
||||||
|
|
||||||
|
// Calculate totalUsage by aggregating user's API keys usage (if requested)
|
||||||
|
if (calculateUsage) {
|
||||||
|
try {
|
||||||
|
const usageStats = await this.calculateUserUsageStats(userId)
|
||||||
|
user.totalUsage = usageStats.totalUsage
|
||||||
|
user.apiKeyCount = usageStats.apiKeyCount
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Error calculating user usage stats:', error)
|
||||||
|
// Fallback to stored values if calculation fails
|
||||||
|
user.totalUsage = user.totalUsage || {
|
||||||
|
requests: 0,
|
||||||
|
inputTokens: 0,
|
||||||
|
outputTokens: 0,
|
||||||
|
totalCost: 0
|
||||||
|
}
|
||||||
|
user.apiKeyCount = user.apiKeyCount || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Error getting user by ID:', error)
|
logger.error('❌ Error getting user by ID:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 📊 计算用户使用统计(通过聚合API Keys)
|
||||||
|
async calculateUserUsageStats(userId) {
|
||||||
|
try {
|
||||||
|
// Use redis directly to avoid circular dependency
|
||||||
|
const client = redis.getClientSafe()
|
||||||
|
const pattern = 'api_key:*'
|
||||||
|
const keys = await client.keys(pattern)
|
||||||
|
|
||||||
|
const userApiKeys = []
|
||||||
|
for (const key of keys) {
|
||||||
|
const keyData = await client.get(key)
|
||||||
|
if (keyData) {
|
||||||
|
const apiKey = JSON.parse(keyData)
|
||||||
|
if (apiKey.userId === userId) {
|
||||||
|
// Get usage stats for this API key
|
||||||
|
const usage = await redis.getUsageStats(apiKey.id)
|
||||||
|
userApiKeys.push({
|
||||||
|
id: apiKey.id,
|
||||||
|
name: apiKey.name,
|
||||||
|
usage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalUsage = {
|
||||||
|
requests: 0,
|
||||||
|
inputTokens: 0,
|
||||||
|
outputTokens: 0,
|
||||||
|
totalCost: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const apiKey of userApiKeys) {
|
||||||
|
if (apiKey.usage) {
|
||||||
|
totalUsage.requests += apiKey.usage.requests || 0
|
||||||
|
totalUsage.inputTokens += apiKey.usage.inputTokens || 0
|
||||||
|
totalUsage.outputTokens += apiKey.usage.outputTokens || 0
|
||||||
|
totalUsage.totalCost += apiKey.usage.totalCost || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalUsage,
|
||||||
|
apiKeyCount: userApiKeys.length
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Error calculating user usage stats:', error)
|
||||||
|
return {
|
||||||
|
totalUsage: {
|
||||||
|
requests: 0,
|
||||||
|
inputTokens: 0,
|
||||||
|
outputTokens: 0,
|
||||||
|
totalCost: 0
|
||||||
|
},
|
||||||
|
apiKeyCount: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 📋 获取所有用户列表(管理员功能)
|
// 📋 获取所有用户列表(管理员功能)
|
||||||
async getAllUsers(options = {}) {
|
async getAllUsers(options = {}) {
|
||||||
try {
|
try {
|
||||||
@@ -124,8 +209,12 @@ class UserService {
|
|||||||
const user = JSON.parse(userData)
|
const user = JSON.parse(userData)
|
||||||
|
|
||||||
// 应用过滤条件
|
// 应用过滤条件
|
||||||
if (role && user.role !== role) continue
|
if (role && user.role !== role) {
|
||||||
if (typeof isActive === 'boolean' && user.isActive !== isActive) continue
|
continue
|
||||||
|
}
|
||||||
|
if (typeof isActive === 'boolean' && user.isActive !== isActive) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
users.push(user)
|
users.push(user)
|
||||||
}
|
}
|
||||||
@@ -153,7 +242,7 @@ class UserService {
|
|||||||
// 🔄 更新用户状态
|
// 🔄 更新用户状态
|
||||||
async updateUserStatus(userId, isActive) {
|
async updateUserStatus(userId, isActive) {
|
||||||
try {
|
try {
|
||||||
const user = await this.getUserById(userId)
|
const user = await this.getUserById(userId, false) // Skip usage calculation
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found')
|
||||||
}
|
}
|
||||||
@@ -179,7 +268,7 @@ class UserService {
|
|||||||
// 🔄 更新用户角色
|
// 🔄 更新用户角色
|
||||||
async updateUserRole(userId, role) {
|
async updateUserRole(userId, role) {
|
||||||
try {
|
try {
|
||||||
const user = await this.getUserById(userId)
|
const user = await this.getUserById(userId, false) // Skip usage calculation
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found')
|
||||||
}
|
}
|
||||||
@@ -197,46 +286,22 @@ class UserService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📊 更新用户使用统计
|
// 📊 更新用户API Key数量 (已废弃,现在通过聚合计算)
|
||||||
async updateUserUsage(userId, usage) {
|
async updateUserApiKeyCount(userId, _count) {
|
||||||
try {
|
// This method is deprecated since apiKeyCount is now calculated dynamically
|
||||||
const user = await this.getUserById(userId)
|
// in getUserById by aggregating the user's API keys
|
||||||
if (!user) return
|
logger.debug(
|
||||||
|
`📊 updateUserApiKeyCount called for ${userId} but is now deprecated (count auto-calculated)`
|
||||||
const { requests = 0, inputTokens = 0, outputTokens = 0, cost = 0 } = usage
|
)
|
||||||
|
|
||||||
user.totalUsage.requests += requests
|
|
||||||
user.totalUsage.inputTokens += inputTokens
|
|
||||||
user.totalUsage.outputTokens += outputTokens
|
|
||||||
user.totalUsage.totalCost += cost
|
|
||||||
user.updatedAt = new Date().toISOString()
|
|
||||||
|
|
||||||
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('❌ Error updating user usage:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 📊 更新用户API Key数量
|
|
||||||
async updateUserApiKeyCount(userId, count) {
|
|
||||||
try {
|
|
||||||
const user = await this.getUserById(userId)
|
|
||||||
if (!user) return
|
|
||||||
|
|
||||||
user.apiKeyCount = count
|
|
||||||
user.updatedAt = new Date().toISOString()
|
|
||||||
|
|
||||||
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('❌ Error updating user API key count:', error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📝 记录用户登录
|
// 📝 记录用户登录
|
||||||
async recordUserLogin(userId) {
|
async recordUserLogin(userId) {
|
||||||
try {
|
try {
|
||||||
const user = await this.getUserById(userId)
|
const user = await this.getUserById(userId, false) // Skip usage calculation
|
||||||
if (!user) return
|
if (!user) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user.lastLoginAt = new Date().toISOString()
|
user.lastLoginAt = new Date().toISOString()
|
||||||
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
|
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
|
||||||
@@ -272,7 +337,9 @@ class UserService {
|
|||||||
async validateUserSession(sessionToken) {
|
async validateUserSession(sessionToken) {
|
||||||
try {
|
try {
|
||||||
const sessionData = await redis.get(`${this.userSessionPrefix}${sessionToken}`)
|
const sessionData = await redis.get(`${this.userSessionPrefix}${sessionToken}`)
|
||||||
if (!sessionData) return null
|
if (!sessionData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const session = JSON.parse(sessionData)
|
const session = JSON.parse(sessionData)
|
||||||
|
|
||||||
@@ -283,7 +350,7 @@ class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
const user = await this.getUserById(session.userId)
|
const user = await this.getUserById(session.userId, false) // Skip usage calculation for validation
|
||||||
if (!user || !user.isActive) {
|
if (!user || !user.isActive) {
|
||||||
await this.invalidateUserSession(sessionToken)
|
await this.invalidateUserSession(sessionToken)
|
||||||
return null
|
return null
|
||||||
@@ -332,7 +399,7 @@ class UserService {
|
|||||||
// 🗑️ 删除用户(软删除,标记为不活跃)
|
// 🗑️ 删除用户(软删除,标记为不活跃)
|
||||||
async deleteUser(userId) {
|
async deleteUser(userId) {
|
||||||
try {
|
try {
|
||||||
const user = await this.getUserById(userId)
|
const user = await this.getUserById(userId, false) // Skip usage calculation
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user