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 contactRoutes from './routes/contacts.js';
|
||||||
import profileRoutes from './routes/profile.js';
|
import profileRoutes from './routes/profile.js';
|
||||||
import logger from './middleware/logger.js';
|
import logger from './middleware/logger.js';
|
||||||
|
const { rateLimit, authRateLimit } = require('./middleware/rateLimit.js');
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
|
@ -33,13 +34,16 @@ app.get('/metrics', (_req, res) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/auth', authRoutes);
|
// Rate limiting für Auth-Endpunkte
|
||||||
app.use('/requests', helpRequestRoutes);
|
app.use('/auth', authRateLimit(), authRoutes);
|
||||||
app.use('/offers', offerRoutes);
|
|
||||||
app.use('/reviews', reviewRoutes);
|
// Rate limiting für write-heavy Endpunkte
|
||||||
app.use('/addresses', addressRoutes);
|
app.use('/requests', rateLimit({ max: 50 }), helpRequestRoutes);
|
||||||
app.use('/contacts', contactRoutes);
|
app.use('/offers', rateLimit({ max: 50 }), offerRoutes);
|
||||||
app.use('/profile', profileRoutes);
|
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);
|
const port = Number(process.env.PORT || 3000);
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue