diff --git a/server/lib/api/address.js b/server/lib/api/address.js index cc9c077..1472f1b 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -1,21 +1,14 @@ const logger = require('../logger'); -const util = require('../util'); const db = require('../db'); module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { const addr = req.params.addr || ''; - if (!util.isBitcoinAddress(addr)) { - return res.status(404).send({ - error: 'Invalid bitcoin address', - }); - } - - return db.txs.getTxByAddress(addr, 0, 999999999, (error, txs) => { - if (error) { + return db.txs.getTxByAddress(addr, 0, 999999999, (err, txs) => { + if (err || txs.length === 0) { logger.log('error', - `getTxByBlock ${error}`); + `getTxByBlock ${err}`); return res.status(404).send(); } diff --git a/server/lib/api/block.js b/server/lib/api/block.js index 3041c2c..9ca7636 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -1,17 +1,10 @@ const logger = require('../logger'); const db = require('../db'); -const util = require('../util'); module.exports = function BlockAPI(router) { router.get('/block/:blockHash', (req, res) => { const blockHash = req.params.blockHash; - if (!util.isBlockHash(blockHash)) { - return res.status(404).send({ - error: 'Invalid bitcoin address', - }); - } - // Pass Mongo params, fields and limit to db api. return db.blocks.getByHash(blockHash, (err, block) => { @@ -70,12 +63,6 @@ module.exports = function BlockAPI(router) { router.get('/rawblock/:blockHash', (req, res) => { const blockHash = req.params.blockHash || ''; - if (!util.isBlockHash(blockHash)) { - return res.status(400).send({ - error: 'Invalid bitcoin address', - }); - } - // Pass Mongo params, fields and limit to db api. return db.blocks.getRawBlock(blockHash, (err, block) => { diff --git a/server/lib/api/index.js b/server/lib/api/index.js index c8aced1..db8693a 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -2,6 +2,7 @@ const express = require('express'); const config = require('../../config'); const bodyParser = require('body-parser'); const helmet = require('helmet'); +const sanitizer = require('./middleware/sanitizer'); const app = express(); const api = express.Router(); @@ -11,11 +12,9 @@ app.use(cors); app.use(helmet()); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); - +app.use(sanitizer); // Serve insight ui front end from root dir public folder app.use(express.static('../app/www', { maxage: '1w' })); -// Legacy UI - useful for 1:1 compares -// app.use(express.static('./public', { maxage: '1w' })); app.set('json spaces', config.api.json_spaces); diff --git a/server/lib/api/message.js b/server/lib/api/message.js index e6e01b3..2ec2d13 100644 --- a/server/lib/api/message.js +++ b/server/lib/api/message.js @@ -1,17 +1,10 @@ const Message = require('bitcore-message'); -const util = require('../util'); // Copied from previous source function verifyMessage(req, res) { const address = req.body.address || req.query.address; const signature = req.body.signature || req.query.signature; const message = req.body.message || req.query.message; - if (!util.isBitcoinAddress(address)) { - return res.status(400).send({ - error: 'Invalid bitcoin address', - }); - } - if (!address || !signature || !message) { return res.json({ message: 'Missing parameters (expected "address", "signature" and "message")', diff --git a/server/lib/api/middleware/sanitizer.js b/server/lib/api/middleware/sanitizer.js new file mode 100644 index 0000000..a8fb1de --- /dev/null +++ b/server/lib/api/middleware/sanitizer.js @@ -0,0 +1,140 @@ +const util = require('../../util'); + +// Strip the request, sanitize inputs, rebuild +module.exports = function sanitize(req, res, next) { + const params = req.params || null; + const body = req.body || null; + const query = req.query || null; + + let cleanParams = null; + let cleanBody = null; + let cleanQuery = null; + + // req.params + if (params) { + // Transaction Id + if (params.txid && !util.isTxid(params.txid)) { + return res.status(404).send({ + error: 'Invalid Transaction Id', + }); + } + // Address + if (params.addr && typeof (params.addr) !== 'string') { + return res.status(404).send({ + error: 'Invalid Bitcoin Address', + }); + } + // Block Hash + if (params.blockHash && typeof (params.blockHash) !== 'string') { + return res.status(404).send({ + error: 'Invalid Block Hash', + }); + } + // Height + if (params.height) { + if (typeof (params.height) !== 'number') { + return res.status(404).send({ + error: 'Invalid Block Hash', + }); + } + params.height = parseInt(params.height, 10); + } + + cleanParams = { + txid: params.txid || null, + addr: params.addr || null, + blockHash: params.blockHash || null, + height: params.height || null, + }; + } + + // req.body + if (body) { + // Signature + if (body.signature && typeof (body.signature) !== 'string') { + return res.status(404).send({ + error: 'Invalid Signature', + }); + } + // Message + if (body.message && typeof (body.message) !== 'string') { + return res.status(404).send({ + error: 'Invalid Message', + }); + } + // Address + if (body.address && !util.isBitcoinAddress(body.address)) { + return res.status(404).send({ + error: 'Invalid Bitcoin Address', + }); + } + cleanBody = { + signature: body.signature || null, + message: body.message || null, + address: body.address || null, + }; + } + + if (query) { + // Address + if (query.address && !util.isBitcoinAddress(query.address)) { + return res.status(404).send({ + error: 'Invalid Bitcoin Address', + }); + } + // Signature + if (query.signature && typeof (query.signature) !== 'string') { + return res.status(404).send({ + error: 'Invalid Signature', + }); + } + // Message + if (query.message && typeof (query.message) !== 'string') { + return res.status(404).send({ + error: 'Invalid Message', + }); + } + // q + if (query.q && typeof (query.q) !== 'string') { + return res.status(404).send({ + error: 'Invalid Q', + }); + } + // Page Number + if (query.pageNum && typeof (query.pageNum) !== 'number') { + return res.status(404).send({ + error: 'Invalid Page Number', + }); + } + // Block (hash - implicit) + if (query.block && typeof (query.block) !== 'string') { + return res.status(404).send({ + error: 'Invalid Block', + }); + } + // Raw Tx + if (query.rawtx && typeof (query.rawtx) !== 'string') { + return res.status(404).send({ + error: 'Invalid Bitcoin Address', + }); + } + + cleanQuery = { + address: query.address || null, + signature: query.signature || null, + message: query.message || null, + q: query.q || null, + pageNum: query.pageNum || null, + block: query.block || null, + rawtx: query.rawtx || null, + }; + } + + // Strip off unexpected params + req.params = cleanParams; + req.body = cleanBody; + req.query = cleanQuery; + + return next(); +}; + diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 1552e07..00b29c6 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -2,34 +2,24 @@ const logger = require('../logger'); const request = require('request'); const config = require('../../config'); const db = require('../db'); -const util = require('../util'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; const MAX_TXS = config.api.max_txs; -const TTL = config.api.request_ttl; module.exports = function transactionAPI(router) { // Txs by txid router.get('/tx/:txid', (req, res) => { - if (!util.isTxid(req.params.txid)) { - return res.status(404).send({ - error: 'Invalid transaction id', - }); - } - // Get max block height for calculating confirmations const height = db.blocks.bestHeight(); - // Bcoin transaction data const txid = req.params.txid || ''; - db.txs.getTxById(txid, (err, transaction) => { - if (err || !transaction) { + return db.txs.getTxById(txid, (err, tx) => { + if (err || !tx) { logger.log('error', - `/tx/:tid getTxById: ${err.err}`); + `/tx/:tid getTxById: ${err ? err.err : ''}`); return res.status(404).send(); } - const tx = transaction; return res.send({ txid: tx.hash, version: tx.version, @@ -61,17 +51,9 @@ module.exports = function transactionAPI(router) { // query by address router.get('/txs', (req, res) => { const pageNum = parseInt(req.query.pageNum, 10) || 0; - const rangeStart = pageNum * MAX_TXS; - const rangeEnd = rangeStart + MAX_TXS; const height = db.blocks.bestHeight(); // get txs for blockhash, start with best height to calc confirmations if (req.query.block) { - if (!util.isBlockHash(req.query.block)) { - return res.status(400).send({ - error: 'Invalid block hash', - }); - } - return db.txs.getTxCountByBlock(req.query.block, (err, count) => { if (err) { logger.log('error', @@ -115,16 +97,10 @@ module.exports = function transactionAPI(router) { }); }); } else if (req.query.address) { - if (!util.isBitcoinAddress(req.query.address)) { - return res.status(400).send({ - error: 'Invalid bitcoin address', - }); - } - // Get txs by address, start with best height to calc confirmations const addr = req.query.address || ''; - db.txs.getTxCountByAddress(req.query.address, (err, count) => { + return db.txs.getTxCountByAddress(addr, (err, count) => { if (err) { logger.log('error', `getTxByBlock ${err}`); @@ -132,7 +108,7 @@ module.exports = function transactionAPI(router) { } const totalPages = Math.ceil(count / MAX_TXS); - return db.txs.getTxByAddress(req.query.address, pageNum, MAX_TXS, (error, txs) => { + return db.txs.getTxByAddress(addr, pageNum, MAX_TXS, (error, txs) => { if (error) { logger.log('error', `getTxByBlock ${error}`); @@ -165,39 +141,38 @@ module.exports = function transactionAPI(router) { }); }); }); - } else { - // Get last n txs - db.txs.getTopTransactions((err, txs) => { - if (err) { - logger.log('err', - `/txs getTopTransactions ${err}`); - return res.status(404).send(err); - } - return res.send(txs.map(tx => ({ - txid: tx.hash, - fees: tx.fee / 1e8, - size: tx.size, - confirmations: (height - tx.height) + 1, - valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, - vin: tx.inputs.map(input => ({ - scriptSig: { - asm: input.script, - }, - addr: input.address, - value: input.value / 1e8, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - asm: output.script, - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - })), - ); - }); } + // Get last n txs + return db.txs.getTopTransactions((err, txs) => { + if (err) { + logger.log('err', + `/txs getTopTransactions ${err}`); + return res.status(404).send(err); + } + return res.send(txs.map(tx => ({ + txid: tx.hash, + fees: tx.fee / 1e8, + size: tx.size, + confirmations: (height - tx.height) + 1, + valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, + vin: tx.inputs.map(input => ({ + scriptSig: { + asm: input.script, + }, + addr: input.address, + value: input.value / 1e8, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + asm: output.script, + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + })), + ); + }); }); router.get('/rawtx/:txid', (req, res) => res.send(req.params.txid)); diff --git a/server/models/transaction.js b/server/models/transaction.js index 2376357..f6f5c0e 100644 --- a/server/models/transaction.js +++ b/server/models/transaction.js @@ -7,7 +7,6 @@ const config = require('../config'); const Schema = mongoose.Schema; // These limits can be overriden higher up the stack const MAX_TXS = config.api.max_txs; -const MAX_PAGE_TXS = config.api.max_page_txs; const TransactionSchema = new Schema({ hash: { type: String, default: '' },