Restored original jwt token handling

This commit is contained in:
Chris Troutner
2020-03-27 10:21:09 -07:00
parent c7c80c1f4f
commit 140e2b1cc1
4 changed files with 83 additions and 256 deletions
+7
View File
@@ -0,0 +1,7 @@
/*
Common configuration settings.
*/
module.exports = {
apiTokenSecret: process.env.TOKENSECRET ? process.env.TOKENSECRET : 'secret-jwt-token'
}
-1
View File
@@ -41,7 +41,6 @@
"helmet": "^3.21.2",
"ioredis": "^4.14.1",
"jsonwebtoken": "^8.5.1",
"key-encoder": "^2.0.3",
"level": "^6.0.0",
"mkdirp": "^1.0.0",
"mocha": "^7.1.1",
+76 -76
View File
@@ -5,10 +5,9 @@ const RateLimit = require('express-rate-limit')
const axios = require('axios')
const wlogger = require('../util/winston-logging')
const config = require('../../config')
const jwt = require('jsonwebtoken')
const KeyEncoder = require('key-encoder').default
const keyEncoder = new KeyEncoder('secp256k1')
// Redis
const redisOptions = {
@@ -31,8 +30,8 @@ const rateLimitOptions = {
// This hard-coded value is temporary. It will be swapped out with an environment
// variable when moved to production.
const publicKey =
'03e6c358092a459f7da9420de770eef3e16cf3c9c54a3d3d14ac2d7f0b82af4d7d'
// const publicKey =
// '03e6c358092a459f7da9420de770eef3e16cf3c9c54a3d3d14ac2d7f0b82af4d7d'
// Set max requests per minute
const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
@@ -270,18 +269,19 @@ class RateLimits {
// Decode the JWT token if one exists.
if (req.locals.jwtToken) {
const jwtOptions = {
algorithms: ['ES256']
}
// const jwtOptions = {
// algorithms: ['ES256']
// }
const pemPublicKey = keyEncoder.encodePublic(publicKey, 'raw', 'pem')
// const pemPublicKey = keyEncoder.encodePublic(publicKey, 'raw', 'pem')
// Validate the JWT token.
decoded = _this.jwt.verify(
req.locals.jwtToken,
pemPublicKey,
jwtOptions
)
// decoded = _this.jwt.verify(
// req.locals.jwtToken,
// pemPublicKey,
// jwtOptions
// )
decoded = _this.jwt.verify(req.locals.jwtToken, config.apiTokenSecret)
// console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
userId = decoded.id
@@ -398,69 +398,69 @@ class RateLimits {
// potentially be used by Bitcoin.com.
// Rather than using apiLevel, the rateLimit is explicitly recorded in the
// JWT token.
async rateLimitSimple (req, res, next) {
try {
let userId
let decoded = {}
// Create a res.locals object if not passed in.
if (!req.locals) {
req.locals = {
// default values
jwtToken: '',
proLimit: false,
rateLimit: 3
}
}
// Decode the JWT token if one exists.
if (req.locals.jwtToken) {
const jwtOptions = {
algorithms: ['ES256']
}
const pemPublicKey = keyEncoder.encodePublic(publicKey, 'raw', 'pem')
// Validate the JWT token.
decoded = _this.jwt.verify(
req.locals.jwtToken,
pemPublicKey,
jwtOptions
)
// console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
userId = decoded.id
} else {
wlogger.debug('No JWT token found!')
}
// Code here for the rate limiter is adapted from this example:
// https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users
try {
// Key for Redis key/value pair.
const key = userId || req.ip
const pointsToConsume = _this.calcPoints2(decoded)
wlogger.debug(`User ${key} consuming ${pointsToConsume}.`)
await _this.rateLimiter.consume(key, pointsToConsume)
} catch (err) {
// console.log(`err: `, err)
// Rate limited was triggered
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Too many requests. Your limits are currently ${maxRequests} requests per minute. Increase rate limits at https://fullstack.cash`
})
}
} catch (err) {
wlogger.error('Error in route-ratelimit.js/rateLimitSimple(): ', err)
// throw err
}
next()
}
// async rateLimitSimple (req, res, next) {
// try {
// let userId
// let decoded = {}
//
// // Create a res.locals object if not passed in.
// if (!req.locals) {
// req.locals = {
// // default values
// jwtToken: '',
// proLimit: false,
// rateLimit: 3
// }
// }
//
// // Decode the JWT token if one exists.
// if (req.locals.jwtToken) {
// const jwtOptions = {
// algorithms: ['ES256']
// }
//
// const pemPublicKey = keyEncoder.encodePublic(publicKey, 'raw', 'pem')
//
// // Validate the JWT token.
// decoded = _this.jwt.verify(
// req.locals.jwtToken,
// pemPublicKey,
// jwtOptions
// )
// // console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
//
// userId = decoded.id
// } else {
// wlogger.debug('No JWT token found!')
// }
//
// // Code here for the rate limiter is adapted from this example:
// // https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users
// try {
// // Key for Redis key/value pair.
// const key = userId || req.ip
//
// const pointsToConsume = _this.calcPoints2(decoded)
//
// wlogger.debug(`User ${key} consuming ${pointsToConsume}.`)
//
// await _this.rateLimiter.consume(key, pointsToConsume)
// } catch (err) {
// // console.log(`err: `, err)
//
// // Rate limited was triggered
// res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
// return res.json({
// error: `Too many requests. Your limits are currently ${maxRequests} requests per minute. Increase rate limits at https://fullstack.cash`
// })
// }
// } catch (err) {
// wlogger.error('Error in route-ratelimit.js/rateLimitSimple(): ', err)
// // throw err
// }
//
// next()
// }
// Calculates the points consumed, based on the explicit rateLimit defined
// in the JWT token.
-179
View File
@@ -618,185 +618,6 @@ describe('#route-ratelimits & jwt-auth', () => {
)
})
})
describe('#rateLimitSimple', () => {
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.rateLimitSimple(req, res, next)
await rateLimits.rateLimitSimple(req, res, next)
// 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.rateLimitSimple(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 = {
rateLimit: 10,
id: '5e3a0415eb29a962da2708c1'
}
// 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.rateLimitSimple(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 = {
rateLimit: 10,
id: '5e3a0415eb29a962da2708c2'
}
// 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.rateLimitSimple(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 handler for pro-tier at 25 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 = {
rateLimit: 100,
id: '5e3a0415eb29a962da2708c3'
}
// Mock the call to the jwt library.
sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo)
for (let i = 0; i < 25; i++) {
next.reset() // reset the stubbed next() function.
await rateLimits.rateLimitSimple(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 still rate-limit at a higher RPM for pro-tier', 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 = {
rateLimit: 100,
id: '5e3a0415eb29a962da2708c4'
}
// Mock the call to the jwt library.
sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo)
for (let i = 0; i < 150; i++) {
next.reset() // reset the stubbed next() function.
await rateLimits.rateLimitSimple(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,
false,
'next should NOT be called if rate limit was triggered.'
)
})
})
})
// Generates a Basic authorization header.