From 97e49180e3165cee2df80567cc49317ab693f2b5 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 7 Feb 2020 00:29:39 -0800 Subject: [PATCH] Need to fix slp tier as 40 --- src/middleware/route-ratelimit.js | 16 ++++- test/v3/rate-limits.js | 106 +++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/middleware/route-ratelimit.js b/src/middleware/route-ratelimit.js index 9373b86..bdfadf5 100644 --- a/src/middleware/route-ratelimit.js +++ b/src/middleware/route-ratelimit.js @@ -37,6 +37,7 @@ class RateLimits { constructor() { _this = this + this.jwt = jwt this.rateLimiter = new RateLimiterRedis(rateLimitOptions) } @@ -259,7 +260,11 @@ class RateLimits { const pemPublicKey = keyEncoder.encodePublic(publicKey, "raw", "pem") // Validate the JWT token. - decoded = jwt.verify(req.locals.jwtToken, pemPublicKey, jwtOptions) + decoded = _this.jwt.verify( + req.locals.jwtToken, + pemPublicKey, + jwtOptions + ) // console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`) userId = decoded.id @@ -289,7 +294,7 @@ class RateLimits { await _this.rateLimiter.consume(key, pointsToConsume) } catch (err) { - console.log(`err: `, err) + // console.log(`err: `, err) // Rate limited was triggered res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 @@ -336,9 +341,14 @@ class RateLimits { else retVal = 10 } + // Full node tier + else if (apiLevel >= 10) { + retVal = 1 + } + // Free tier, full node only. else { - retVal = 1 + retVal = 10 } } diff --git a/test/v3/rate-limits.js b/test/v3/rate-limits.js index 892d732..889972d 100644 --- a/test/v3/rate-limits.js +++ b/test/v3/rate-limits.js @@ -3,6 +3,7 @@ const chai = require("chai") const assert = chai.assert const nock = require("nock") // HTTP mocking +const sinon = require("sinon") // Used for debugging. const util = require("util") @@ -13,7 +14,7 @@ const { mockReq, mockRes, mockNext } = require("./mocks/express-mocks") // Libraries under test const RateLimits = require("../../src/middleware/route-ratelimit") -const rateLimits = new RateLimits() +let rateLimits = new RateLimits() let rateLimitMiddleware = rateLimits.routeRateLimit const controlRoute = require("../../src/routes/v3/full-node/control") @@ -26,6 +27,8 @@ let originalEnvVars // Used during transition from integration to unit tests. const jwt = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYWRlM2Y1NzM5ZTZjMGZmMDM0YjlhMSIsImlhdCI6MTU3MTY3NzQ1MCwiZXhwIjoxNTc0MjY5NDUwfQ.SSz7F7ETyBB3eoNG2VKCzPOhddtB-vrtmEoj7PxicrQ` describe("#route-ratelimits & jwt-auth", () => { + let sandbox + before(() => { // Save existing environment variables. originalEnvVars = { @@ -50,6 +53,12 @@ describe("#route-ratelimits & jwt-auth", () => { req.params = {} req.body = {} req.query = {} + + sandbox = sinon.createSandbox() + }) + + afterEach(() => { + sandbox.restore() }) after(() => { @@ -261,7 +270,7 @@ describe("#route-ratelimits & jwt-auth", () => { assert.equal(result, 30) }) - it("should return 1 point for free tier requesting full node access", () => { + it("should return 10 points for free tier requesting full node access", () => { const jwtInfo = { apiLevel: 10, resource: "blockchain", @@ -269,7 +278,7 @@ describe("#route-ratelimits & jwt-auth", () => { } const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 1) + assert.equal(result, 10) }) it("should return 10 points for free tier requesting indexer access", () => { @@ -375,6 +384,97 @@ describe("#route-ratelimits & jwt-auth", () => { // next() will be called if rate-limit is not triggered assert.equal(next.called, true) }) + + it("should trigger rate-limit handler if rate limits exceeds 5 request per minute", async () => { + req.baseUrl = "/v3" + req.path = "/control/getNetworkInfo" + req.url = req.path + req.method = "GET" + + for (let i = 0; i < 5; i++) { + next.reset() // reset the stubbed next() function. + + await rateLimits.newRateLimit(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 () => { + // Create a new instance of the rate limit so we start with zeroed tracking. + rateLimits = new RateLimits() + + req.baseUrl = "/v3" + req.path = "/control/getNetworkInfo" + req.url = req.path + req.method = "GET" + + req.locals.jwtToken = "some-token" + + const jwtInfo = { + apiLevel: 10, + id: "5e3a0415eb29a962da2708b4" + } + + // Mock the call to the jwt library. + sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo) + + for (let i = 0; i < 5; i++) { + next.reset() // reset the stubbed next() function. + + await rateLimits.newRateLimit(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 after 10 RPM", async () => { + // Create a new instance of the rate limit so we start with zeroed tracking. + rateLimits = new RateLimits() + + req.baseUrl = "/v3" + req.path = "/control/getNetworkInfo" + req.url = req.path + req.method = "GET" + + req.locals.jwtToken = "some-token" + + const jwtInfo = { + apiLevel: 10, + id: "5e3a0415eb29a962da2708b4" + } + + // Mock the call to the jwt library. + sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo) + + for (let i = 0; i < 12; i++) { + next.reset() // reset the stubbed next() function. + + await rateLimits.newRateLimit(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.` + ) + }) }) })