diff --git a/src/middleware/route-ratelimit.js b/src/middleware/route-ratelimit.js index 961ea82..9373b86 100644 --- a/src/middleware/route-ratelimit.js +++ b/src/middleware/route-ratelimit.js @@ -235,7 +235,7 @@ class RateLimits { async newRateLimit(req, res, next) { try { let userId - let decoded + let decoded = {} // Create a res.locals object if not passed in. if (!req.locals) { @@ -275,16 +275,18 @@ class RateLimits { console.log(`resource: ${resource}`) let key = userId ? userId : req.ip - key = `${key}-${resource}` // const pointsToConsume = userId ? 1 : 30 decoded.resource = resource const pointsToConsume = _this.calcPoints(decoded) console.log( - `User ${userId} consuming ${pointsToConsume} point for resource ${resource}.` + `User ${key} consuming ${pointsToConsume} point for resource ${resource}.` ) + // Update the key so that rate limits track both the user and the resource. + key = `${key}-${resource}` + await _this.rateLimiter.consume(key, pointsToConsume) } catch (err) { console.log(`err: `, err) @@ -317,18 +319,21 @@ class RateLimits { const level20Routes = ["insight", "bitcore", "blockbook"] const level30Routes = ["slp"] + // console.log(`apiLevel: ${apiLevel}`) + // Only evaluate if user is using a JWT token. if (jwtInfo.id) { // SLP indexer routes if (level30Routes.includes(resource)) { if (apiLevel >= 30) retVal = 1 - else retVal = 30 + // else if (apiLevel >= 10) retVal = 10 + else retVal = 10 } // Normal indexer routes else if (level20Routes.includes(resource)) { if (apiLevel >= 20) retVal = 1 - else retVal = 30 + else retVal = 10 } // Free tier, full node only. @@ -353,7 +358,7 @@ class RateLimits { // what kind of variations will be seen in production. getResource(url) { try { - // console.log(`url: ${JSON.stringify(url, null, 2)}`) + console.log(`url: ${JSON.stringify(url, null, 2)}`) const splitUrl = url.split("/") const resource = splitUrl[1] diff --git a/test/v3/rate-limits.js b/test/v3/rate-limits.js index db237cb..892d732 100644 --- a/test/v3/rate-limits.js +++ b/test/v3/rate-limits.js @@ -252,6 +252,130 @@ describe("#route-ratelimits & jwt-auth", () => { assert.equal(result, "blockchain") }) }) + + describe("#calcPoints", () => { + it("should return 30 points for anonymous user", () => { + const result = rateLimits.calcPoints() + // console.log(`result: ${result}`) + + assert.equal(result, 30) + }) + + it("should return 1 point for free tier requesting full node access", () => { + const jwtInfo = { + apiLevel: 10, + resource: "blockchain", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 1) + }) + + it("should return 10 points for free tier requesting indexer access", () => { + const jwtInfo = { + apiLevel: 10, + resource: "blockbook", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 10) + }) + + it("should return 10 points for free tier requesting SLPDB access", () => { + const jwtInfo = { + apiLevel: 10, + resource: "slp", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 10) + }) + + it("should return 1 point for indexer tier requesting full node access", () => { + const jwtInfo = { + apiLevel: 20, + resource: "blockchain", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 1) + }) + + it("should return 1 points for indexer tier requesting indexer access", () => { + const jwtInfo = { + apiLevel: 20, + resource: "blockbook", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 1) + }) + + it("should return 10 points for indexer tier requesting SLPDB access", () => { + const jwtInfo = { + apiLevel: 20, + resource: "slp", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 10) + }) + + it("should return 1 point for SLP tier requesting full node access", () => { + const jwtInfo = { + apiLevel: 30, + resource: "blockchain", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 1) + }) + + it("should return 1 points for SLP tier requesting indexer access", () => { + const jwtInfo = { + apiLevel: 30, + resource: "blockbook", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 1) + }) + + it("should return 1 points for SLP tier requesting SLPDB access", () => { + const jwtInfo = { + apiLevel: 30, + resource: "slp", + id: "5e3a0415eb29a962da2708b4" + } + + const result = rateLimits.calcPoints(jwtInfo) + assert.equal(result, 1) + }) + }) + + describe("#newRateLimit", () => { + it("should pass through rate-limit middleware", async () => { + req.baseUrl = "/v3" + req.path = "/control/getNetworkInfo" + req.url = req.path + req.method = "GET" + + // Call the route twice to trigger the rate handling. + await rateLimits.newRateLimit(req, res, next) + await rateLimits.newRateLimit(req, res, next) + + // next() will be called if rate-limit is not triggered + assert.equal(next.called, true) + }) + }) }) // Generates a Basic authorization header.