|
| 1 | +// @flow |
| 2 | +const debug = require('debug')('shared:middlewares:ratelimiter'); |
| 3 | +import requestIp from 'request-ip'; |
| 4 | +import ms from 'ms'; |
| 5 | +import Limiter from 'ratelimiter'; |
| 6 | +import createRedis from '../bull/create-redis'; |
| 7 | +import Raven from 'shared/raven'; |
| 8 | + |
| 9 | +const server = process.env.SENTRY_NAME || 'unnamed'; |
| 10 | +const redis = createRedis({ |
| 11 | + keyPrefix: 'request-rate-limit:', |
| 12 | +}); |
| 13 | + |
| 14 | +const rateLimiter = ({ max, duration }: { max: number, duration: string }) => { |
| 15 | + return ( |
| 16 | + req: express$Request, |
| 17 | + res: express$Response, |
| 18 | + next: express$NextFunction |
| 19 | + ) => { |
| 20 | + // if user is logged in than use their id, otherwise use their ip address |
| 21 | + const id = |
| 22 | + req.user && req.user.id ? req.user.id : requestIp.getClientIp(req); |
| 23 | + const limiter = new Limiter({ |
| 24 | + // $FlowIssue |
| 25 | + id: `${server}:${id}`, |
| 26 | + db: redis, |
| 27 | + max, |
| 28 | + duration: ms(duration), |
| 29 | + }); |
| 30 | + |
| 31 | + limiter.get(function(err, limit) { |
| 32 | + if (err) return next(err); |
| 33 | + |
| 34 | + const remaining = limit.remaining - 1; |
| 35 | + res.set('X-RateLimit-Limit', String(limit.total)); |
| 36 | + res.set('X-RateLimit-Remaining', String(remaining)); |
| 37 | + res.set('X-RateLimit-Reset', String(limit.reset)); |
| 38 | + |
| 39 | + const after = (limit.reset - Date.now() / 1000) | 0; |
| 40 | + const remainingTime = ms(after * 1000, { long: true }); |
| 41 | + debug( |
| 42 | + '%s of %s requests remaining in the next %s, userId: %s', |
| 43 | + remaining, |
| 44 | + limit.total, |
| 45 | + remainingTime, |
| 46 | + id |
| 47 | + ); |
| 48 | + if (limit.remaining) return next(); |
| 49 | + |
| 50 | + const delta = (limit.reset * 1000 - Date.now()) | 0; |
| 51 | + res.set('Retry-After', String(after)); |
| 52 | + res |
| 53 | + .status(429) |
| 54 | + .send('Rate limit exceeded, retry in ' + ms(delta, { long: true })); |
| 55 | + }); |
| 56 | + }; |
| 57 | +}; |
| 58 | + |
| 59 | +export default rateLimiter; |
0 commit comments