mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-21 16:52:04 -07:00
feat(rate-limit tiers): Created finer-resolution rate limits + unit tests
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
This file controls the request-per-minute (RPM) rate limits.
|
This file controls the request-per-minute (RPM) rate limits.
|
||||||
|
|
||||||
It is assumed that this middleware is run AFTER the auth.js middleware which
|
It is assumed that this middleware is run AFTER the jwt-auth.js and auth.js
|
||||||
checks for Basic auth. If the user adds the correct Basic auth to the header
|
middleware.
|
||||||
of their API request, they will get pro-tier rate limits. By default, the
|
|
||||||
freemium rate limits apply.
|
Current rate limiting rules in requests-per-minute:
|
||||||
|
- anonymous access: 3
|
||||||
|
- free access: 10
|
||||||
|
- any paid tier: 100
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict"
|
"use strict"
|
||||||
@@ -16,10 +19,10 @@ const axios = require("axios")
|
|||||||
// Set max requests per minute
|
// Set max requests per minute
|
||||||
const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
|
const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
|
||||||
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
|
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
|
||||||
: 10
|
: 3
|
||||||
|
|
||||||
// Pro-tier rate limits are 10x the freemium limits.
|
// Pro-tier rate limits are 10x the freemium limits.
|
||||||
const PRO_RPM = 10 * maxRequests
|
// const PRO_RPM = 10 * maxRequests
|
||||||
|
|
||||||
// Unique route mapped to its rate limit
|
// Unique route mapped to its rate limit
|
||||||
const uniqueRateLimits = {}
|
const uniqueRateLimits = {}
|
||||||
@@ -87,6 +90,9 @@ const routeRateLimit = async function(req, res, next) {
|
|||||||
// TODO: replace the console.logs with calls to our logging system.
|
// TODO: replace the console.logs with calls to our logging system.
|
||||||
//console.log(`applying pro-rate limits`)
|
//console.log(`applying pro-rate limits`)
|
||||||
|
|
||||||
|
let PRO_RPM = 10 // Default value for free tier
|
||||||
|
if (req.locals.apiLevel > 0) PRO_RPM = 100 // RPM for paid tiers.
|
||||||
|
|
||||||
// Create new RateLimit if none exists for this route
|
// Create new RateLimit if none exists for this route
|
||||||
if (!uniqueRateLimits[route]) {
|
if (!uniqueRateLimits[route]) {
|
||||||
uniqueRateLimits[route] = new RateLimit({
|
uniqueRateLimits[route] = new RateLimit({
|
||||||
|
|||||||
+60
-13
@@ -62,12 +62,67 @@ describe("#route-ratelimits", () => {
|
|||||||
assert.equal(next.called, true)
|
assert.equal(next.called, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should trigger rate-limit handler if rate limits exceeds 25 request per minute", async () => {
|
it("should trigger rate-limit handler if rate limits exceeds 5 request per minute", async () => {
|
||||||
req.baseUrl = "/v3"
|
req.baseUrl = "/v3"
|
||||||
req.path = "/control/getNetworkInfo"
|
req.path = "/control/getNetworkInfo"
|
||||||
req.method = "GET"
|
req.method = "GET"
|
||||||
|
|
||||||
for (let i = 0; i < 35; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await routeRateLimit(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
false,
|
||||||
|
`next should not be called if rate limit was triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should NOT trigger rate-limit for free-tier at 5 RPM", async () => {
|
||||||
|
// Clear the require cache before running this test.
|
||||||
|
delete require.cache[
|
||||||
|
require.resolve("../../src/middleware/route-ratelimit")
|
||||||
|
]
|
||||||
|
rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
||||||
|
routeRateLimit = rateLimitMiddleware.routeRateLimit
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.proLimit = true
|
||||||
|
req.locals.apiLevel = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await routeRateLimit(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`req.locals after test: ${util.inspect(req.locals)}`)
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
true,
|
||||||
|
`next should be called if rate limit was not triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trigger rate-limit for free tier 10 RPM", async () => {
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.proLimit = true
|
||||||
|
req.locals.apiLevel = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
next.reset() // reset the stubbed next() function.
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
await routeRateLimit(req, res, next)
|
await routeRateLimit(req, res, next)
|
||||||
@@ -95,11 +150,7 @@ describe("#route-ratelimits", () => {
|
|||||||
req.method = "GET"
|
req.method = "GET"
|
||||||
|
|
||||||
req.locals.proLimit = true
|
req.locals.proLimit = true
|
||||||
|
req.locals.apiLevel = 10
|
||||||
//console.log(`req.locals before test: ${util.inspect(req.locals)}`)
|
|
||||||
|
|
||||||
// Prepare the authorization header
|
|
||||||
//req.headers.authorization = generateAuthHeader("BITBOX")
|
|
||||||
|
|
||||||
for (let i = 0; i < 25; i++) {
|
for (let i = 0; i < 25; i++) {
|
||||||
next.reset() // reset the stubbed next() function.
|
next.reset() // reset the stubbed next() function.
|
||||||
@@ -131,13 +182,9 @@ describe("#route-ratelimits", () => {
|
|||||||
req.method = "GET"
|
req.method = "GET"
|
||||||
|
|
||||||
req.locals.proLimit = true
|
req.locals.proLimit = true
|
||||||
|
req.locals.apiLevel = 10
|
||||||
|
|
||||||
//console.log(`req.locals before test: ${util.inspect(req.locals)}`)
|
for (let i = 0; i < 150; i++) {
|
||||||
|
|
||||||
// Prepare the authorization header
|
|
||||||
//req.headers.authorization = generateAuthHeader("BITBOX")
|
|
||||||
|
|
||||||
for (let i = 0; i < 400; i++) {
|
|
||||||
next.reset() // reset the stubbed next() function.
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
await routeRateLimit(req, res, next)
|
await routeRateLimit(req, res, next)
|
||||||
|
|||||||
Reference in New Issue
Block a user