fix: deploy crash + multiple bug fixes + cleanup
- server.js: fix getSiteName() returning string when not installed causing 'getSiteName(...).then is not a function' crash on homepage (deploy blocker) - server.js: auto-load business routes after install completes (no restart needed), HTML cache keyed by api_key - install: validate db name/email/password, trigger route loading after complete - app.js: fix forgot/reset/verify pages rendering HomePage (missing page mapping) - verify.js: support URL token auto-verification for external registration links - auth: new email_code template for 6-digit code, reset_password template (forgot-password was using verify_email template) - upload.js: fix MP4 magic-bytes check using undefined buf variable - tickets.js: status enum validation, anonymous submission rate limit - security.js: XSS whitelist preserves email template HTML, strips scripts, blocks javascript:/data: hrefs; CORS reject returns 403 - bans.js: allow clearing reason/duration, status enum validation - users.js: fix req.user.role ReferenceError in create user modal - home.js: tracking results now have detail view button - .gitignore: ignore data/ (db credentials), logs, session files, temp scripts
This commit is contained in:
@@ -109,7 +109,7 @@ const App = {
|
||||
pub.classList.remove('hidden');
|
||||
document.getElementById('top-bar').classList.remove('hidden');
|
||||
this.updateTopAuth();
|
||||
const comp = page === 'login' ? LoginPage : page === 'register' ? RegisterPage : page === 'install' ? InstallPage : 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(() => {
|
||||
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>';
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ const HomePage = {
|
||||
r.innerHTML = '<div class="loading"><i class="fas fa-spinner"></i></div>';
|
||||
try {
|
||||
const tickets = await API.get(`/tickets/tracking?token=${token}`);
|
||||
r.innerHTML = tickets.length ? `<div class="table-wrap"><table><thead><tr><th>#</th><th>类型</th><th>标题</th><th>状态</th><th>时间</th></tr></thead><tbody>${tickets.map(tk=>`<tr><td>${tk.id}</td><td>${U.badge(tk.type,'type')}</td><td>${U.esc(tk.title)}</td><td>${U.badge(tk.status,'status')}</td><td>${U.date(tk.created_at)}</td></tr>`).join('')}</tbody></table></div>` : '<div class="empty"><i class="fas fa-inbox"></i><p>未找到工单</p></div>';
|
||||
r.innerHTML = tickets.length ? `<div class="table-wrap"><table><thead><tr><th>#</th><th>类型</th><th>标题</th><th>状态</th><th>时间</th><th>操作</th></tr></thead><tbody>${tickets.map(tk=>`<tr><td>${tk.id}</td><td>${U.badge(tk.type,'type')}</td><td>${U.esc(tk.title)}</td><td>${U.badge(tk.status,'status')}</td><td>${U.date(tk.created_at)}</td><td><button class="btn btn-o btn-sm" onclick="HomePage.showTrackDetail(${tk.id},'${U.escJs(token)}')"><i class="fas fa-eye"></i></button></td></tr>`).join('')}</tbody></table></div>` : '<div class="empty"><i class="fas fa-inbox"></i><p>未找到工单</p></div>';
|
||||
} catch (e) { r.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
|
||||
},
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ const InstallPage = {
|
||||
admin_game_name: document.getElementById('ia-game').value,
|
||||
admin_game_uid: document.getElementById('ia-uid').value,
|
||||
});
|
||||
document.getElementById('install-admin').innerHTML = '<div class="alert alert-s"><h3><i class="fas fa-check-circle"></i> 安装成功!</h3><p>配置已保存。请重启服务器后刷新页面。</p></div>';
|
||||
document.getElementById('install-admin').innerHTML = '<div class="alert alert-s"><h3><i class="fas fa-check-circle"></i> 安装成功!</h3><p>系统已就绪,正在跳转登录...</p></div>';
|
||||
setTimeout(() => location.hash = '#/login', 1500);
|
||||
} catch (ex) {
|
||||
document.getElementById('ia-err').textContent = ex.message;
|
||||
document.getElementById('ia-err').classList.remove('hidden');
|
||||
|
||||
@@ -44,7 +44,7 @@ const UsersPage = {
|
||||
<form id="cu-form">
|
||||
<div class="grid-2"><div class="form-group"><label>用户名 *</label><input id="cu-u" required></div><div class="form-group"><label>密码 *</label><input type="password" id="cu-p" required minlength="6"></div></div>
|
||||
<div class="grid-2"><div class="form-group"><label>邮箱 *</label><input type="email" id="cu-e" required></div><div class="form-group"><label>来源</label><select id="cu-s"><option value="netease">网易端</option><option value="skin">皮肤站</option></select></div></div>
|
||||
<div class="grid-2"><div class="form-group"><label>角色 *</label><select id="cu-r" onchange="UsersPage.toggleCreateServers()">${req.user.role==='owner'?'<option value="owner">服主</option>':''}<option value="admin">管理员</option><option value="player">玩家</option></select></div></div>
|
||||
<div class="grid-2"><div class="form-group"><label>角色 *</label><select id="cu-r" onchange="UsersPage.toggleCreateServers()">${Auth.isOwner()?'<option value="owner">服主</option>':''}<option value="admin">管理员</option><option value="player">玩家</option></select></div></div>
|
||||
<div class="grid-2"><div class="form-group"><label>游戏名 *</label><input id="cu-gn" required></div><div class="form-group"><label>游戏UID *</label><input id="cu-gu" required></div></div>
|
||||
<div class="form-group hidden" id="cu-servers"><label>管辖子服务器</label><div style="max-height:120px;overflow-y:auto;border:1px solid var(--g200);border-radius:var(--r);padding:8px">${checks||'<span class="tm">暂无可选服务器</span>'}</div></div>
|
||||
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">取消</button><button type="submit" class="btn btn-p">创建</button></div>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
const VerifyPage = {
|
||||
async render() {
|
||||
const params = new URLSearchParams((location.hash.split('?')[1] || ''));
|
||||
const token = params.get('token');
|
||||
if (token) {
|
||||
return `<div class="pub-card"><div class="logo"><i class="fas fa-envelope"></i><h2>邮箱验证</h2><p>正在验证您的邮箱...</p></div>
|
||||
<div id="vf-msg" class="alert alert-i">验证中,请稍候...</div>
|
||||
<div style="margin-top:14px;text-align:center;font-size:13px"><a href="#/login" style="color:var(--p);text-decoration:none">返回登录</a></div>
|
||||
</div>`;
|
||||
}
|
||||
return `<div class="pub-card"><div class="logo"><i class="fas fa-envelope"></i><h2>邮箱验证</h2><p>请输入邮件中的6位验证码</p></div>
|
||||
<form id="verify-form"><div class="form-group"><input id="vf-code" placeholder="6位验证码" required maxlength="6" minlength="6" pattern="[0-9]{6}" autofocus></div>
|
||||
<div id="vf-msg" class="hidden"></div>
|
||||
@@ -8,6 +16,12 @@ const VerifyPage = {
|
||||
</div>`;
|
||||
},
|
||||
mount() {
|
||||
const params = new URLSearchParams((location.hash.split('?')[1] || ''));
|
||||
const token = params.get('token');
|
||||
if (token) {
|
||||
this.verifyToken(token);
|
||||
return;
|
||||
}
|
||||
document.getElementById('verify-form').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const code = document.getElementById('vf-code').value.trim();
|
||||
@@ -22,5 +36,15 @@ const VerifyPage = {
|
||||
msg.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
},
|
||||
async verifyToken(token) {
|
||||
const msg = document.getElementById('vf-msg');
|
||||
try {
|
||||
const r = await API.post('/auth/verify-email', { code: token });
|
||||
msg.className = 'alert alert-s'; msg.textContent = r.message || '验证成功';
|
||||
setTimeout(() => location.hash = '#/login', 2000);
|
||||
} catch (ex) {
|
||||
msg.className = 'alert alert-e'; msg.textContent = ex.message;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user