-
Notifications
You must be signed in to change notification settings - Fork 230
Expand file tree
/
Copy pathrateLimiter.ts
More file actions
109 lines (97 loc) · 3.9 KB
/
rateLimiter.ts
File metadata and controls
109 lines (97 loc) · 3.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import rateLimit, { RateLimitRequestHandler } from 'express-rate-limit'
import { searchIP } from 'range_check'
import { RateLimiterConfig } from '../types'
import { CouponService } from '../CouponService/couponService'
export class RateLimiter {
PATH: string
type: 'ip' | 'wallet' | 'global' | 'common_token_disbursal'
constructor(
app: any,
configs: RateLimiterConfig[],
type: 'ip' | 'wallet' | 'global' | 'common_token_disbursal',
couponService: CouponService,
keyGenerator?: any,
) {
if (configs.length === 0) {
this.PATH = '/api/sendToken'
this.type = 'common_token_disbursal'
return
}
this.PATH = configs[0].RATELIMIT.PATH || '/api/sendToken'
this.type = type
let rateLimiters: any = new Map()
configs.forEach((config: any) => {
const { RATELIMIT } = config
let RL_CONFIG = {
MAX_LIMIT: RATELIMIT.MAX_LIMIT,
WINDOW_SIZE: RATELIMIT.WINDOW_SIZE,
SKIP_FAILED_REQUESTS: RATELIMIT.SKIP_FAILED_REQUESTS || true,
}
rateLimiters.set(config.ID, this.getLimiter(RL_CONFIG, config.ID, type, keyGenerator))
})
if(configs[0]?.RATELIMIT?.REVERSE_PROXIES) {
app.set('trust proxy', configs[0]?.RATELIMIT?.REVERSE_PROXIES)
}
app.use(this.PATH, (req: any, res: any, next: any) => {
// skip rate limit based on coupon - coupon limit verification is done in the request handler
const couponId = req.body?.couponId
if (couponId) {
const coupon = couponService.getCoupon(couponId)
if (this.type === 'ip' && coupon && coupon.skipIpRateLimit) {
return next()
} else if (this.type === 'wallet' && coupon && coupon.skipWalletRateLimit) {
return next()
} else if (this.type === 'common_token_disbursal' && coupon && coupon.skipCommonTokenDisbursalRateLimit) {
return next()
}
}
if(this.PATH == '/api/sendToken' && req.body.chain) {
const rateLimiter = rateLimiters.get(req.body.erc20 ? req.body.erc20 : req.body.chain)
if(rateLimiter) {
return rateLimiter(req, res, next)
}
next()
} else {
return rateLimiters.get(configs[0].ID)(req, res, next)
}
})
}
getLimiter(
config: any,
faucetConfigId: string,
type: 'ip' | 'wallet' | 'global' | 'common_token_disbursal',
keyGenerator?: any,
): RateLimitRequestHandler {
const limiter = rateLimit({
windowMs: config.WINDOW_SIZE * 60 * 1000,
max: config.MAX_LIMIT,
standardHeaders: true,
legacyHeaders: false,
skipFailedRequests: config.SKIP_FAILED_REQUESTS,
handler: (req, res, next, options) => {
if(type === 'common_token_disbursal') {
console.log(JSON.stringify({
date: new Date(),
type: "CommonTokenDisbursalRateLimit",
faucetConfigId,
ip: req.headers["cf-connecting-ip"] || req.ip
}))
}
res.status(options.statusCode).send({
message: `Too many requests. Please try again after ${config.WINDOW_SIZE} minutes`
})
},
keyGenerator: keyGenerator ? keyGenerator : (req, res) => {
const ip = this.getIP(req)
if(ip != null) {
return ip
}
}
})
return limiter
}
getIP(req: any) {
const ip = req.headers['cf-connecting-ip'] || req.ip
return searchIP(ip)
}
}