g
This commit is contained in:
@@ -875,13 +875,28 @@ process.on('uncaughtException', err => {
|
||||
}
|
||||
});
|
||||
|
||||
// CSRF validation helper — used by route handlers that have already populated req.session
|
||||
// NOTE: Cannot be used in flummpress app.use() middlewares for upload/avatar bypass handlers
|
||||
// because flummpress runs ALL middlewares in parallel (Promise.all), so the session
|
||||
// middleware hasn't finished by the time these run. Those handlers validate CSRF inline.
|
||||
const validateCsrf = (req, res) => {
|
||||
// Intercept app.readBody to allow memoized reading of request body.
|
||||
// flummpress runs app.use() in parallel via Promise.all before routing/body-reading,
|
||||
// so validateCsrf needs to be able to read req.post without breaking subsequent router handler body parsing.
|
||||
const _originalReadBody = app.readBody.bind(app);
|
||||
app.readBody = async (req) => {
|
||||
if (req.post !== undefined) return req.post;
|
||||
return await _originalReadBody(req);
|
||||
};
|
||||
|
||||
// CSRF validation helper — used by route handlers and global middleware
|
||||
const validateCsrf = async (req, res) => {
|
||||
if (req.session && req.session.csrf_token) {
|
||||
const token = req.headers['x-csrf-token'] || req.body?.csrf_token || req.post?.csrf_token || req.url.qs?.csrf_token;
|
||||
let token = req.headers['x-csrf-token'] || req.body?.csrf_token || req.post?.csrf_token || req.url.qs?.csrf_token;
|
||||
|
||||
// If header/query token is missing and body is not parsed yet on a non-GET method, parse it now
|
||||
if (!token && req.post === undefined && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
|
||||
try {
|
||||
req.post = await app.readBody(req);
|
||||
token = req.post?.csrf_token;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!token || token !== req.session.csrf_token) {
|
||||
console.error(`[CSRF] Blocked ${req.method} ${req.url.pathname} for user ${req.session.user}. Reason: ${!token ? 'Missing token' : 'Token mismatch'}`);
|
||||
res.writeHead(403, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: 'Invalid CSRF token' }));
|
||||
@@ -903,7 +918,7 @@ process.on('uncaughtException', err => {
|
||||
if (cfg.websrv.halls_enabled !== false && req.url.pathname.match(/^\/api\/v2\/admin\/halls(\/|$)/)) return;
|
||||
// User hall image upload is handled by bypass middleware below
|
||||
if (cfg.websrv.userhalls_enabled !== false && cfg.websrv.enable_userhall_image_upload !== false && req.url.pathname.match(/^\/api\/v2\/me\/halls\/[^/]+\/image$/)) return;
|
||||
if (!validateCsrf(req, res)) return;
|
||||
if (!(await validateCsrf(req, res))) return;
|
||||
});
|
||||
|
||||
// Bypass middleware for direct upload handling
|
||||
@@ -1363,6 +1378,8 @@ process.on('uncaughtException', err => {
|
||||
site_description: cfg.websrv.description || "The webs dumpster",
|
||||
enable_nsfl: !!cfg.enable_nsfl,
|
||||
enable_private_uploads: cfg.enable_private_uploads !== false,
|
||||
default_upload_visibility: (typeof cfg.default_upload_visibility === 'number' ? cfg.default_upload_visibility : (typeof cfg.websrv?.default_upload_visibility === 'number' ? cfg.websrv.default_upload_visibility : 0)),
|
||||
allow_user_upload_visibility: cfg.allow_user_upload_visibility !== false && cfg.websrv?.allow_user_upload_visibility !== false,
|
||||
nsfl_tag_id: cfg.nsfl_tag_id || 3,
|
||||
scroller_mime_cats: Array.isArray(cfg.allowedMimes) ? cfg.allowedMimes.filter(c => ['video','image','audio'].includes(c)) : ['video','image','audio'],
|
||||
themes_json: JSON.stringify(cfg.websrv.themes || []),
|
||||
|
||||
Reference in New Issue
Block a user