diff --git a/README.md b/README.md index fdbba4e3d..b66e100df 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![Build Status](https://dev.azure.com/Hyperledger/blockchain-explorer/_apis/build/status/hyperledger.blockchain-explorer?branchName=master)](https://dev.azure.com/Hyperledger/blockchain-explorer/_build/latest?definitionId=41&branchName=master) [![CII Best Practice](https://bestpractices.coreinfrastructure.org/projects/2710/badge)](https://bestpractices.coreinfrastructure.org/projects/2710) [![Documentation Status](https://readthedocs.org/projects/blockchain-explorer/badge/?version=master)](https://blockchain-explorer.readthedocs.io/en/master/?badge=master) +[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/hyperledger/blockchain-explorer.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/hyperledger/blockchain-explorer/context:javascript) diff --git a/app/Explorer.js b/app/Explorer.js index e854a5297..54e920358 100644 --- a/app/Explorer.js +++ b/app/Explorer.js @@ -7,6 +7,7 @@ const bodyParser = require('body-parser'); const swaggerUi = require('swagger-ui-express'); const compression = require('compression'); const passport = require('passport'); +const RateLimit = require('express-rate-limit'); const PlatformBuilder = require('./platform/PlatformBuilder'); const explorerconfig = require('./explorerconfig.json'); const PersistenceFactory = require('./persistence/PersistenceFactory'); @@ -37,12 +38,26 @@ class Explorer { */ constructor() { this.app = new Express(); + + // set up rate limiter: maximum of 1000 requests per minute + + const limiter = new RateLimit({ + windowMs: 1 * 60 * 1000, // 1 minute + max: 1000 + }); + // apply rate limiter to all requests + this.app.use(limiter); + this.app.use(bodyParser.json()); this.app.use( bodyParser.urlencoded({ extended: true }) ); + + // eslint-disable-next-line spellcheck/spell-checker + // handle rate limit, see https://lgtm.com/rules/1506065727959/ + this.app.use(passport.initialize()); if (process.env.NODE_ENV !== 'production') { this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); diff --git a/app/persistence/fabric/CRUDService.js b/app/persistence/fabric/CRUDService.js index 943cc7a50..9725eff6d 100644 --- a/app/persistence/fabric/CRUDService.js +++ b/app/persistence/fabric/CRUDService.js @@ -76,13 +76,25 @@ class CRUDService { * @memberof CRUDService */ getTxList(channel_genesis_hash, blockNum, txid, from, to, orgs) { - let txListSql = ''; + let byOrgs = false; if (orgs && orgs !== '') { - txListSql = `and t.creator_msp_id in (${orgs})`; + byOrgs = true; } + + logger.debug('getTxList.byOrgs ', byOrgs); + console.debug('getTxList.byOrgs ', byOrgs); + + const sqlTxListByOrgs = ` select t.creator_msp_id,t.txhash,t.type,t.chaincodename,t.createdt,channel.name as channelName from transactions as t + inner join channel on t.channel_genesis_hash=channel.channel_genesis_hash where t.blockid >= ${blockNum} and t.id >= ${txid} and t.creator_msp_id in (${orgs}) and + t.channel_genesis_hash = '${channel_genesis_hash}' and t.createdt between '${from}' and '${to}' order by t.id desc`; + const sqlTxList = ` select t.creator_msp_id,t.txhash,t.type,t.chaincodename,t.createdt,channel.name as channelName from transactions as t - inner join channel on t.channel_genesis_hash=channel.channel_genesis_hash where t.blockid >= ${blockNum} and t.id >= ${txid} ${txListSql} and - t.channel_genesis_hash = '${channel_genesis_hash}' and t.createdt between '${from}' and '${to}' order by t.id desc`; + inner join channel on t.channel_genesis_hash=channel.channel_genesis_hash where t.blockid >= ${blockNum} and t.id >= ${txid} and + t.channel_genesis_hash = '${channel_genesis_hash}' and t.createdt between '${from}' and '${to}' order by t.id desc`; + + if (byOrgs) { + return this.sql.getRowsBySQlQuery(sqlTxListByOrgs); + } return this.sql.getRowsBySQlQuery(sqlTxList); } @@ -99,17 +111,33 @@ class CRUDService { * @memberof CRUDService */ getBlockAndTxList(channel_genesis_hash, blockNum, from, to, orgs) { - let blockTxListSql = ''; + let byOrgs = false; + // workaround for SQL injection if (orgs && orgs !== '') { - blockTxListSql = `and creator_msp_id in (${orgs})`; + byOrgs = true; } + + logger.debug('getBlockAndTxList.byOrgs ', byOrgs); + console.debug('getBlockAndTxList.byOrgs ', byOrgs); + const sqlBlockTxList = `select a.* from ( select (select c.name from channel c where c.channel_genesis_hash = '${channel_genesis_hash}' ) as channelname, blocks.blocknum,blocks.txcount ,blocks.datahash ,blocks.blockhash ,blocks.prehash,blocks.createdt, blocks.blksize, ( - SELECT array_agg(txhash) as txhash FROM transactions where blockid = blocks.blocknum ${blockTxListSql} and + SELECT array_agg(txhash) as txhash FROM transactions where blockid = blocks.blocknum and channel_genesis_hash = '${channel_genesis_hash}' and createdt between '${from}' and '${to}') from blocks where blocks.channel_genesis_hash ='${channel_genesis_hash}' and blocknum >= 0 and blocks.createdt between '${from}' and '${to}' - order by blocks.blocknum desc) a where a.txhash IS NOT NULL`; + order by blocks.blocknum desc) a where a.txhash IS NOT NULL`; + + const sqlBlockTxListByOrgs = `select a.* from ( + select (select c.name from channel c where c.channel_genesis_hash = + '${channel_genesis_hash}' ) as channelname, blocks.blocknum,blocks.txcount ,blocks.datahash ,blocks.blockhash ,blocks.prehash,blocks.createdt, blocks.blksize, ( + SELECT array_agg(txhash) as txhash FROM transactions where blockid = blocks.blocknum and creator_msp_id in (${orgs}) and + channel_genesis_hash = '${channel_genesis_hash}' and createdt between '${from}' and '${to}') from blocks where + blocks.channel_genesis_hash ='${channel_genesis_hash}' and blocknum >= 0 and blocks.createdt between '${from}' and '${to}' + order by blocks.blocknum desc) a where a.txhash IS NOT NULL`; + if (byOrgs) { + return this.sql.getRowsBySQlQuery(sqlBlockTxListByOrgs); + } return this.sql.getRowsBySQlQuery(sqlBlockTxList); } diff --git a/lgtm.yml b/lgtm.yml index 83601a78a..706954da9 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -3,7 +3,10 @@ path_classifiers: queries: - exclude: "js/unused-local-variable" + - exclude: "js/unused-local-variable" + - exclude: "js/stack-trace-exposure" + - exclude: "js/useless-assignment-to-local" + - exclude: "js/react/unused-or-undefined-state-property" extraction: javascript: index: diff --git a/package-lock.json b/package-lock.json index 886d8b7bf..8b28cefd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hyperledger-explorer", - "version": "0.3.9", + "version": "1.0.0-rc1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1830,6 +1830,11 @@ } } }, + "express-rate-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.0.0.tgz", + "integrity": "sha512-dhT57wqxfqmkOi4HM7NuT4Gd7gbUgSK2ocG27Y6lwm8lbOAw9XQfeANawGq8wLDtlGPO1ZgDj0HmKsykTxfFAg==" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", diff --git a/package.json b/package.json index bad5f0914..9981f95b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hyperledger-explorer", - "version": "0.3.9", + "version": "1.0.0-rc1", "description": "hyperledger-explorer", "private": true, "main": "main.js", @@ -25,6 +25,7 @@ "ejs": "^2.5.6", "enum": "^2.5.0", "express": "^4.15.3", + "express-rate-limit": "^5.0.0", "fabric-ca-client": "^1.4.4", "fabric-client": "^1.4.4", "fabric-network": "^1.4.4",