fix: exempt public unauthenticated endpoints from CSRF protection
The lusca CSRF implementation was breaking login and registration because the frontend doesn't fetch or send CSRF tokens. This is a structural issue that requires frontend implementation. As a pragmatic fix, this commit exempts public unauthenticated endpoints from CSRF protection: - /api/login, /api/register, /api/verify-email - /api/version, /api/registration-status, /api/health - /api/oidc/* (all OIDC authentication endpoints) - /api/feature-flags Authenticated endpoints still require CSRF tokens via lusca. Also updates csrf.js to use lusca's token generation mechanism, making it compatible with the global lusca CSRF middleware. TODO: Implement proper CSRF token handling in the frontend for enhanced security on public endpoints.
This commit is contained in:
parent
caffea977c
commit
a89f2b72d9
2 changed files with 44 additions and 10 deletions
|
|
@ -97,10 +97,28 @@ const lusca = require('lusca');
|
|||
|
||||
// Pre-check middleware to exempt test/Bearer requests before lusca runs
|
||||
app.use((req, res, next) => {
|
||||
// Public unauthenticated endpoints that should bypass CSRF
|
||||
// These are routes that don't require authentication and are used during login/registration
|
||||
const publicPaths = [
|
||||
'/api/login',
|
||||
'/api/register',
|
||||
'/api/verify-email',
|
||||
'/api/version',
|
||||
'/api/registration-status',
|
||||
'/api/health',
|
||||
];
|
||||
|
||||
const isPublicPath = publicPaths.some((path) => req.path === path);
|
||||
const isOidcPath = req.path.startsWith('/api/oidc/');
|
||||
const isFeatureFlagsPath = req.path.startsWith('/api/feature-flags');
|
||||
|
||||
// Mark exempt requests so lusca wrapper can skip them
|
||||
if (
|
||||
process.env.NODE_ENV === 'test' ||
|
||||
req.headers.authorization?.startsWith('Bearer ')
|
||||
req.headers.authorization?.startsWith('Bearer ') ||
|
||||
isPublicPath ||
|
||||
isOidcPath ||
|
||||
isFeatureFlagsPath
|
||||
) {
|
||||
req._csrfExempt = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
const csurf = require('@dr.pogodin/csurf');
|
||||
const lusca = require('lusca');
|
||||
|
||||
const csrfMiddleware = csurf({
|
||||
cookie: false,
|
||||
value: (req) => {
|
||||
return req.headers['x-csrf-token'] || req.body?._csrf;
|
||||
},
|
||||
});
|
||||
const csrfMiddleware = (req, res, next) => {
|
||||
if (!req.session) {
|
||||
return res.status(500).json({ error: 'Session not initialized' });
|
||||
}
|
||||
|
||||
if (!req.session._csrf) {
|
||||
req.session._csrf = require('crypto').randomBytes(16).toString('hex');
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const csrfProtection = (req, res, next) => {
|
||||
if (
|
||||
|
|
@ -16,11 +21,22 @@ const csrfProtection = (req, res, next) => {
|
|||
return next();
|
||||
}
|
||||
|
||||
return csrfMiddleware(req, res, next);
|
||||
return lusca.csrf({
|
||||
header: 'x-csrf-token',
|
||||
cookie: false,
|
||||
})(req, res, next);
|
||||
};
|
||||
|
||||
const generateToken = (req) => {
|
||||
return req.csrfToken ? req.csrfToken() : '';
|
||||
if (!req.session) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!req.session._csrf) {
|
||||
req.session._csrf = require('crypto').randomBytes(16).toString('hex');
|
||||
}
|
||||
|
||||
return req.session._csrf;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue