fix(#19): Implement rate limiting for auth and write-heavy endpoints
Some checks are pending
Docker Test / test (push) Waiting to run

This commit is contained in:
OpenClaw 2026-03-06 23:55:29 +00:00
parent a0fc6fe236
commit 2b09cf05eb
3 changed files with 128 additions and 7 deletions

View 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);
});
});
});

View 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 };

View file

@ -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, () => {