Skip to content

Commit 31edc83

Browse files
committed
security fix #21
1 parent 6089278 commit 31edc83

1 file changed

Lines changed: 40 additions & 0 deletions

File tree

server/server.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import rateLimit from 'express-rate-limit';
1818
import { z } from 'zod';
1919
import { timingSafeEqual } from 'crypto';
2020
import cookieParser from 'cookie-parser';
21+
import lusca from 'lusca';
2122

2223
const __filename = fileURLToPath(import.meta.url);
2324
const __dirname = dirname(__filename);
@@ -71,6 +72,32 @@ app.use(express.urlencoded({ limit: '10mb', extended: true }));
7172
app.use(express.static(join(__dirname, '../dist')));
7273
app.use(cookieParser(COOKIE_SECRET));
7374

75+
// CSRF Protection
76+
app.use(lusca.csrf({
77+
cookie: {
78+
name: '_csrf',
79+
secure: process.env.NODE_ENV === 'production',
80+
httpOnly: true,
81+
sameSite: 'strict'
82+
},
83+
value: (req) => {
84+
// Get CSRF token from header, body, or query string
85+
return req.headers['x-csrf-token'] ||
86+
req.body && req.body._csrf ||
87+
req.query && req.query._csrf;
88+
}
89+
}));
90+
91+
// Add CSRF token to response for SPA
92+
app.use((req, res, next) => {
93+
res.cookie('XSRF-TOKEN', req.csrfToken(), {
94+
secure: process.env.NODE_ENV === 'production',
95+
sameSite: 'strict',
96+
path: '/'
97+
});
98+
next();
99+
});
100+
74101
// Rate limiting
75102
const apiLimiter = rateLimit({
76103
windowMs: 15 * 60 * 1000,
@@ -119,6 +146,19 @@ app.use('/api', apiLimiter);
119146
// Initialize database
120147
await initializeDatabase();
121148

149+
// Add CSRF token endpoint for SPA
150+
app.get('/api/csrf-token', (req, res) => {
151+
res.json({ csrfToken: req.csrfToken() });
152+
});
153+
154+
// Apply CSRF protection to all non-GET/HEAD/OPTIONS requests
155+
app.use((req, res, next) => {
156+
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
157+
return next();
158+
}
159+
lusca.csrf()(req, res, next);
160+
});
161+
122162
// Auth Routes
123163
app.get('/api/auth/setup-status', authLimiter, async (req, res) => {
124164
try {

0 commit comments

Comments
 (0)