// VULNERABLE: No rate limiting on critical endpoints
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
// Authentication endpoint without rate limiting
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Expensive bcrypt operation
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
res.json({ token, user: { id: user.id, email: user.email } });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
// Password reset without rate limiting
app.post('/api/auth/reset-password', async (req, res) => {
const { email } = req.body;
const user = await User.findOne({ email });
if (user) {
// Expensive email operation
await sendPasswordResetEmail(user.email);
}
// Always return success to prevent email enumeration
res.json({ message: 'If email exists, reset link sent' });
});
// File upload without limits
app.post('/api/files/upload', upload.single('file'), (req, res) => {
// Process file upload
const processedFile = processFile(req.file);
res.json({ fileId: processedFile.id });
});
// SECURE: Comprehensive rate limiting implementation
const express = require('express');
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
const redisClient = redis.createClient(process.env.REDIS_URL);
// Progressive delay for suspicious behavior
const speedLimiter = slowDown({
store: new RedisStore({
client: redisClient,
prefix: 'speed_limit:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
delayAfter: 5, // Allow 5 requests per window at full speed
delayMs: 500, // Add 500ms delay per request after delayAfter
maxDelayMs: 20000, // Maximum delay of 20 seconds
});
// Strict rate limiting for authentication
const authLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'auth_limit:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per window
message: {
error: 'Too many authentication attempts',
retryAfter: 900 // 15 minutes in seconds
},
standardHeaders: true,
keyGenerator: (req) => {
// Combine IP and email for more granular limiting
return `${req.ip}:${req.body.email || 'anonymous'}`;
},
skip: (req) => {
// Skip rate limiting for successful authentications
return req.skipRateLimit === true;
}
});
// Rate limiting for password reset
const resetLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'reset_limit:'
}),
windowMs: 60 * 60 * 1000, // 1 hour
max: 3, // 3 reset attempts per hour per email
keyGenerator: (req) => req.body.email,
message: { error: 'Too many password reset attempts' }
});
// File upload rate limiting
const uploadLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'upload_limit:'
}),
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 uploads per minute
message: { error: 'Upload rate limit exceeded' }
});
// Apply middleware
app.use('/api/auth', speedLimiter);
app.post('/api/auth/login', authLimiter, async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Skip rate limiting for successful authentication
req.skipRateLimit = true;
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
res.json({ token, user: { id: user.id, email: user.email } });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/api/auth/reset-password', resetLimiter, async (req, res) => {
const { email } = req.body;
const user = await User.findOne({ email });
if (user) {
await sendPasswordResetEmail(user.email);
}
res.json({ message: 'If email exists, reset link sent' });
});
app.post('/api/files/upload', uploadLimiter, upload.single('file'), (req, res) => {
const processedFile = processFile(req.file);
res.json({ fileId: processedFile.id });
});