mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-11 01:55:43 +00:00
e855f5786d
Add a new betterdesk-server Go codebase (server, api, auth, db, relay, signal, metrics, audit, ratelimit, proto, tools, tests) and related deployment/migration scripts. Add a comprehensive SECURITY_AUDIT_2026-03-01 report and .gitattributes; update copilot-instructions (ALL-IN-ONE v2.4.0), README, VERSION, Dockerfiles, scripts, docker-compose and entrypoint. Large updates to web-nodejs (translations, routes, services, frontend assets and middleware) and numerous new utilities; remove legacy Flask web files and archive hbbs-patch-v2 artifacts. Prepares repository for PostgreSQL support, DB migration tooling and the new Go server as the production backend.
84 lines
2.8 KiB
JavaScript
84 lines
2.8 KiB
JavaScript
/**
|
|
* BetterDesk Console - CSRF Protection Middleware
|
|
* Uses csrf-csrf (double-submit cookie pattern) for stateless CSRF protection.
|
|
*
|
|
* Token flow:
|
|
* 1. Server generates token, sets it as a cookie + passes to EJS views
|
|
* 2. Client JS reads window.BetterDesk.csrfToken and sends it in X-CSRF-Token header
|
|
* 3. Middleware validates header matches cookie on state-changing requests (POST/PUT/DELETE/PATCH)
|
|
*/
|
|
|
|
const { doubleCsrf } = require('csrf-csrf');
|
|
const config = require('../config/config');
|
|
|
|
const {
|
|
generateToken,
|
|
doubleCsrfProtection
|
|
} = doubleCsrf({
|
|
getSecret: () => config.sessionSecret,
|
|
cookieName: '__csrf',
|
|
cookieOptions: {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
secure: config.httpsEnabled,
|
|
path: '/'
|
|
},
|
|
getTokenFromRequest: (req) => {
|
|
// Read token from X-CSRF-Token header (set by public/js/utils.js)
|
|
return req.headers['x-csrf-token'] || req.body?._csrf || '';
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Middleware that generates a CSRF token and makes it available to views.
|
|
* Must be applied AFTER cookie-parser and session middleware.
|
|
*
|
|
* If the existing __csrf cookie is malformed (e.g. leftover from an older
|
|
* session or manual edit), generating/validating the token will throw.
|
|
* We catch that, clear the bad cookie, and retry once so that the user
|
|
* is never locked out on a simple GET page load.
|
|
*/
|
|
function csrfTokenProvider(req, res, next) {
|
|
try {
|
|
const token = generateToken(req, res);
|
|
res.locals.csrfToken = token;
|
|
return next();
|
|
} catch (_err) {
|
|
// Clear the corrupt cookie and try once more
|
|
res.clearCookie('__csrf', { path: '/' });
|
|
if (req.cookies) delete req.cookies['__csrf'];
|
|
try {
|
|
const token = generateToken(req, res);
|
|
res.locals.csrfToken = token;
|
|
} catch (_e) {
|
|
// Give views a harmless empty token so rendering never breaks
|
|
res.locals.csrfToken = '';
|
|
}
|
|
return next();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper around doubleCsrfProtection that tolerates corrupt cookies on
|
|
* safe HTTP methods (GET / HEAD / OPTIONS). On those methods the library
|
|
* should never block — but a malformed __csrf cookie can still make it
|
|
* throw. We catch that, wipe the cookie and let the request through.
|
|
*/
|
|
function safeCsrfProtection(req, res, next) {
|
|
doubleCsrfProtection(req, res, (err) => {
|
|
if (err && ['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
|
|
// Corrupt cookie on a safe method — clear and continue
|
|
res.clearCookie('__csrf', { path: '/' });
|
|
if (req.cookies) delete req.cookies['__csrf'];
|
|
return next();
|
|
}
|
|
// For state-changing methods (POST/PUT/DELETE/PATCH) propagate normally
|
|
return next(err);
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
csrfTokenProvider,
|
|
doubleCsrfProtection: safeCsrfProtection
|
|
};
|