fix(local rate limits): Skipping basic auth and rate limits with an env var

This commit is contained in:
Chris Troutner
2021-03-07 10:01:17 -08:00
parent ff5554ab68
commit bb3d020646
3 changed files with 133 additions and 17 deletions
+18 -9
View File
@@ -96,17 +96,26 @@ app.use('/', logReqInfo)
const v4prefix = 'v4' const v4prefix = 'v4'
// Inspect the header for a JWT token. // START Rate Limits
app.use(`/${v4prefix}/`, jwtAuth.getTokenFromHeaders) // Allow users to turn off rate limits with an environment variable.
const USE_RATE_LIMITS = process.env.USE_RATE_LIMITS
// Instantiate the authorization middleware, used to implement pro-tier rate limiting. ? process.env.USE_RATE_LIMITS
// Handles Anonymous and Basic Authorization schemes used by passport.js : true
const auth = new AuthMW() const auth = new AuthMW()
app.use(`/${v4prefix}/`, auth.mw())
// Rate limit on all v4 routes if (USE_RATE_LIMITS) {
// Establish and enforce rate limits. // Inspect the header for a JWT token.
app.use(`/${v4prefix}/`, rateLimits.rateLimitByResource) app.use(`/${v4prefix}/`, jwtAuth.getTokenFromHeaders)
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
// Handles Anonymous and Basic Authorization schemes used by passport.js
app.use(`/${v4prefix}/`, auth.mw())
// Rate limit on all v4 routes
// Establish and enforce rate limits.
app.use(`/${v4prefix}/`, rateLimits.rateLimitByResource)
}
// END Rate Limits
// Connect v4 routes // Connect v4 routes
app.use(`/${v4prefix}/` + 'health-check', healthCheckV4) app.use(`/${v4prefix}/` + 'health-check', healthCheckV4)
+12 -8
View File
@@ -1,22 +1,25 @@
/* /*
CT 2/4/20 Note: This library handles anonymous and Basic auth. This library This library handles anonymous and Basic Authentication.
can be phased out with the chage to JWT tokens and the new rate-limit library.
Handle authorization for bypassing rate limits.
1) Default is 'Anonymous Authentication', which unlocks the freemimum tier by 1) Default is 'Anonymous Authentication', which unlocks the freemimum tier by
default. default.
2) Hard-coded 'Basic Authentication' is a token that does not expire and is 2) Hard-coded 'Basic Authentication' is a token that does not expire and is
provided to buisiness partners. provided for clients who run their own isolated infrastructure without rate
limits, but still need a way from preventing the random public from using
their API.
3) JWT-based 'Local Authentication' is used for normal users that pay to 3) JWT-based 'Local Authentication' is used for normal users that pay to
access the premium pro-tier services. access the premium pro-tier services.
This file uses the passport npm library to check the header of each REST API This file uses the passport npm library to check the header of each REST API
call for the prescence of a Basic authorization header: call for the prescence of a Basic Authentication header:
https://en.wikipedia.org/wiki/Basic_access_authentication https://en.wikipedia.org/wiki/Basic_access_authentication
If the header is found and validated, the req.locals.proLimit Boolean value If the header is found and validated, the req.locals.proLimit Boolean value
is set and passed to the route-ratelimits.ts middleware. is set and passed to the route-ratelimit.js middleware. route-ratelimit.js
is for fine-grain JWT-based rate limits. If req.locals.proLimit is set to
true, then those rate limits will be skipped.
*/ */
'use strict' 'use strict'
@@ -76,8 +79,9 @@ class AuthMW {
req.locals.proLimit = false req.locals.proLimit = false
// Evaluate the username and password and set the rate limit accordingly. // Evaluate the username and password and set the rate limit accordingly.
// if (username === "BITBOX" && password === PRO_PASS) {
if (username === 'fullstackcash') { if (username === 'fullstackcash') {
// Can set several different passwords in the environment variable.
// Loop through each one to see if one matches.
for (let i = 0; i < PRO_PASS.length; i++) { for (let i = 0; i < PRO_PASS.length; i++) {
const thisPass = PRO_PASS[i] const thisPass = PRO_PASS[i]
+103
View File
@@ -0,0 +1,103 @@
/*
This file will replace the original rate-limit.js file.
Sets the rate limits for the anonymous and paid tiers. Current rate limits:
- 1000 points in 60 seconds
- 10 points per call for paid tier (100 RPM)
- 50 points per call for anonymous tier (20 RPM)
The rate limit handling is designed for these four use cases:
- Users who want to buy a JWT token for 24 hour access.
- Users who want to buy different RPM tiers: 100, 250, 600
- Basic Authentication which should not have any rate limits applied.
The Basci Auth use cases is considered when determining internal rate limits.
The internal rate limits should not be applied to calls from those users.
A lot of attention has been paid to passing rate-limit information for the user
when they trigger an endpoint that makes a lot of internal API calls. Examples
are hydrateUtxos() and getPublicKey().
*/
// Public npm libraries.
const jwt = require('jsonwebtoken')
const Redis = require('ioredis')
const { RateLimiterRedis } = require('rate-limiter-flexible')
// local libraries.
// const wlogger = require('../util/winston-logging')
const config = require('../../config')
// let _this // Global pointer to instance of class, when 'this' context is lost.
// Setup Redis to track rate limits for each user.
const redisOptions = {
enableOfflineQueue: false,
port: process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379,
host: process.env.REDIS_HOST ? process.env.REDIS_HOST : '127.0.0.1'
}
console.log(`redisOptions: ${JSON.stringify(redisOptions, null, 2)}`)
const redisClient = new Redis(redisOptions)
const rateLimitOptions = {
storeClient: redisClient,
points: 1000, // Number of points
duration: 60 // Per minute (per 60 seconds)
}
// Constants
// const ANON_LIMITS = config.anonRateLimit
// const WHITELIST_RATE_LIMIT = config.whitelistRateLimit
// const WHITELIST_DOMAINS = config.whitelistDomains
// const INTERNAL_RATE_LIMIT = 1
class RateLimits {
constructor () {
// _this = this
this.jwt = jwt
this.rateLimiter = new RateLimiterRedis(rateLimitOptions)
this.config = config
}
// This is the main middleware funciton of this library. All other functions
// support this function.
async applyRateLimits (req, res, next) {
try {
// let userId
// let decoded = {}
// Create a re*Q*.locals object if not passed in.
// req.locals.proLimit will be true if the user is using Basic Authentication.
if (!req.locals) {
req.locals = {
// default values
jwtToken: '',
proLimit: false,
apiLevel: 0
}
}
// Create a re*S*.locals object if it does not exist.
if (!res.locals) {
res.locals = {
rateLimitTriggered: false
}
}
} catch (err) {
}
}
// Used to disconnect from the Redis DB.
// Called by unit tests so that node.js thread doesn't live forever.
closeRedis () {
redisClient.disconnect()
}
// Clear the redis database. Used by unit tests.
async wipeRedis () {
await redisClient.flushdb()
}
}
module.exports = RateLimits