diff --git a/package.json b/package.json index 4eec6e2..717fded 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:integration:nft": "mocha --timeout 25000 -g '#nft' test/v4/integration/nft.js", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/", - "docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs", + "docs": "./node_modules/.bin/apidoc -i src/routes/v5 -o docs", "test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha -g '#utxosBulk' --exit --timeout 30000 test/v5/", "test:temp2": "export NETWORK=mainnet && mocha -g '#utxosBulk' --exit --timeout 30000 test/v5/" }, @@ -86,6 +86,6 @@ }, "apidoc": { "title": "bch-api", - "url": "https://api.fullstack.cash/v4" + "url": "https://api.fullstack.cash/v5" } } diff --git a/src/app.js b/src/app.js index a87dff3..2ca0dda 100644 --- a/src/app.js +++ b/src/app.js @@ -53,6 +53,7 @@ const xpubV5 = require('./routes/v5/xpub') const ElectrumXV5 = require('./routes/v5/electrumx') const EncryptionV5 = require('./routes/v5/encryption') const PriceV5 = require('./routes/v5/price') +const JWTV5 = require('./routes/v5/jwt') // const Ninsight = require('./routes/v5/ninsight') require('dotenv').config() @@ -80,6 +81,7 @@ const encryptionv5 = new EncryptionV5() const pricev5 = new PriceV5() const utilV5 = new UtilV5({ electrumx: electrumxv5 }) const dsproofV5 = new DSProofV5() +const jwtV5 = new JWTV5() const app = express() app.locals.env = process.env @@ -189,6 +191,7 @@ app.use(`/${v5prefix}/` + 'encryption', encryptionv5.router) app.use(`/${v5prefix}/` + 'price', pricev5.router) app.use(`/${v5prefix}/` + 'util', utilV5.router) app.use(`/${v5prefix}/` + 'dsproof', dsproofV5.router) +app.use(`/${v5prefix}/` + 'jwt', jwtV5.router) // const ninsight = new Ninsight() app.use(`/${v5prefix}/` + 'ninsight', ninsight.router) diff --git a/src/routes/v5/full-node/control.js b/src/routes/v5/full-node/control.js index f0b0468..692fad0 100644 --- a/src/routes/v5/full-node/control.js +++ b/src/routes/v5/full-node/control.js @@ -75,42 +75,6 @@ class Control { return _this.errorHandler(error, res) } } - // router.get('/getMemoryInfo', (req, res, next) => { - // BitboxHTTP({ - // method: 'post', - // auth: { - // username: username, - // password: password - // }, - // data: { - // jsonrpc: "1.0", - // id:"getmemoryinfo", - // method: "getmemoryinfo" - // } - // }) - // .then((response) => { - // res.json(response.data.result); - // }) - // .catch((error) => { - // res.send(error.response.data.error.message); - // }); - // }); - // - // router.get('/help', (req, res, next) => { - // BITBOX.Control.help() - // .then((result) => { - // res.json(result); - // }, (err) => { console.log(err); - // }); - // }); - // - // router.post('/stop', (req, res, next) => { - // BITBOX.Control.stop() - // .then((result) => { - // res.json(result); - // }, (err) => { console.log(err); - // }); - // }); } module.exports = Control diff --git a/src/routes/v5/jwt.js b/src/routes/v5/jwt.js new file mode 100644 index 0000000..b7d7578 --- /dev/null +++ b/src/routes/v5/jwt.js @@ -0,0 +1,95 @@ +/* + Utility functions for troubleshooting a JWT token. +*/ + +'use strict' + +// Public npm libraries. +const jwt = require('jsonwebtoken') +const axios = require('axios') + +const express = require('express') +const router = express.Router() + +const config = require('../../../config') +const wlogger = require('../../util/winston-logging') + +const RouteUtils = require('../../util/route-utils') +const routeUtils = new RouteUtils() + +// Used for processing error messages before sending them to the user. +const util = require('util') +util.inspect.defaultOptions = { depth: 1 } + +let _this + +class JWT { + constructor () { + _this = this + + _this.axios = axios + _this.routeUtils = routeUtils + _this.jwt = jwt + _this.config = config + + _this.router = router + _this.router.get('/', _this.root) + _this.router.post('/info', _this.jwtInfo) + } + + root (req, res, next) { + return res.json({ status: 'jwt' }) + } + + // DRY error handler. + errorHandler (err, res) { + // Attempt to decode the error message. + const { msg, status } = _this.routeUtils.decodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } + + res.status(500) + return res.json({ error: util.inspect(err) }) + } + + /** + * @api {post} /jwt/info Get JWT Info + * @apiName GetJWTInfo + * @apiGroup JWT + * @apiDescription + * Get info on your JWT token. Useful for debugging rate limit issues with your + * JWT token. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v5/jwt/info" -H "accept: application/json" -H "Content-Type: application/json" -d '{"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwZGRmZmI2NzRhM2Q2MDAxOTY3NjE1NCIsImVtYWlsIjoiY2hyaXNAYmNodGVzdC5uZXQiLCJhcGlMZXZlbCI6NjAsInJhdGVMaW1pdCI6MywicG9pbnRzVG9Db25zdW1lIjoxNiwiZHVyYXRpb24iOjMwLCJpYXQiOjE2MjU0MzQ2MzYsImV4cCI6MTYyODAyNjYzNn0.1WLugTkQKVG0yMXD1h5nxfho3gRzvSvs8NMa9obVhPM"}' + * + */ + async jwtInfo (req, res, next) { + try { + const jwtIn = req.body.jwt + // console.log('jwt: ', jwtIn) + // console.log('_this.config.apiTokenSecret: ', _this.config.apiTokenSecret) + + const decoded = _this.jwt.verify(jwtIn, _this.config.apiTokenSecret) + // console.log('decoded: ', decoded) + + const expiration = new Date(decoded.exp * 1000) + decoded.expiration = expiration.toISOString() + + const createdAt = new Date(decoded.iat * 1000) + decoded.createdAt = createdAt.toISOString() + + res.status(200) + return res.json(decoded) + } catch (error) { + // console.log('Error in jwt.js/jwtInfo().', error) + wlogger.error('Error in jwt.js/jwtInfo().', error) + + return _this.errorHandler(error, res) + } + } +} + +module.exports = JWT diff --git a/test/v5/jwt.js b/test/v5/jwt.js new file mode 100644 index 0000000..eff3f52 --- /dev/null +++ b/test/v5/jwt.js @@ -0,0 +1,99 @@ +/* + TESTS FOR THE JWT.JS LIBRARY + + This test file uses the environment variable TEST to switch between unit + and integration tests. By default, TEST is set to 'unit'. Set this variable + to 'integration' to run the tests against BCH mainnet. + +*/ + +'use strict' + +const chai = require('chai') +const assert = chai.assert +const JWTRoute = require('../../src/routes/v5/jwt') +const uut = new JWTRoute() +const sinon = require('sinon') + +// Mocking data. +const { mockReq, mockRes } = require('./mocks/express-mocks') +// const mockData = require('./mocks/control-mock') + +// Used for debugging. +const util = require('util') +util.inspect.defaultOptions = { depth: 1 } + +describe('#JWTRouter', () => { + let req, res + let sandbox + + before(() => { + // Set default environment variables for unit tests. + }) + + // Setup the mocks before each test. + beforeEach(() => { + // Mock the req and res objects used by Express routes. + req = mockReq + res = mockRes + + // Explicitly reset the parmas and body. + req.params = {} + req.body = {} + req.query = {} + + sandbox = sinon.createSandbox() + }) + + afterEach(() => { + // Restore Sandbox + sandbox.restore() + }) + + after(() => {}) + + describe('#root', async () => { + // root route handler. + // const root = controlRoute.testableComponents.root + + it('should respond to GET for base route', async () => { + const result = uut.root(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.equal(result.status, 'jwt', 'Returns static string') + }) + }) + + describe('#jwtInfo', () => { + it('should decode the JWT token', async () => { + const jwtToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwZWM3NTY1YTM4ZjkyMjdlOWVjNTM3MCIsImVtYWlsIjoidGVzdHVzZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjAsInJhdGVMaW1pdCI6MywicG9pbnRzVG9Db25zdW1lIjo1MDAsImR1cmF0aW9uIjozMCwiaWF0IjoxNjI2MTA5MzQ4LCJleHAiOjE2Mjg3MDEzNDh9.hF8BKU-1SvlvQBnTNbB65ErQTtNSl-pWlRANSJY-Zb4' + req.body.jwt = jwtToken + + const result = await uut.jwtInfo(req, res) + // console.log('result: ', result) + + assert.property(result, 'id') + assert.property(result, 'email') + assert.property(result, 'apiLevel') + assert.property(result, 'rateLimit') + assert.property(result, 'pointsToConsume') + assert.property(result, 'duration') + assert.property(result, 'iat') + assert.property(result, 'exp') + assert.property(result, 'expiration') + assert.property(result, 'createdAt') + }) + + it('should return an error with malformed JWT token', async () => { + const jwtToken = + 'eyJhbGciOiJIUzI1NiLsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwZWM3NTY1YTM4ZjkyMjdlOWVjNTM3MCIsImVtYWlsIjoidGVzdHVzZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjAsInJhdGVMaW1pdCI6MywicG9pbnRzVG9Db25zdW1lIjo1MDAsImR1cmF0aW9uIjozMCwiaWF0IjoxNjI2MTA5MzQ4LCJleHAiOjE2Mjg3MDEzNDh9.hF8BKU-1SvlvQBnTNbB65ErQTtNSl-pWlRANSJY-Zb4' + req.body.jwt = jwtToken + + const result = await uut.jwtInfo(req, res) + // console.log('result: ', result) + + assert.equal(result.error, 'invalid token') + }) + }) +})