diff --git a/backend/auth.js b/backend/auth.js index a52c70c..1fe9fb6 100644 --- a/backend/auth.js +++ b/backend/auth.js @@ -132,9 +132,15 @@ function adminMiddleware(req, res, next) { next(); } +// 检查JWT密钥是否安全 +function isJwtSecretSecure() { + return !DEFAULT_SECRETS.includes(JWT_SECRET) && JWT_SECRET.length >= 32; +} + module.exports = { JWT_SECRET, generateToken, authMiddleware, - adminMiddleware + adminMiddleware, + isJwtSecretSecure }; diff --git a/backend/server.js b/backend/server.js index 6589201..5639328 100644 --- a/backend/server.js +++ b/backend/server.js @@ -21,7 +21,7 @@ const execAsync = util.promisify(exec); const execFileAsync = util.promisify(execFile); const { db, UserDB, ShareDB, SettingsDB, VerificationDB, PasswordResetTokenDB } = require('./database'); -const { generateToken, authMiddleware, adminMiddleware } = require('./auth'); +const { generateToken, authMiddleware, adminMiddleware, isJwtSecretSecure } = require('./auth'); const app = express(); const PORT = process.env.PORT || 40001; @@ -3382,6 +3382,184 @@ app.post('/api/admin/settings/test-smtp', authMiddleware, adminMiddleware, async } }); +// 系统健康检测API +app.get('/api/admin/health-check', authMiddleware, adminMiddleware, async (req, res) => { + try { + const checks = []; + let overallStatus = 'healthy'; // healthy, warning, critical + + // 1. JWT密钥安全检查 + const jwtSecure = isJwtSecretSecure(); + checks.push({ + name: 'JWT密钥', + category: 'security', + status: jwtSecure ? 'pass' : 'fail', + message: jwtSecure ? 'JWT密钥已正确配置(随机生成)' : 'JWT密钥使用默认值或长度不足,存在安全风险!', + suggestion: jwtSecure ? null : '请在.env中设置随机生成的JWT_SECRET,至少32字符' + }); + if (!jwtSecure) overallStatus = 'critical'; + + // 2. CORS配置检查 + const allowedOrigins = process.env.ALLOWED_ORIGINS; + const corsConfigured = allowedOrigins && allowedOrigins.trim().length > 0; + checks.push({ + name: 'CORS跨域配置', + category: 'security', + status: corsConfigured ? 'pass' : 'warning', + message: corsConfigured + ? `已配置允许的域名: ${allowedOrigins}` + : 'CORS未配置,允许所有来源(仅适合开发环境)', + suggestion: corsConfigured ? null : '生产环境建议配置ALLOWED_ORIGINS环境变量' + }); + if (!corsConfigured && overallStatus === 'healthy') overallStatus = 'warning'; + + // 3. HTTPS/Cookie安全配置 + const enforceHttps = process.env.ENFORCE_HTTPS === 'true'; + const cookieSecure = process.env.COOKIE_SECURE === 'true'; + const httpsConfigured = enforceHttps && cookieSecure; + checks.push({ + name: 'HTTPS安全配置', + category: 'security', + status: httpsConfigured ? 'pass' : 'warning', + message: httpsConfigured + ? 'HTTPS强制开启,Cookie安全标志已设置' + : `ENFORCE_HTTPS=${enforceHttps}, COOKIE_SECURE=${cookieSecure}`, + suggestion: httpsConfigured ? null : '生产环境建议开启ENFORCE_HTTPS和COOKIE_SECURE' + }); + + // 4. 管理员密码强度检查(检查是否为默认值) + const adminUsername = process.env.ADMIN_USERNAME; + const adminConfigured = adminUsername && adminUsername !== 'admin'; + checks.push({ + name: '管理员账号配置', + category: 'security', + status: adminConfigured ? 'pass' : 'warning', + message: adminConfigured + ? '管理员用户名已自定义' + : '管理员使用默认用户名"admin"', + suggestion: adminConfigured ? null : '建议使用自定义管理员用户名' + }); + + // 5. SMTP邮件配置检查 + const smtpHost = SettingsDB.get('smtp_host') || process.env.SMTP_HOST; + const smtpUser = SettingsDB.get('smtp_user') || process.env.SMTP_USER; + const smtpPassword = SettingsDB.get('smtp_password') || process.env.SMTP_PASSWORD; + const smtpConfigured = smtpHost && smtpUser && smtpPassword; + checks.push({ + name: 'SMTP邮件服务', + category: 'service', + status: smtpConfigured ? 'pass' : 'warning', + message: smtpConfigured + ? `已配置: ${smtpHost}` + : '未配置SMTP,邮箱验证和密码重置功能不可用', + suggestion: smtpConfigured ? null : '配置SMTP以启用邮箱验证功能' + }); + + // 6. 数据库连接检查 + let dbStatus = 'pass'; + let dbMessage = '数据库连接正常'; + try { + const testQuery = db.prepare('SELECT 1').get(); + if (!testQuery) throw new Error('查询返回空'); + } catch (dbError) { + dbStatus = 'fail'; + dbMessage = '数据库连接异常: ' + dbError.message; + overallStatus = 'critical'; + } + checks.push({ + name: '数据库连接', + category: 'service', + status: dbStatus, + message: dbMessage, + suggestion: dbStatus === 'fail' ? '检查数据库文件权限和路径配置' : null + }); + + // 7. 存储目录检查 + const storageRoot = process.env.STORAGE_ROOT || path.join(__dirname, 'storage'); + let storageStatus = 'pass'; + let storageMessage = `存储目录正常: ${storageRoot}`; + try { + if (!fs.existsSync(storageRoot)) { + fs.mkdirSync(storageRoot, { recursive: true }); + storageMessage = `存储目录已创建: ${storageRoot}`; + } + // 检查写入权限 + const testFile = path.join(storageRoot, '.health-check-test'); + fs.writeFileSync(testFile, 'test'); + fs.unlinkSync(testFile); + } catch (storageError) { + storageStatus = 'fail'; + storageMessage = '存储目录不可写: ' + storageError.message; + overallStatus = 'critical'; + } + checks.push({ + name: '存储目录', + category: 'service', + status: storageStatus, + message: storageMessage, + suggestion: storageStatus === 'fail' ? '检查存储目录权限,确保Node进程有写入权限' : null + }); + + // 8. 限流器状态 + const rateLimiterActive = typeof loginLimiter !== 'undefined' && loginLimiter !== null; + checks.push({ + name: '登录防爆破', + category: 'security', + status: rateLimiterActive ? 'pass' : 'warning', + message: rateLimiterActive + ? '限流器已启用(5次/15分钟,封锁30分钟)' + : '限流器未正常初始化', + suggestion: null + }); + + // 9. 信任代理配置(反向代理环境) + const trustProxy = app.get('trust proxy'); + checks.push({ + name: '反向代理支持', + category: 'config', + status: trustProxy ? 'pass' : 'info', + message: trustProxy + ? '已启用trust proxy,支持X-Forwarded-For' + : '未启用trust proxy,直接暴露时无影响', + suggestion: null + }); + + // 10. Node环境 + const nodeEnv = process.env.NODE_ENV || 'development'; + checks.push({ + name: '运行环境', + category: 'config', + status: nodeEnv === 'production' ? 'pass' : 'info', + message: `当前环境: ${nodeEnv}`, + suggestion: nodeEnv !== 'production' ? '生产部署建议设置NODE_ENV=production' : null + }); + + // 统计 + const summary = { + total: checks.length, + pass: checks.filter(c => c.status === 'pass').length, + warning: checks.filter(c => c.status === 'warning').length, + fail: checks.filter(c => c.status === 'fail').length, + info: checks.filter(c => c.status === 'info').length + }; + + res.json({ + success: true, + overallStatus, + summary, + checks, + timestamp: new Date().toISOString(), + version: process.env.npm_package_version || '1.1.0' + }); + } catch (error) { + console.error('健康检测失败:', error); + res.status(500).json({ + success: false, + message: '健康检测失败: ' + error.message + }); + } +}); + // 获取服务器存储统计信息 app.get('/api/admin/storage-stats', authMiddleware, adminMiddleware, async (req, res) => { try { diff --git a/frontend/app.html b/frontend/app.html index fdf5d2f..4fdde2a 100644 --- a/frontend/app.html +++ b/frontend/app.html @@ -654,6 +654,12 @@ background: rgba(102, 126, 234, 0.05); border-radius: 12px; } + + /* 健康检测状态颜色 */ + .text-green-600 { color: #16a34a; } + .text-yellow-600 { color: #ca8a04; } + .text-red-600 { color: #dc2626; } + .text-blue-600 { color: #2563eb; }
@@ -1747,6 +1753,127 @@ + +点击"刷新检测"按钮开始系统健康检测
+