diff --git a/src/middleware/route-ratelimit2.js b/src/middleware/route-ratelimit2.js index aaa9127..e484265 100644 --- a/src/middleware/route-ratelimit2.js +++ b/src/middleware/route-ratelimit2.js @@ -11,7 +11,7 @@ The rate limit handling is designed for these four use cases: - 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 Basic 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 @@ -25,10 +25,10 @@ const Redis = require('ioredis') const { RateLimiterRedis } = require('rate-limiter-flexible') // local libraries. -// const wlogger = require('../util/winston-logging') +const wlogger = require('../util/winston-logging') const config = require('../../config') -// let _this // Global pointer to instance of class, when 'this' context is lost. +let _this // Global pointer to instance of class, when 'this' context is lost. // Setup Redis to track rate limits for each user. const redisOptions = { @@ -45,14 +45,14 @@ const rateLimitOptions = { } // Constants -// const ANON_LIMITS = config.anonRateLimit -// const WHITELIST_RATE_LIMIT = config.whitelistRateLimit -// const WHITELIST_DOMAINS = config.whitelistDomains -// const INTERNAL_RATE_LIMIT = 1 +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 = this this.jwt = jwt this.rateLimiter = new RateLimiterRedis(rateLimitOptions) @@ -63,8 +63,8 @@ class RateLimits { // support this function. async applyRateLimits (req, res, next) { try { - // let userId - // let decoded = {} + let userId + const decoded = {} // Create a re*Q*.locals object if not passed in. // req.locals.proLimit will be true if the user is using Basic Authentication. @@ -83,8 +83,98 @@ class RateLimits { rateLimitTriggered: false } } - } catch (err) { + // Exit if the user has already authenticated with Basic Authentication. + if (req.locals.proLimit) { + return next() + } + + // Determine if the call is an external or internal API call. + const isInternal = this.checkInternalIp(req) + + // Determine if the call originates from another computer on the intranet. + const isWhitelistOrigin = this.isInWhitelist(req) + console.log('isWhitelistOrigin: ', isWhitelistOrigin) + + // Handle the use case of internally-generated requests. + if (isInternal) { + // Internal API calls should pass the authentication data in through the + // the usrObj in the body. + if (req.body && req.body.usrObj) { + // + if (req.body.usrObj.proLimit) { + // If this is an internal call that originated from a user using + // Basic Authentication, then skip rate-limits. + return next() + // + } else if (req.body.usrObj.jwtToken) { + // Internal call originated from a user using a JWT token. + console.log( + 'Internal call originated from a user using a JWT token' + ) + // + } else { + // Internal call originates from an anonymous user. + console.log('Internal call originates from an anonymous user') + } + } + } + } catch (err) { + wlogger.error('Error in route-ratelimit2.js/applyRateLimits(): ', err) + } + + next() + } + + // Returns a boolean if the origin of the request matches a domain in the + // whitelist. + isInWhitelist (req) { + try { + const retVal = false // Default value. + + // Retrieve the origin. + const origin = req.get('origin') + + // console.log(`WHITELIST_DOMAINS: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`) + + for (let i = 0; i < WHITELIST_DOMAINS.length; i++) { + const thisDomain = WHITELIST_DOMAINS[i] + + if (origin.toString().indexOf(thisDomain) > -1) { + return true + } + } + + return retVal + } catch (err) { + wlogger.error( + 'Error in route-ratelimit.js/isInWhitelist(). Returning false by default.' + ) + return false + } + } + + // Checks the request object to see if it's IP address matches an internal + // IP address. That means the call is an internal API call and should be + // treated differently than an external API call. + checkInternalIp (req) { + try { + // Default value + let isInternal = false + + const ip = req.ip + + if (ip.includes('127.0.0.1')) isInternal = true + + if (ip.includes('172.17.')) isInternal = true + + return isInternal + } catch (err) { + console.error( + 'Error in checkInternalIp(). Returning false be default. Err: ', + err + ) + return false } }