fix(#19): Implement rate limiting for auth and write-heavy endpoints
Some checks are pending
Docker Test / test (push) Waiting to run
Some checks are pending
Docker Test / test (push) Waiting to run
This commit is contained in:
parent
a0fc6fe236
commit
2b09cf05eb
3 changed files with 128 additions and 7 deletions
65
backend/src/__tests__/rateLimit.test.js
Normal file
65
backend/src/__tests__/rateLimit.test.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { rateLimit, authRateLimit } from '../middleware/rateLimit.js';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
describe('Rate Limit Middleware', () => {
|
||||
let app;
|
||||
|
||||
beforeEach(() => {
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
});
|
||||
|
||||
it('should allow requests within limit', (done) => {
|
||||
const middleware = rateLimit({ max: 2, windowMs: 1000 });
|
||||
|
||||
app.get('/test', middleware, (req, res) => {
|
||||
res.status(200).json({ message: 'OK' });
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/test')
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should block requests exceeding limit', (done) => {
|
||||
const middleware = rateLimit({ max: 1, windowMs: 1000 });
|
||||
|
||||
app.get('/test', middleware, (req, res) => {
|
||||
res.status(200).json({ message: 'OK' });
|
||||
});
|
||||
|
||||
// Erster Request sollte erfolgreich sein
|
||||
request(app)
|
||||
.get('/test')
|
||||
.expect(200)
|
||||
.end(() => {
|
||||
// Zweiter Request sollte blockiert werden
|
||||
request(app)
|
||||
.get('/test')
|
||||
.expect(429)
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply auth rate limiting correctly', (done) => {
|
||||
const middleware = authRateLimit({ max: 1, windowMs: 1000 });
|
||||
|
||||
app.get('/auth-test', middleware, (req, res) => {
|
||||
res.status(200).json({ message: 'OK' });
|
||||
});
|
||||
|
||||
// Erster Request sollte erfolgreich sein
|
||||
request(app)
|
||||
.get('/auth-test')
|
||||
.expect(200)
|
||||
.end(() => {
|
||||
// Zweiter Request sollte blockiert werden
|
||||
request(app)
|
||||
.get('/auth-test')
|
||||
.expect(429)
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
52
backend/src/middleware/rateLimit.js
Normal file
52
backend/src/middleware/rateLimit.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Einfache Rate-Limiting-Middleware ohne externe Abhängigkeiten
|
||||
|
||||
// Konfigurierbare Limits über Umgebungsvariablen
|
||||
const DEFAULT_WINDOW_MS = parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000; // 15 Minuten
|
||||
const DEFAULT_MAX_REQUESTS = parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100;
|
||||
const DEFAULT_AUTH_WINDOW_MS = parseInt(process.env.RATE_LIMIT_AUTH_WINDOW_MS) || 5 * 60 * 1000; // 5 Minuten
|
||||
const DEFAULT_AUTH_MAX_REQUESTS = parseInt(process.env.RATE_LIMIT_AUTH_MAX_REQUESTS) || 5;
|
||||
|
||||
// Speicher für IP-Adressen und Request-Zähler
|
||||
const rateLimits = new Map();
|
||||
|
||||
const rateLimit = (options = {}) => {
|
||||
const windowMs = options.windowMs || DEFAULT_WINDOW_MS;
|
||||
const maxRequests = options.max || DEFAULT_MAX_REQUESTS;
|
||||
|
||||
return (req, res, next) => {
|
||||
const key = req.ip; // IP-Adresse als Schlüssel
|
||||
const now = Date.now();
|
||||
|
||||
if (!rateLimits.has(key)) {
|
||||
rateLimits.set(key, { count: 1, windowStart: now });
|
||||
} else {
|
||||
const data = rateLimits.get(key);
|
||||
if (now - data.windowStart > windowMs) {
|
||||
// Fenster abgelaufen, neues Fenster starten
|
||||
rateLimits.set(key, { count: 1, windowStart: now });
|
||||
} else {
|
||||
// Prüfen, ob Limit erreicht wurde
|
||||
if (data.count >= maxRequests) {
|
||||
return res.status(429).json({
|
||||
error: 'Too many requests',
|
||||
retryAfter: Math.ceil((windowMs - (now - data.windowStart)) / 1000)
|
||||
});
|
||||
}
|
||||
// Zähler erhöhen
|
||||
rateLimits.set(key, { count: data.count + 1, windowStart: data.windowStart });
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Spezielle Middleware für Auth-Endpunkte mit kürzerem Fenster und niedrigerem Limit
|
||||
const authRateLimit = (options = {}) => {
|
||||
const windowMs = options.windowMs || DEFAULT_AUTH_WINDOW_MS;
|
||||
const maxRequests = options.max || DEFAULT_AUTH_MAX_REQUESTS;
|
||||
|
||||
return rateLimit({ windowMs, max: maxRequests });
|
||||
};
|
||||
|
||||
module.exports = { rateLimit, authRateLimit };
|
||||
|
|
@ -8,6 +8,7 @@ import addressRoutes from './routes/addresses.js';
|
|||
import contactRoutes from './routes/contacts.js';
|
||||
import profileRoutes from './routes/profile.js';
|
||||
import logger from './middleware/logger.js';
|
||||
const { rateLimit, authRateLimit } = require('./middleware/rateLimit.js');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
|
@ -33,13 +34,16 @@ app.get('/metrics', (_req, res) => {
|
|||
});
|
||||
});
|
||||
|
||||
app.use('/auth', authRoutes);
|
||||
app.use('/requests', helpRequestRoutes);
|
||||
app.use('/offers', offerRoutes);
|
||||
app.use('/reviews', reviewRoutes);
|
||||
app.use('/addresses', addressRoutes);
|
||||
app.use('/contacts', contactRoutes);
|
||||
app.use('/profile', profileRoutes);
|
||||
// Rate limiting für Auth-Endpunkte
|
||||
app.use('/auth', authRateLimit(), authRoutes);
|
||||
|
||||
// Rate limiting für write-heavy Endpunkte
|
||||
app.use('/requests', rateLimit({ max: 50 }), helpRequestRoutes);
|
||||
app.use('/offers', rateLimit({ max: 50 }), offerRoutes);
|
||||
app.use('/reviews', rateLimit({ max: 50 }), reviewRoutes);
|
||||
app.use('/addresses', rateLimit({ max: 50 }), addressRoutes);
|
||||
app.use('/contacts', rateLimit({ max: 50 }), contactRoutes);
|
||||
app.use('/profile', rateLimit({ max: 50 }), profileRoutes);
|
||||
|
||||
const port = Number(process.env.PORT || 3000);
|
||||
app.listen(port, () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue