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:
2026-08-16 21:21:25 +08:00
parent 64199a0aaf
commit 6e9101a506
15 changed files with 97 additions and 40 deletions

View File

@@ -17,6 +17,7 @@ const HTML_PATH = path.join(PUBLIC_DIR, 'index.html');
let cachedHtml = null;
let htmlTimestamp = 0;
let htmlKey = null;
function getSiteName() {
if (!isInstalled()) return 'MC举报系统';
@@ -27,17 +28,18 @@ function getSiteName() {
}
function getHtmlWithKey() {
return getSiteName().then(siteName => {
return Promise.resolve(getSiteName()).then(siteName => {
const stat = fs.statSync(HTML_PATH);
const mtime = stat.mtimeMs;
if (!cachedHtml || htmlTimestamp < mtime || cachedHtml.indexOf(siteName) === -1) {
const cfg = getConfig();
const key = cfg?.api_key || '';
if (!cachedHtml || htmlTimestamp < mtime || htmlKey !== key || cachedHtml.indexOf(siteName) === -1) {
let html = fs.readFileSync(HTML_PATH, 'utf-8');
const cfg = getConfig();
const key = cfg?.api_key || '';
const redirect = isInstalled() ? '' : '<script>location.hash="#/install"</script>';
html = html.replace('</head>', `<script>window.__API_KEY__=${JSON.stringify(key)};window.__SITE_NAME__=${JSON.stringify(siteName)}</script>${redirect}</head>`);
cachedHtml = html;
htmlTimestamp = mtime;
htmlKey = key;
}
return cachedHtml;
});
@@ -65,11 +67,9 @@ app.use(cors({
const allowed = process.env.CORS_ORIGIN;
if (!allowed || allowed === '*') return cb(null, true);
if (allowed === origin) return cb(null, true);
if (!allowed) {
const host = origin || '';
if (host.startsWith('http://localhost:') || host.startsWith('https://localhost:')) return cb(null, true);
}
cb(new Error('Not allowed by CORS'));
const host = origin || '';
if (host.startsWith('http://localhost:') || host.startsWith('https://localhost:')) return cb(null, true);
cb(null, false);
},
credentials: true,
methods: ['GET','POST','PUT','DELETE'],
@@ -100,7 +100,11 @@ app.use('/js', express.static(path.join(PUBLIC_DIR, 'js'), staticOpts));
const installRoutes = require('./routes/install');
app.use('/api/install', installRoutes);
if (isInstalled()) {
let businessLoaded = false;
function loadBusinessRoutes() {
if (businessLoaded) return;
businessLoaded = true;
const authRoutes = require('./routes/auth');
app.use('/api/auth/login', methodGuard(['POST']), loginLimiter);
app.use('/api/auth/register', methodGuard(['POST']), registerLimiter);
@@ -124,6 +128,9 @@ if (isInstalled()) {
app.get('/api/verify', (req, res) => res.redirect(`/#/verify?token=${req.query.token}`));
}
if (isInstalled()) loadBusinessRoutes();
global.__loadBusinessRoutes = loadBusinessRoutes;
app.get('/api/health', (req, res) => res.json({ status: 'ok', installed: isInstalled() }));
app.get('/', async (req, res) => {