diff --git a/backend/middleware/security.js b/backend/middleware/security.js index ad4c9a2..2825b02 100644 --- a/backend/middleware/security.js +++ b/backend/middleware/security.js @@ -154,7 +154,7 @@ const captchaLimiter = rateLimit({ function apiKeyGuard(req, res, next) { const p = req.originalUrl; - if (p === '/api/health' || p.startsWith('/api/install') || p.startsWith('/api/polls/server-list')) return next(); + if (p === '/api/health' || p.startsWith('/api/install') || p.startsWith('/api/polls/server-list') || p.startsWith('/api/settings/public') || p.startsWith('/api/settings/skin-site')) return next(); const key = getApiKey(); if (!key) return next(); if (!req.headers['x-api-key'] || req.headers['x-api-key'].length !== key.length) return res.status(401).json({ error: '无效的 API 密钥' }); diff --git a/backend/routes/settings.js b/backend/routes/settings.js index 37eb942..c62391e 100644 --- a/backend/routes/settings.js +++ b/backend/routes/settings.js @@ -70,6 +70,16 @@ function formatUuid(id) { return `${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20)}`; } +// 公开: 页尾信息(版权/ICP, 访客可见, 不暴露其他设置) +router.get('/public', async (req, res) => { + try { + const rows = await query("SELECT k, v FROM settings WHERE k IN ('copyright','icp')"); + const map = {}; + for (const r of rows) map[r.k] = r.v; + res.json({ copyright: map.copyright || '', icp: map.icp || '' }); + } catch { res.json({ copyright: '', icp: '' }); } +}); + // 公开: 读取站点配置的皮肤站地址(玩家绑定身份时自动带出) router.get('/skin-site', async (req, res) => { try { diff --git a/public/js/app.js b/public/js/app.js index 7d45bac..6b7ab6b 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -71,14 +71,15 @@ const App = { }, async loadFooter() { - if (!Auth.isAdmin() || this._footerLoaded) return; + if (this._footerLoaded) return; this._footerLoaded = true; try { - const s = await API.get('/settings/settings'); + // 公开接口: 访客与登录用户都能读到页尾(版权/ICP), 不暴露敏感设置 + const s = await API.get('/settings/public'); const cr = document.getElementById('footer-copyright'); const icp = document.getElementById('footer-icp'); - if (cr && s.copyright) cr.innerHTML = s.copyright.value; - if (icp && s.icp && s.icp.value) icp.innerHTML = `${s.icp.value}`; + if (cr && s.copyright) cr.innerHTML = s.copyright; + if (icp && s.icp) icp.innerHTML = `${s.icp}`; } catch {} }, @@ -160,6 +161,7 @@ const App = { pub.classList.remove('hidden'); document.getElementById('top-bar').classList.remove('hidden'); this.updateTopAuth(); + this.loadFooter(); const comp = page === 'login' ? LoginPage : page === 'register' ? RegisterPage : page === 'install' ? InstallPage : page === 'forgot' ? ForgotPage : page === 'reset' ? ResetPage : page === 'verify' ? VerifyPage : HomePage; comp.render(param).then(html => { pub.innerHTML = html; if (comp.mount) comp.mount(param); }).catch(() => { pub.innerHTML = '
';