diff --git a/src/middleware/auth.js b/src/middleware/auth.js index 97ae4df..682c23d 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -22,7 +22,6 @@ const passport = require("passport") const BasicStrategy = require("passport-http").BasicStrategy const AnonymousStrategy = require("passport-anonymous") const wlogger = require("../util/winston-logging") -const axios = require("axios") // Used for debugging and iterrogating JS objects. const util = require("util") diff --git a/src/middleware/route-ratelimit.js b/src/middleware/route-ratelimit.js index 70e2c20..a2d56ee 100644 --- a/src/middleware/route-ratelimit.js +++ b/src/middleware/route-ratelimit.js @@ -11,11 +11,12 @@ const express = require("express") const RateLimit = require("express-rate-limit") +const axios = require("axios") // Set max requests per minute const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS ? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) - : 30 + : 10 // Pro-tier rate limits are 10x the freemium limits. const PRO_RPM = 10 * maxRequests @@ -23,29 +24,38 @@ const PRO_RPM = 10 * maxRequests // Unique route mapped to its rate limit const uniqueRateLimits = {} -const routeRateLimit = function(req, res, next) { +const routeRateLimit = async function(req, res, next) { // Disable rate limiting if 0 passed from RATE_LIMIT_MAX_REQUESTS if (maxRequests === 0) return next() // Create a res.locals object if not passed in. if (!req.locals) req.locals = {} - if (req.locals.jwtToken) - console.log(`req.locals.jwtToken: ${req.locals.jwtToken}`) + // Warn if JWT_AUTH_SERVER env var is not set. + const authServer = process.env.JWT_AUTH_SERVER + if (!authServer || authServer === "") { + console.warn( + "JWT_AUTH_SERVER env var is not set. JWT tokens not being evaluated." + ) + } else { + // If a JWT token is passed in, validate it and enable pro-tier rate limits + // if it's valid. + if (req.locals.jwtToken) { + // console.log(`req.locals.jwtToken: ${req.locals.jwtToken}`) - // If no JWT token was provided, skip - if (req.payload) - console.log(`req.payload: ${JSON.stringify(req.payload, null, 2)}`) - // // Unlock the pro-tier rate limits if the user passed in a valid JWT token. - // if (!proRateLimits) { - // const user = await getUserFromJWT(req) - // - // // Enable pro-tier rate limits for this user. - // if(user) { - // console.log(`${user.email} (${user.id}) passed in valid JWT`) - // proRateLimits = true - // } - // } + const path = `${authServer}apitoken/isvalid/${req.locals.jwtToken}` + + let isValidJwt = await axios.get(path) + isValidJwt = isValidJwt.data + // console.log(`isValidJwt: ${JSON.stringify(isValidJwt, null, 2)}`) + + // Enable pro-tier rate limits if JWT if valid. + if (isValidJwt) { + // console.log(`JWT is valid. Enabling pro-tier rate limits.`) + req.locals.proLimit = true + } + } + } // Current route const rateLimitTier = req.locals.proLimit ? "PRO" : "BASIC" diff --git a/test/v3/integration/rate-limits.js b/test/v3/integration/rate-limits.js index 3a71efa..99ba139 100644 --- a/test/v3/integration/rate-limits.js +++ b/test/v3/integration/rate-limits.js @@ -29,124 +29,124 @@ describe("#rate limits", () => { const result = await axios(options) //console.log(`result.status: ${result.status}`) - //console.log(`result.data: ${util.inspect(result.data)}`) + // console.log(`result.data: ${util.inspect(result.data)}`) assert.equal(result.status, 200) assert.hasAnyKeys(result.data, ["version"]) }) - // it("should trigger rate-limit handler if rate limits exceeds 30 request per minute", async () => { - // try { - // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - // const options = { - // method: "GET", - // url: `${SERVER}control/getNetworkInfo` - // } - // - // const promises = [] - // for (let i = 0; i < 30; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // assert.equal(true, false, "Unexpected result!") - // } catch (err) { - // //console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal(err.response.status, 429) - // assert.include(err.response.data.error, "Too many requests") - // } - // }) + it("should trigger rate-limit handler if rate limits exceeds 30 request per minute", async () => { + try { + // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. + const options = { + method: "GET", + url: `${SERVER}control/getNetworkInfo` + } - // it("should not trigger rate-limit handler if correct pro-tier password is used", async () => { - // try { - // const username = "BITBOX" - // - // // Pro-tier is accessed by using the right password. - // const password = "BITBOX" - // //const password = "something" - // - // const combined = `${username}:${password}` - // const base64Credential = Buffer.from(combined).toString("base64") - // const readyCredential = `Basic ${base64Credential}` - // - // const options = { - // method: "GET", - // url: `${SERVER}control/getNetworkInfo`, - // headers: { Authorization: readyCredential } - // } - // - // const promises = [] - // for (let i = 0; i < 30; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // assert.equal(true, true, "Not throwing an error is a pass!") - // } catch (err) { - // // console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal( - // true, - // false, - // "This error handler should not have been triggered. Is the password correct?" - // ) - // } - // }) + const promises = [] + for (let i = 0; i < 30; i++) { + const promise = axios(options) + promises.push(promise) + } - // it("should trigger rate-limit handler if rate limits exceeds pro-tier limit", async () => { - // try { - // const username = "BITBOX" - // - // // Pro-tier is accessed by using the right password. - // const password = "BITBOX" - // //const password = "something" - // - // const combined = `${username}:${password}` - // const base64Credential = Buffer.from(combined).toString("base64") - // const readyCredential = `Basic ${base64Credential}` - // - // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - // const options = { - // method: "GET", - // url: `${SERVER}control/getNetworkInfo`, - // headers: { Authorization: readyCredential } - // } - // - // const promises = [] - // for (let i = 0; i < 80; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // assert.equal(true, false, "Unexpected result!") - // } catch (err) { - // //console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal(err.response.status, 429) - // assert.include(err.response.data.error, "Too many requests") - // } - // }) + await Promise.all(promises) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err.response: ${util.inspect(err.response)}`) + + assert.equal(err.response.status, 429) + assert.include(err.response.data.error, "Too many requests") + } + }) + + it("should not trigger rate-limit handler if correct pro-tier password is used", async () => { + try { + const username = "BITBOX" + + // Pro-tier is accessed by using the right password. + const password = "BITBOX" + //const password = "something" + + const combined = `${username}:${password}` + const base64Credential = Buffer.from(combined).toString("base64") + const readyCredential = `Basic ${base64Credential}` + + const options = { + method: "GET", + url: `${SERVER}control/getNetworkInfo`, + headers: { Authorization: readyCredential } + } + + const promises = [] + for (let i = 0; i < 30; i++) { + const promise = axios(options) + promises.push(promise) + } + + await Promise.all(promises) + + assert.equal(true, true, "Not throwing an error is a pass!") + } catch (err) { + // console.log(`err.response: ${util.inspect(err.response)}`) + + assert.equal( + true, + false, + "This error handler should not have been triggered. Is the password correct?" + ) + } + }) + + it("should trigger rate-limit handler if rate limits exceeds pro-tier limit", async () => { + try { + const username = "BITBOX" + + // Pro-tier is accessed by using the right password. + const password = "BITBOX" + //const password = "something" + + const combined = `${username}:${password}` + const base64Credential = Buffer.from(combined).toString("base64") + const readyCredential = `Basic ${base64Credential}` + + // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. + const options = { + method: "GET", + url: `${SERVER}control/getNetworkInfo`, + headers: { Authorization: readyCredential } + } + + const promises = [] + for (let i = 0; i < 80; i++) { + const promise = axios(options) + promises.push(promise) + } + + await Promise.all(promises) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err.response: ${util.inspect(err.response)}`) + + assert.equal(err.response.status, 429) + assert.include(err.response.data.error, "Too many requests") + } + }) it("should unlock pro-tier for a valid JWT token", async () => { try { // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. const options = { method: "GET", - url: `${SERVER}control/getNetworkInfo`, + url: `${SERVER}control/`, headers: { Authorization: `Token ${TEST_JWT}` } } const promises = [] - for (let i = 0; i < 1; i++) { + for (let i = 0; i < 60; i++) { const promise = axios(options) promises.push(promise) }