input sanitization moved to middleware
This commit is contained in:
parent
e7fd05afe9
commit
4ba24bba25
@ -1,21 +1,14 @@
|
|||||||
const logger = require('../logger');
|
const logger = require('../logger');
|
||||||
const util = require('../util');
|
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
|
|
||||||
module.exports = function AddressAPI(router) {
|
module.exports = function AddressAPI(router) {
|
||||||
router.get('/addr/:addr', (req, res) => {
|
router.get('/addr/:addr', (req, res) => {
|
||||||
const addr = req.params.addr || '';
|
const addr = req.params.addr || '';
|
||||||
|
|
||||||
if (!util.isBitcoinAddress(addr)) {
|
return db.txs.getTxByAddress(addr, 0, 999999999, (err, txs) => {
|
||||||
return res.status(404).send({
|
if (err || txs.length === 0) {
|
||||||
error: 'Invalid bitcoin address',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.txs.getTxByAddress(addr, 0, 999999999, (error, txs) => {
|
|
||||||
if (error) {
|
|
||||||
logger.log('error',
|
logger.log('error',
|
||||||
`getTxByBlock ${error}`);
|
`getTxByBlock ${err}`);
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,10 @@
|
|||||||
const logger = require('../logger');
|
const logger = require('../logger');
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const util = require('../util');
|
|
||||||
|
|
||||||
module.exports = function BlockAPI(router) {
|
module.exports = function BlockAPI(router) {
|
||||||
router.get('/block/:blockHash', (req, res) => {
|
router.get('/block/:blockHash', (req, res) => {
|
||||||
const blockHash = req.params.blockHash;
|
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.
|
// Pass Mongo params, fields and limit to db api.
|
||||||
return db.blocks.getByHash(blockHash,
|
return db.blocks.getByHash(blockHash,
|
||||||
(err, block) => {
|
(err, block) => {
|
||||||
@ -70,12 +63,6 @@ module.exports = function BlockAPI(router) {
|
|||||||
router.get('/rawblock/:blockHash', (req, res) => {
|
router.get('/rawblock/:blockHash', (req, res) => {
|
||||||
const blockHash = req.params.blockHash || '';
|
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.
|
// Pass Mongo params, fields and limit to db api.
|
||||||
return db.blocks.getRawBlock(blockHash,
|
return db.blocks.getRawBlock(blockHash,
|
||||||
(err, block) => {
|
(err, block) => {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const express = require('express');
|
|||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
|
const sanitizer = require('./middleware/sanitizer');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const api = express.Router();
|
const api = express.Router();
|
||||||
@ -11,11 +12,9 @@ app.use(cors);
|
|||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
app.use(sanitizer);
|
||||||
// Serve insight ui front end from root dir public folder
|
// Serve insight ui front end from root dir public folder
|
||||||
app.use(express.static('../app/www', { maxage: '1w' }));
|
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);
|
app.set('json spaces', config.api.json_spaces);
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,10 @@
|
|||||||
const Message = require('bitcore-message');
|
const Message = require('bitcore-message');
|
||||||
const util = require('../util');
|
|
||||||
// Copied from previous source
|
// Copied from previous source
|
||||||
function verifyMessage(req, res) {
|
function verifyMessage(req, res) {
|
||||||
const address = req.body.address || req.query.address;
|
const address = req.body.address || req.query.address;
|
||||||
const signature = req.body.signature || req.query.signature;
|
const signature = req.body.signature || req.query.signature;
|
||||||
const message = req.body.message || req.query.message;
|
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) {
|
if (!address || !signature || !message) {
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'Missing parameters (expected "address", "signature" and "message")',
|
message: 'Missing parameters (expected "address", "signature" and "message")',
|
||||||
|
|||||||
140
server/lib/api/middleware/sanitizer.js
Normal file
140
server/lib/api/middleware/sanitizer.js
Normal file
@ -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();
|
||||||
|
};
|
||||||
|
|
||||||
@ -2,34 +2,24 @@ const logger = require('../logger');
|
|||||||
const request = require('request');
|
const request = require('request');
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const util = require('../util');
|
|
||||||
|
|
||||||
const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`;
|
const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`;
|
||||||
const MAX_TXS = config.api.max_txs;
|
const MAX_TXS = config.api.max_txs;
|
||||||
const TTL = config.api.request_ttl;
|
|
||||||
|
|
||||||
module.exports = function transactionAPI(router) {
|
module.exports = function transactionAPI(router) {
|
||||||
// Txs by txid
|
// Txs by txid
|
||||||
router.get('/tx/:txid', (req, res) => {
|
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
|
// Get max block height for calculating confirmations
|
||||||
const height = db.blocks.bestHeight();
|
const height = db.blocks.bestHeight();
|
||||||
// Bcoin transaction data
|
|
||||||
const txid = req.params.txid || '';
|
const txid = req.params.txid || '';
|
||||||
|
|
||||||
db.txs.getTxById(txid, (err, transaction) => {
|
return db.txs.getTxById(txid, (err, tx) => {
|
||||||
if (err || !transaction) {
|
if (err || !tx) {
|
||||||
logger.log('error',
|
logger.log('error',
|
||||||
`/tx/:tid getTxById: ${err.err}`);
|
`/tx/:tid getTxById: ${err ? err.err : ''}`);
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
const tx = transaction;
|
|
||||||
return res.send({
|
return res.send({
|
||||||
txid: tx.hash,
|
txid: tx.hash,
|
||||||
version: tx.version,
|
version: tx.version,
|
||||||
@ -61,17 +51,9 @@ module.exports = function transactionAPI(router) {
|
|||||||
// query by address
|
// query by address
|
||||||
router.get('/txs', (req, res) => {
|
router.get('/txs', (req, res) => {
|
||||||
const pageNum = parseInt(req.query.pageNum, 10) || 0;
|
const pageNum = parseInt(req.query.pageNum, 10) || 0;
|
||||||
const rangeStart = pageNum * MAX_TXS;
|
|
||||||
const rangeEnd = rangeStart + MAX_TXS;
|
|
||||||
const height = db.blocks.bestHeight();
|
const height = db.blocks.bestHeight();
|
||||||
// get txs for blockhash, start with best height to calc confirmations
|
// get txs for blockhash, start with best height to calc confirmations
|
||||||
if (req.query.block) {
|
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) => {
|
return db.txs.getTxCountByBlock(req.query.block, (err, count) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.log('error',
|
logger.log('error',
|
||||||
@ -115,16 +97,10 @@ module.exports = function transactionAPI(router) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (req.query.address) {
|
} 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
|
// Get txs by address, start with best height to calc confirmations
|
||||||
const addr = req.query.address || '';
|
const addr = req.query.address || '';
|
||||||
|
|
||||||
db.txs.getTxCountByAddress(req.query.address, (err, count) => {
|
return db.txs.getTxCountByAddress(addr, (err, count) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.log('error',
|
logger.log('error',
|
||||||
`getTxByBlock ${err}`);
|
`getTxByBlock ${err}`);
|
||||||
@ -132,7 +108,7 @@ module.exports = function transactionAPI(router) {
|
|||||||
}
|
}
|
||||||
const totalPages = Math.ceil(count / MAX_TXS);
|
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) {
|
if (error) {
|
||||||
logger.log('error',
|
logger.log('error',
|
||||||
`getTxByBlock ${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));
|
router.get('/rawtx/:txid', (req, res) => res.send(req.params.txid));
|
||||||
|
|||||||
@ -7,7 +7,6 @@ const config = require('../config');
|
|||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
// These limits can be overriden higher up the stack
|
// These limits can be overriden higher up the stack
|
||||||
const MAX_TXS = config.api.max_txs;
|
const MAX_TXS = config.api.max_txs;
|
||||||
const MAX_PAGE_TXS = config.api.max_page_txs;
|
|
||||||
|
|
||||||
const TransactionSchema = new Schema({
|
const TransactionSchema = new Schema({
|
||||||
hash: { type: String, default: '' },
|
hash: { type: String, default: '' },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user