fix: public footer reads DB settings for all visitors
- settings.js: new public GET /settings/public returns only copyright + icp (no auth, no sensitive settings leaked) - app.js loadFooter(): dropped Auth.isAdmin() gate, uses public endpoint; showPublic() (home/root) now loads footer too - security.js: apiKeyGuard whitelist /api/settings/public and /api/settings/skin-site
This commit is contained in:
@@ -154,7 +154,7 @@ const captchaLimiter = rateLimit({
|
|||||||
|
|
||||||
function apiKeyGuard(req, res, next) {
|
function apiKeyGuard(req, res, next) {
|
||||||
const p = req.originalUrl;
|
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();
|
const key = getApiKey();
|
||||||
if (!key) return next();
|
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 密钥' });
|
if (!req.headers['x-api-key'] || req.headers['x-api-key'].length !== key.length) return res.status(401).json({ error: '无效的 API 密钥' });
|
||||||
|
|||||||
@@ -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)}`;
|
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) => {
|
router.get('/skin-site', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -71,14 +71,15 @@ const App = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async loadFooter() {
|
async loadFooter() {
|
||||||
if (!Auth.isAdmin() || this._footerLoaded) return;
|
if (this._footerLoaded) return;
|
||||||
this._footerLoaded = true;
|
this._footerLoaded = true;
|
||||||
try {
|
try {
|
||||||
const s = await API.get('/settings/settings');
|
// 公开接口: 访客与登录用户都能读到页尾(版权/ICP), 不暴露敏感设置
|
||||||
|
const s = await API.get('/settings/public');
|
||||||
const cr = document.getElementById('footer-copyright');
|
const cr = document.getElementById('footer-copyright');
|
||||||
const icp = document.getElementById('footer-icp');
|
const icp = document.getElementById('footer-icp');
|
||||||
if (cr && s.copyright) cr.innerHTML = s.copyright.value;
|
if (cr && s.copyright) cr.innerHTML = s.copyright;
|
||||||
if (icp && s.icp && s.icp.value) icp.innerHTML = `<a href="https://beian.miit.gov.cn" target="_blank">${s.icp.value}</a>`;
|
if (icp && s.icp) icp.innerHTML = `<a href="https://beian.miit.gov.cn" target="_blank">${s.icp}</a>`;
|
||||||
} catch {}
|
} catch {}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -160,6 +161,7 @@ const App = {
|
|||||||
pub.classList.remove('hidden');
|
pub.classList.remove('hidden');
|
||||||
document.getElementById('top-bar').classList.remove('hidden');
|
document.getElementById('top-bar').classList.remove('hidden');
|
||||||
this.updateTopAuth();
|
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;
|
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(() => {
|
comp.render(param).then(html => { pub.innerHTML = html; if (comp.mount) comp.mount(param); }).catch(() => {
|
||||||
pub.innerHTML = '<div class="pub-card"><div class="logo"><i class="fas fa-exclamation-triangle" style="color:var(--d)"></i><h2>加载失败</h2><p>请刷新页面重试</p></div></div>';
|
pub.innerHTML = '<div class="pub-card"><div class="logo"><i class="fas fa-exclamation-triangle" style="color:var(--d)"></i><h2>加载失败</h2><p>请刷新页面重试</p></div></div>';
|
||||||
|
|||||||
Reference in New Issue
Block a user