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:
@@ -22,9 +22,20 @@ function getApiKey() {
|
||||
function clearApiKeyCache() { cachedApiKey = null; }
|
||||
|
||||
const xssOptions = {
|
||||
whiteList: {},
|
||||
whiteList: {
|
||||
p: [], br: [], strong: [], b: [], em: [], i: [], u: [], s: [], strike: [],
|
||||
a: ['href', 'title', 'target', 'rel'], span: [], div: [],
|
||||
ul: [], ol: [], li: [], h1: [], h2: [], h3: [], h4: [], h5: [], h6: [],
|
||||
table: [], thead: [], tbody: [], tfoot: [], tr: [], th: [], td: [], caption: [],
|
||||
code: [], pre: [], blockquote: [], hr: [], img: ['src', 'alt', 'title', 'width', 'height'],
|
||||
font: ['color', 'size', 'face'], small: [], sub: [], sup: [],
|
||||
},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style', 'xml', 'iframe', 'object', 'embed'],
|
||||
onTagAttr: (tag, name, value) => {
|
||||
if ((name === 'href' || name === 'src') && /^\s*(javascript|data):/i.test(value)) return '';
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
function sanitize(value) {
|
||||
@@ -85,6 +96,7 @@ const ticketLimiter = rateLimit({
|
||||
const ticketAnonLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 5,
|
||||
skip: (req) => !!(req.headers.authorization),
|
||||
message: { error: '匿名提交过于频繁,请登录后再试或稍后重试' },
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
|
||||
@@ -24,7 +24,7 @@ function validateMagicBytes(buffer, mime) {
|
||||
'image/png': () => head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4E && head[3] === 0x47,
|
||||
'image/gif': () => head.toString('ascii', 0, 6) === 'GIF89a' || head.toString('ascii', 0, 6) === 'GIF87a',
|
||||
'image/webp': () => head.toString('ascii', 0, 4) === 'RIFF' && head.toString('ascii', 8, 12) === 'WEBP',
|
||||
'video/mp4': () => buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70,
|
||||
'video/mp4': () => head[4] === 0x66 && head[5] === 0x74 && head[6] === 0x79 && head[7] === 0x70,
|
||||
'video/webm': () => head[0] === 0x1A && head[1] === 0x45 && head[2] === 0xDF && head[3] === 0xA3,
|
||||
};
|
||||
if (!sigs[mime]) return false;
|
||||
|
||||
Reference in New Issue
Block a user