Express Throttle
import redis from '../services/redis';
import * as crypto from 'crypto';
// Cookie interface
interface Ithrottle {
id: string;
requests: number[];
ip: string;
}
// Cookie based middleware
export default async ({ip, cookies}, res, next): Promise<void> => {
let throttle: Ithrottle = cookies._throttle;
// If request cookie does not exists reject request
if (!throttle) {
res.status(400);
res.send('Unauthorised access.');
}
// If cookie id does not exists assume new user
if (!throttle.id) {
throttle.id = `throttle_${crypto.randomBytes(20).toString('hex')}`;
throttle.requests = [];
throttle.ip = ip;
}
// Check redis for existing session
let session = redis.hgetall(throttle.id);
//If no requests have been made store session
if (!session) {
redis.hmset(throttle.id, 'id', throttle.id, 'requests', [], 'ip', ip);
session = redis.hgetall(throttle.id);
}
// Get existing requests from session
const requests = session.requests;
// If cookie request length and session length don't match reject request
if (throttle.requests.length !== requests.length) {
res.status(400);
res.send('Unauthorised access.');
}
// Add current request to session
requests.push((new Date()).getTime());
redis.hset(throttle.id, 'requests', requests);
// Store cookie in response to user
res.cookie.throttle = {id: throttle.id, requests, ip};
// Reject request if user has reaches 50 requests in 10 seconds
if (requests.length >= 50) {
res.status(400);
res.send('Unauthorised access.');
}
// If number of requests exceeds 5 in the last 10 seconds start throttling
if (requests.length >= 5) {
setTimeout(() => {
next();
}, (requests.length - 5) * 1000);
}
// Expire session after 10 seconds
redis.expire(throttle.id, 10);
// Move to next step in express
next();
};