diff --git a/README.md b/README.md index 86ccad6..6091b6b 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,9 @@ Open [config/default.json](config/default.json) and edit it with your settings: - `rpcPort` - `rpcUser` - `rpcPassword` +- change authentication parameters (see [Authentication](#Authentication)): + - `enabled` which enables authentication checks on the api endpoints + - `jwtKey` the key used to generate the jwt If you need to change the settings dynamically from the environment variables, you overwrite them using the environment variable *NODE_CONFIG*. Such as this: @@ -132,6 +135,42 @@ Change any settings in the [docker-compose](docker-compose.yml) to fit your conf $ docker-compose up -d ``` +#### Initial Setup + +Once the docker container is running, you will need to setup and configure your Miner ID by generating a Miner ID private key as well as setting up your [Validity Check Transaction output (VCTx)](https://github.com/bitcoin-sv-specs/brfc-minerid#323-key-design-decisions). You can do that using `docker exec`: + +```console +$ docker exec -it bash + +root@2623e1f4ed4e:/app# +``` + +Then run the cli commands to setup and configure the above: + +```console +root@2623e1f4ed4e:/app# npm run cli -- generateminerid --name testMiner +``` + +```console +root@2623e1f4ed4e:/app# npm run cli -- generatevctx --name testMiner +``` + +If you are running on `livenet` (mainnet), follow the instructions to fund your VCTx. + +## Authentication + +This service uses [JWT tokens](https://tools.ietf.org/html/rfc7519) for authentication. The `authentication.jwtKey` [config](config/default.json) is used for all tokens. To revoke all tokens, change this key. To generate a new `jwtKey`, run the following script: + +```console +node -e "console.log(require('crypto').randomBytes(32).toString('hex'));" +``` + +To generate a JTW token for a user of MinerId, run the `generate_jwt` npm command (you can also set the expiry time in the [generateJWT](config/generateJWT.js) file): + +```console +$ npm run generate_jwt +``` + ## Testing ```console diff --git a/builder.js b/builder.js index 04724f6..0a9bd24 100644 --- a/builder.js +++ b/builder.js @@ -4,12 +4,13 @@ const config = require('config') const fm = require('./utils/filemanager') const { placeholderCB1 } = require('./services/extensions') const coinbaseDocService = require('./services/coinbaseDocumentService') +const { authenticateToken } = require('./utils/authentication') const bsv = require('bsv') const app = express() app.use(bodyParser.json()) -app.get('/opreturn/:alias/:blockHeight([0-9]+)', async (req, res) => { +app.get('/opreturn/:alias/:blockHeight([0-9]+)', authenticateToken, async (req, res) => { const { blockHeight, alias } = req.params res.setHeader('Content-Type', 'text/plain') @@ -27,15 +28,19 @@ app.get('/opreturn/:alias/:blockHeight([0-9]+)', async (req, res) => { } try { - const opReturn = await coinbaseDocService.createMinerIdOpReturn(blockHeight, alias) + const opReturn = await coinbaseDocService.createMinerIdOpReturn( + blockHeight, + alias + ) res.send(opReturn) } catch (err) { res.status(500).send(`Internal error: ${err.message}`) console.warn(`Internal error: ${err.message}`) } -}) +} +) -app.post('/coinbase2', async (req, res) => { +app.post('/coinbase2', authenticateToken, async (req, res) => { const { blockHeight, alias, coinbase2, jobData } = req.body res.setHeader('Content-Type', 'text/plain') @@ -72,7 +77,12 @@ app.post('/coinbase2', async (req, res) => { try { // try to create a BitCoin transaction using Coinbase 2 - bsv.Transaction(Buffer.concat([Buffer.from(placeholderCB1, 'hex'), Buffer.from(coinbase2, 'hex')])) + bsv.Transaction( + Buffer.concat([ + Buffer.from(placeholderCB1, 'hex'), + Buffer.from(coinbase2, 'hex') + ]) + ) } catch (error) { res.status(422).send('Invalid Coinbase 2') console.log('Bad request: invalid coinbase2: ', coinbase2) @@ -80,7 +90,12 @@ app.post('/coinbase2', async (req, res) => { } try { - const cb2 = await coinbaseDocService.createNewCoinbase2(blockHeight, alias, coinbase2, jobData) + const cb2 = await coinbaseDocService.createNewCoinbase2( + blockHeight, + alias, + coinbase2, + jobData + ) res.send(cb2) } catch (err) { res.status(500).send(`Internal error:: ${err.message}`) @@ -88,7 +103,7 @@ app.post('/coinbase2', async (req, res) => { } }) -app.get('/opreturn/:alias/rotate', (req, res) => { +app.get('/opreturn/:alias/rotate', authenticateToken, (req, res) => { res.setHeader('Content-Type', 'text/plain') if (!fm.aliasExists(req.params.alias)) { @@ -106,7 +121,7 @@ app.get('/opreturn/:alias/rotate', (req, res) => { } }) -app.get('/minerid/:alias', (req, res) => { +app.get('/minerid/:alias', authenticateToken, (req, res) => { res.setHeader('Content-Type', 'text/plain') if (!fm.aliasExists(req.params.alias)) { @@ -124,7 +139,7 @@ app.get('/minerid/:alias', (req, res) => { } }) -app.get('/minerid/:alias/sign/:hash([0-9a-fA-F]+)', (req, res) => { +app.get('/minerid/:alias/sign/:hash([0-9a-fA-F]+)', authenticateToken, (req, res) => { res.setHeader('Content-Type', 'text/plain') if (!fm.aliasExists(req.params.alias)) { @@ -140,15 +155,19 @@ app.get('/minerid/:alias/sign/:hash([0-9a-fA-F]+)', (req, res) => { } try { - const signature = coinbaseDocService.signWithCurrentMinerId(req.params.hash, req.params.alias) + const signature = coinbaseDocService.signWithCurrentMinerId( + req.params.hash, + req.params.alias + ) res.send(signature) } catch (err) { res.status(500).send(`Internal error: ${err.message}`) console.warn(`Internal error: ${err.message}`) } -}) +} +) -app.get('/minerid/:alias/pksign/:hash([0-9a-fA-F]+)', (req, res) => { +app.get('/minerid/:alias/pksign/:hash([0-9a-fA-F]+)', authenticateToken, (req, res) => { res.setHeader('Content-Type', 'application/json') if (!fm.aliasExists(req.params.alias)) { @@ -164,15 +183,21 @@ app.get('/minerid/:alias/pksign/:hash([0-9a-fA-F]+)', (req, res) => { } try { - const currentAlias = coinbaseDocService.getCurrentMinerId(req.params.alias) - const signature = coinbaseDocService.signWithCurrentMinerId(req.params.hash, req.params.alias) + const currentAlias = coinbaseDocService.getCurrentMinerId( + req.params.alias + ) + const signature = coinbaseDocService.signWithCurrentMinerId( + req.params.hash, + req.params.alias + ) res.send({ publicKey: currentAlias, signature }) } catch (err) { res.status(500).send(`Internal error: ${err.message}`) console.warn(`Internal error: ${err.message}`) } -}) +} +) app.listen(config.get('port'), () => { console.log(`Server running on port ${config.get('port')}`) diff --git a/config/default.json b/config/default.json index e8fb7f6..fdca90b 100644 --- a/config/default.json +++ b/config/default.json @@ -8,5 +8,9 @@ "rpcPort": 18332, "rpcUser": "bitcoin", "rpcPassword": "bitcoin" + }, + "authentication": { + "enabled": true, + "jwtKey": "e4ef52bbc43782bcbb1dc6ac11bdfe978abccecb180ebd90820ea850fd3ff245" } } \ No newline at end of file diff --git a/config/generateJWT.js b/config/generateJWT.js new file mode 100644 index 0000000..e958ea9 --- /dev/null +++ b/config/generateJWT.js @@ -0,0 +1,14 @@ +// pass argument to npm run generate_JWT to add a username + +const jwt = require('jsonwebtoken') +const config = require('config') + +function generateAccessToken (username) { + // return jwt.sign(username, config.jwtKey, { expiresIn: '1800s' }) + + return jwt.sign(username, config.get('authentication.jwtKey')) +} + +const token = generateAccessToken({ username: process.argv.slice(2)[0] }) + +console.log(token) diff --git a/package-lock.json b/package-lock.json index 6039c0c..b072bd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -400,6 +400,11 @@ "unorm": "1.4.1" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-swap-endianness": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-swap-endianness/-/buffer-swap-endianness-1.0.0.tgz", @@ -770,6 +775,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1760,6 +1773,30 @@ "minimist": "^1.2.5" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1787,6 +1824,25 @@ "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -1846,6 +1902,41 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -3149,8 +3240,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "send": { "version": "0.17.1", diff --git a/package.json b/package.json index 57af91d..87585ad 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "node builder.js", "cli": "node cli.js", - "test": "mocha" + "test": "mocha", + "generate_jwt": "node config/generateJWT.js" }, "author": "nChain Application Development Team", "contributors": [ @@ -25,6 +26,7 @@ "command-line-usage": "^6.0.2", "config": "^3.3.1", "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", "request": "^2.88.2", "request-promise": "^4.2.5" }, diff --git a/plugins/blockbind.js b/plugins/blockbind.js index f092ff5..47ee786 100644 --- a/plugins/blockbind.js +++ b/plugins/blockbind.js @@ -50,7 +50,7 @@ function buildMerkleRootFromCoinbase (coinbaseHash, merkleBranches) { merkleBranches.forEach(merkleBranch => { merkleBranch = swapEndianness(Buffer.from(merkleBranch, 'hex')) // swap endianness before concatenating - let concat = Buffer.concat([res, merkleBranch]) + const concat = Buffer.concat([res, merkleBranch]) res = bsv.crypto.Hash.sha256sha256(concat) }) diff --git a/test/coinbaseDocumentService.test.js b/test/coinbaseDocumentService.test.js index c6fc11a..fe49147 100644 --- a/test/coinbaseDocumentService.test.js +++ b/test/coinbaseDocumentService.test.js @@ -198,11 +198,11 @@ describe('Coinbase Document Services', function () { beforeEach(() => { mock({ [`${os.homedir()}/.minerid-client/unittest`]: { - 'aliases': '[ { "name": "unittest_1" } ]', - 'config': `{ + aliases: '[ { "name": "unittest_1" } ]', + config: `{ "email": "testMiner@testDomain.com" }`, - 'vctx': `{ + vctx: `{ "prv": "KxmBu7NFRxWsT6gcwBDCtthWnokPJhHDVajYAVvTwfucFKdMf1dP", "txid": "6839008199026098cc78bf5f34c9a6bdf7a8009c9f019f8399c7ca1945b4a4ff" }` diff --git a/test/extensions.test.js b/test/extensions.test.js index 74705c3..ecf15c0 100644 --- a/test/extensions.test.js +++ b/test/extensions.test.js @@ -95,16 +95,16 @@ describe('Extensions', function () { describe('BlockInfo', function () { it('can create a proper blockinfo extension', () => { const miningCandidate = { - 'id': 'e706b0e6-793b-448f-a1ae-8ef54459eb72', - 'prevhash': '70f5701644897c92b60e98dbbfe72e1cfd7a2728c6fa3a29c4b4f6e986b0ccaa', - 'coinbaseValue': 5000000974, - 'version': 536870912, - 'nBits': '207fffff', - 'time': 1590152467, - 'height': 106, - 'num_tx': 4, - 'sizeWithoutCoinbase': 1052, - 'merkleProof': [ + id: 'e706b0e6-793b-448f-a1ae-8ef54459eb72', + prevhash: '70f5701644897c92b60e98dbbfe72e1cfd7a2728c6fa3a29c4b4f6e986b0ccaa', + coinbaseValue: 5000000974, + version: 536870912, + nBits: '207fffff', + time: 1590152467, + height: 106, + num_tx: 4, + sizeWithoutCoinbase: 1052, + merkleProof: [ '9bd12ce6508574b3163aadb14eab7bd862306da85b221eb284fb41d6012db98f', '56f04cc78ac493defced65dd58f4437c67bcc697b59778b0cd96c3c64c1b0bbf' ] @@ -128,27 +128,27 @@ describe('Extensions', function () { describe('FeeSpec', function () { it('can create a proper feespec extension', () => { const feeSpec = { - 'fees': [ + fees: [ { - 'feeType': 'standard', - 'miningFee': { - 'satoshis': 1, - 'bytes': 1 + feeType: 'standard', + miningFee: { + satoshis: 1, + bytes: 1 }, - 'relayFee': { - 'satoshis': 1, - 'bytes': 10 + relayFee: { + satoshis: 1, + bytes: 10 } }, { - 'feeType': 'data', - 'miningFee': { - 'satoshis': 2, - 'bytes': 1000 + feeType: 'data', + miningFee: { + satoshis: 2, + bytes: 1000 }, - 'relayFee': { - 'satoshis': 1, - 'bytes': 10000 + relayFee: { + satoshis: 1, + bytes: 10000 } } ] @@ -162,27 +162,27 @@ describe('Extensions', function () { addFeeSpec({ extensions, jobData }) const expectedFeeSpec = { - 'fees': [ + fees: [ { - 'feeType': 'standard', - 'miningFee': { - 'satoshis': 1, - 'bytes': 1 + feeType: 'standard', + miningFee: { + satoshis: 1, + bytes: 1 }, - 'relayFee': { - 'satoshis': 1, - 'bytes': 10 + relayFee: { + satoshis: 1, + bytes: 10 } }, { - 'feeType': 'data', - 'miningFee': { - 'satoshis': 2, - 'bytes': 1000 + feeType: 'data', + miningFee: { + satoshis: 2, + bytes: 1000 }, - 'relayFee': { - 'satoshis': 1, - 'bytes': 10000 + relayFee: { + satoshis: 1, + bytes: 10000 } } ] @@ -194,26 +194,26 @@ describe('Extensions', function () { describe('MinerParams', function () { it('can create a proper minerparams extension', () => { const getInfo = { - 'version': 101000300, - 'protocolversion': 70015, - 'walletversion': 160300, - 'balance': 199.99997068, - 'blocks': 104, - 'timeoffset': 0, - 'connections': 4, - 'proxy': '', - 'difficulty': 4.656542373906925e-10, - 'testnet': false, - 'stn': false, - 'keypoololdest': 1575386196, - 'keypoolsize': 1999, - 'paytxfee': 0.00000000, - 'relayfee': 0.00000250, - 'errors': '', - 'maxblocksize': 9223372036854775807, - 'maxminedblocksize': 128000000, - 'maxstackmemoryusagepolicy': 100000000, - 'maxstackmemoryusageconsensus': 9223372036854775807 + version: 101000300, + protocolversion: 70015, + walletversion: 160300, + balance: 199.99997068, + blocks: 104, + timeoffset: 0, + connections: 4, + proxy: '', + difficulty: 4.656542373906925e-10, + testnet: false, + stn: false, + keypoololdest: 1575386196, + keypoolsize: 1999, + paytxfee: 0.00000000, + relayfee: 0.00000250, + errors: '', + maxblocksize: 9223372036854775807, + maxminedblocksize: 128000000, + maxstackmemoryusagepolicy: 100000000, + maxstackmemoryusageconsensus: 9223372036854775807 } const jobData = { @@ -240,87 +240,87 @@ describe('Extensions', function () { describe('All Extensions', function () { const exampleDoc = { - 'version': '0.1', - 'height': 624455, - 'prevMinerId': '022604665d3a186be9690231a279f8e18b800f4ce78caac2d51940c8c1c92a8354', - 'prevMinerIdSig': '3044022067452f9d9baeef327183e2f565c8c4d76299287d6c0253aa133c75150d78d307022029c9d93ac08c19e20a03dc32307c4f0a023e79a505c02b01857c84d49670acf6', - 'minerId': '022604665d3a186be9690231a279f8e18b800f4ce78caac2d51940c8c1c92a8354', - 'vctx': { - 'txid': '6584f53e13216d34979098362bda34bd3677058c8b4e0621b24395c576b6baad', - 'vout': 0 + version: '0.1', + height: 624455, + prevMinerId: '022604665d3a186be9690231a279f8e18b800f4ce78caac2d51940c8c1c92a8354', + prevMinerIdSig: '3044022067452f9d9baeef327183e2f565c8c4d76299287d6c0253aa133c75150d78d307022029c9d93ac08c19e20a03dc32307c4f0a023e79a505c02b01857c84d49670acf6', + minerId: '022604665d3a186be9690231a279f8e18b800f4ce78caac2d51940c8c1c92a8354', + vctx: { + txid: '6584f53e13216d34979098362bda34bd3677058c8b4e0621b24395c576b6baad', + vout: 0 } } const exampleCoinbase2 = 'ffffffff011a0a5325000000001976a9145deb9155942e7d38febc15de8870222fd24d080e88ac00000000' const miningCandidate = { - 'id': 'e706b0e6-793b-448f-a1ae-8ef54459eb72', - 'prevhash': '70f5701644897c92b60e98dbbfe72e1cfd7a2728c6fa3a29c4b4f6e986b0ccaa', - 'coinbaseValue': 5000000974, - 'version': 536870912, - 'nBits': '207fffff', - 'time': 1590152467, - 'height': 106, - 'num_tx': 4, - 'sizeWithoutCoinbase': 1052, - 'merkleProof': [ + id: 'e706b0e6-793b-448f-a1ae-8ef54459eb72', + prevhash: '70f5701644897c92b60e98dbbfe72e1cfd7a2728c6fa3a29c4b4f6e986b0ccaa', + coinbaseValue: 5000000974, + version: 536870912, + nBits: '207fffff', + time: 1590152467, + height: 106, + num_tx: 4, + sizeWithoutCoinbase: 1052, + merkleProof: [ '9bd12ce6508574b3163aadb14eab7bd862306da85b221eb284fb41d6012db98f', '56f04cc78ac493defced65dd58f4437c67bcc697b59778b0cd96c3c64c1b0bbf' ] } const getInfo = { - 'version': 101000300, - 'protocolversion': 70015, - 'walletversion': 160300, - 'balance': 199.99997068, - 'blocks': 104, - 'timeoffset': 0, - 'connections': 4, - 'proxy': '', - 'difficulty': 4.656542373906925e-10, - 'testnet': false, - 'stn': false, - 'keypoololdest': 1575386196, - 'keypoolsize': 1999, - 'paytxfee': 0.00000000, - 'relayfee': 0.00000250, - 'errors': '', - 'maxblocksize': 9223372036854775807, // - 'maxminedblocksize': 128000000, // - 'maxstackmemoryusagepolicy': 100000000, // - 'maxstackmemoryusageconsensus': 9223372036854775807 // + version: 101000300, + protocolversion: 70015, + walletversion: 160300, + balance: 199.99997068, + blocks: 104, + timeoffset: 0, + connections: 4, + proxy: '', + difficulty: 4.656542373906925e-10, + testnet: false, + stn: false, + keypoololdest: 1575386196, + keypoolsize: 1999, + paytxfee: 0.00000000, + relayfee: 0.00000250, + errors: '', + maxblocksize: 9223372036854775807, // + maxminedblocksize: 128000000, // + maxstackmemoryusagepolicy: 100000000, // + maxstackmemoryusageconsensus: 9223372036854775807 // } const feeSpec = { - 'fees': [ + fees: [ { - 'feeType': 'standard', - 'miningFee': { - 'satoshis': 1, - 'bytes': 1 + feeType: 'standard', + miningFee: { + satoshis: 1, + bytes: 1 }, - 'relayFee': { - 'satoshis': 1, - 'bytes': 10 + relayFee: { + satoshis: 1, + bytes: 10 } }, { - 'feeType': 'data', - 'miningFee': { - 'satoshis': 2, - 'bytes': 1000 + feeType: 'data', + miningFee: { + satoshis: 2, + bytes: 1000 }, - 'relayFee': { - 'satoshis': 1, - 'bytes': 10000 + relayFee: { + satoshis: 1, + bytes: 10000 } } ] } it('makes no changes to the coinbase document when no jobData is included', () => { - let doc = clonedeep(exampleDoc) + const doc = clonedeep(exampleDoc) addExtensions(doc, '', {}) @@ -328,14 +328,14 @@ describe('Extensions', function () { }) it('adds blockinfo extension when ONLY miningCandidate data is included in jobData', () => { - let doc = clonedeep(exampleDoc) - let jobData = { + const doc = clonedeep(exampleDoc) + const jobData = { miningCandidate: miningCandidate } addExtensions(doc, '', jobData) - let expectedDoc = clonedeep(exampleDoc) + const expectedDoc = clonedeep(exampleDoc) expectedDoc.extensions = { blockinfo: { sizeWithoutCoinbase: 1052, @@ -347,14 +347,14 @@ describe('Extensions', function () { }) it('adds minerparams extension when ONLY getInfo data is included in jobData', () => { - let doc = clonedeep(exampleDoc) - let jobData = { + const doc = clonedeep(exampleDoc) + const jobData = { getInfo: getInfo } addExtensions(doc, '', jobData) - let expectedDoc = clonedeep(exampleDoc) + const expectedDoc = clonedeep(exampleDoc) expectedDoc.extensions = { minerparams: { policy: { @@ -372,14 +372,14 @@ describe('Extensions', function () { }) it('adds feespec extension when ONLY feeSpec data is included in jobData', () => { - let doc = clonedeep(exampleDoc) - let jobData = { + const doc = clonedeep(exampleDoc) + const jobData = { feeSpec: feeSpec } addExtensions(doc, '', jobData) - let expectedDoc = clonedeep(exampleDoc) + const expectedDoc = clonedeep(exampleDoc) expectedDoc.extensions = { feeSpec: feeSpec } @@ -388,14 +388,14 @@ describe('Extensions', function () { }) it('adds blockinfo and blockbind extensions when cb1, cb2, and miningCandidate data are included in jobData', () => { - let doc = clonedeep(exampleDoc) - let jobData = { + const doc = clonedeep(exampleDoc) + const jobData = { miningCandidate: miningCandidate } addExtensions(doc, exampleCoinbase2, jobData) - let expectedDoc = clonedeep(exampleDoc) + const expectedDoc = clonedeep(exampleDoc) expectedDoc.extensions = { blockinfo: { sizeWithoutCoinbase: 1052, @@ -411,14 +411,14 @@ describe('Extensions', function () { }) it('adds minerparams extension when cb2, and getInfo data are included in jobData', () => { - let doc = clonedeep(exampleDoc) - let jobData = { + const doc = clonedeep(exampleDoc) + const jobData = { getInfo: getInfo } addExtensions(doc, exampleCoinbase2, jobData) - let expectedDoc = clonedeep(exampleDoc) + const expectedDoc = clonedeep(exampleDoc) expectedDoc.extensions = { minerparams: { policy: { @@ -436,14 +436,14 @@ describe('Extensions', function () { }) it('adds feespec extension when cb2, and feeSpec data are included in jobData', () => { - let doc = clonedeep(exampleDoc) - let jobData = { + const doc = clonedeep(exampleDoc) + const jobData = { feeSpec: feeSpec } addExtensions(doc, exampleCoinbase2, jobData) - let expectedDoc = clonedeep(exampleDoc) + const expectedDoc = clonedeep(exampleDoc) expectedDoc.extensions = { feeSpec: feeSpec } @@ -452,8 +452,8 @@ describe('Extensions', function () { }) it('adds all extensions when cb2, getInfo, miningCandidate, and feeSpec data are included in jobData', () => { - let doc = clonedeep(exampleDoc) - let jobData = { + const doc = clonedeep(exampleDoc) + const jobData = { getInfo: getInfo, miningCandidate: miningCandidate, feeSpec: feeSpec @@ -461,7 +461,7 @@ describe('Extensions', function () { addExtensions(doc, exampleCoinbase2, jobData) - let expectedDoc = clonedeep(exampleDoc) + const expectedDoc = clonedeep(exampleDoc) expectedDoc.extensions = { feeSpec: feeSpec, minerparams: { diff --git a/utils/authentication.js b/utils/authentication.js new file mode 100644 index 0000000..807d612 --- /dev/null +++ b/utils/authentication.js @@ -0,0 +1,30 @@ +const config = require('config') +const jwt = require('jsonwebtoken') + +function authenticateToken (req, res, next) { + if (!config.get('authentication.enabled')) { + return next() + } + + if (!req.headers.authorization) { + return next('Missing Authorization Header') + } + + // Gather the jwt access token from the request header + const token = req.headers.authorization.split('Bearer ')[1] + + if (token == null) return res.sendStatus(401) // if there isn't any token + + jwt.verify(token, config.get('authentication.jwtKey'), (err, data) => { + if (err) { + console.log('Bad request: invalid token: ', token) + return res.sendStatus(403) + } + console.log(`User ${data.username} authenticated`) + + req.user = data + next() // pass the execution off to whatever request the client intended + }) +} + +module.exports = { authenticateToken }