diff --git a/lib/bcoin/http/client.js b/lib/bcoin/http/client.js index a670f527..843bb8de 100644 --- a/lib/bcoin/http/client.js +++ b/lib/bcoin/http/client.js @@ -43,15 +43,6 @@ function HTTPClient(options) { this.auth = options.auth; this.rpc = new RPCClient(options); - if (this.apiKey) { - if (typeof this.apiKey === 'string') { - assert(utils.isHex(this.apiKey), 'API key must be a hex string.'); - this.apiKey = new Buffer(this.apiKey, 'hex'); - } - assert(Buffer.isBuffer(this.apiKey)); - assert(this.apiKey.length === 32, 'API key must be 32 bytes.'); - } - // Open automatically. this.open(); } @@ -125,8 +116,7 @@ HTTPClient.prototype._open = function _open(callback) { }); this.socket.on('connect', function() { - var apiKey = self.apiKey ? self.apiKey.toString('hex') : null; - self.socket.emit('auth', apiKey, function(err) { + self.socket.emit('auth', self.apiKey, function(err) { if (err) return callback(new Error(err.error)); callback(); @@ -208,19 +198,15 @@ HTTPClient.prototype._request = function _request(method, endpoint, json, callba json = null; } - if (json && method === 'get') { - query = json; - json = true; + if (this.token) { + if (!json) + json = {}; + json.token = this.token; } - if (this.apiKey) { - if (method === 'get') { - query = query || {}; - query.apiKey = this.apiKey.toString('hex'); - } else { - json = json || {}; - json.apiKey = this.apiKey.toString('hex'); - } + if (json && method === 'get') { + query = json; + json = null; } request({ @@ -228,7 +214,10 @@ HTTPClient.prototype._request = function _request(method, endpoint, json, callba uri: this.uri + endpoint, query: query, json: json, - auth: this.auth, + auth: { + username: 'bitcoinrpc', + password: this.apiKey || '' + }, expect: 'json' }, function(err, res, body) { if (err) @@ -572,9 +561,7 @@ HTTPClient.prototype.getBlock = function getBlock(hash, callback) { */ HTTPClient.prototype.broadcast = function broadcast(tx, callback) { - var body = { tx: tx.toRaw().toString('hex') }; - - callback = utils.ensure(callback); + var body = { tx: toHex(tx) }; return this._post('/broadcast', body, callback); }; @@ -598,15 +585,11 @@ HTTPClient.prototype.walletSend = function walletSend(id, options, callback) { options.outputs = options.outputs.map(function(output) { return { value: utils.btc(output.value), - address: output.address && output.address.toBase58 - ? output.address.toBase58() - : output.address, - script: output.script ? output.script.toRaw().toString('hex') : null + address: output.address, + script: toHex(output.script) }; }); - callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/send', options, callback); }; @@ -619,8 +602,6 @@ HTTPClient.prototype.walletSend = function walletSend(id, options, callback) { HTTPClient.prototype.walletRetoken = function walletRetoken(id, passphrase, callback) { var options = { passphrase: passphrase }; - callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/retoken', options, function(err, body) { if (err) return callback(err); @@ -639,8 +620,6 @@ HTTPClient.prototype.walletRetoken = function walletRetoken(id, passphrase, call HTTPClient.prototype.walletSetPassphrase = function walletSetPassphrase(id, old, new_, callback) { var options = { old: old, passphrase: new_ }; - callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/passphrase', options, callback); }; @@ -653,7 +632,6 @@ HTTPClient.prototype.walletSetPassphrase = function walletSetPassphrase(id, old, HTTPClient.prototype.walletCreate = function walletCreate(id, options, callback) { options = utils.merge({}, options); - options.outputs = options.outputs || []; if (options.rate) options.rate = utils.btc(options.rate); @@ -661,15 +639,11 @@ HTTPClient.prototype.walletCreate = function walletCreate(id, options, callback) options.outputs = options.outputs.map(function(output) { return { value: utils.btc(output.value), - address: output.address && output.address.toBase58 - ? output.address.toBase58() - : output.address, - script: output.script ? output.script.toRaw().toString('hex') : null + address: output.address, + script: toHex(output.script) }; }); - callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/create', options, callback); }; @@ -686,14 +660,11 @@ HTTPClient.prototype.walletSign = function walletCreate(id, tx, options, callbac if (typeof options === 'function') { callback = options; - options = null; + options = {}; } - body = utils.merge({}, options || {}, { - tx: tx.toRaw().toString('hex') - }); - - callback = utils.ensure(callback); + body = utils.merge({}, options); + body.tx = toHex(tx); return this._post('/wallet/' + id + '/sign', body, callback); }; @@ -705,10 +676,7 @@ HTTPClient.prototype.walletSign = function walletCreate(id, tx, options, callbac */ HTTPClient.prototype.walletFill = function walletFill(tx, callback) { - var body = { tx: tx.toRaw().toString('hex') }; - - callback = utils.ensure(callback); - + var body = { tx: toHex(tx) }; return this._post('/wallet/_/fill', body, callback); }; @@ -735,8 +703,6 @@ HTTPClient.prototype.walletZap = function walletZap(id, account, age, callback) assert(utils.isNumber(age)); - callback = utils.ensure(callback); - return this._post('/wallet/' + id + '/zap', body, callback); }; @@ -761,8 +727,6 @@ HTTPClient.prototype.addKey = function addKey(id, account, key, callback) { key = key.xpubkey || key; options = { account: account, key: key }; - callback = utils.ensure(callback); - return this._put('/wallet/' + id + '/key', options, callback); }; @@ -787,8 +751,6 @@ HTTPClient.prototype.removeKey = function removeKey(id, account, key, callback) key = key.xpubkey || key; options = { account: account, key: key }; - callback = utils.ensure(callback); - return this._del('/wallet/' + id + '/key', options, callback); }; @@ -848,6 +810,23 @@ HTTPClient.prototype.getInfo = function getInfo(callback) { return this._get('/', callback); }; +/* + * Helpers + */ + +function toHex(obj) { + if (!obj) + return; + + if (obj.toRaw) + obj = obj.toRaw(); + + if (Buffer.isBuffer(obj)) + obj = obj.toString('hex'); + + return obj; +} + /* * Expose */ diff --git a/lib/bcoin/http/rpcclient.js b/lib/bcoin/http/rpcclient.js index fac4b22b..08a068f2 100644 --- a/lib/bcoin/http/rpcclient.js +++ b/lib/bcoin/http/rpcclient.js @@ -35,23 +35,13 @@ function RPCClient(options) { this.uri = options.uri || 'http://localhost:' + this.network.rpcPort; this.apiKey = options.apiKey; this.id = 0; - - if (this.apiKey) { - if (typeof this.apiKey === 'string') { - assert(utils.isHex(this.apiKey), 'API key must be a hex string.'); - this.apiKey = new Buffer(this.apiKey, 'hex'); - } - assert(Buffer.isBuffer(this.apiKey)); - assert(this.apiKey.length === 32, 'API key must be 32 bytes.'); - } } /** - * Make an http request to endpoint. + * Make a json rpc request. * @private - * @param {String} method - * @param {String} endpoint - Path. - * @param {Object} json - Body or query depending on method. + * @param {String} method - RPC method name. + * @param {Array} params - RPC parameters. * @param {Function} callback - Returns [Error, Object?]. */ @@ -68,7 +58,7 @@ RPCClient.prototype.call = function call(method, params, callback) { }, auth: { username: 'bitcoinrpc', - password: this.apiKey ? this.apiKey.toString('hex') : '' + password: this.apiKey || '' }, expect: 'json' }, function(err, res, body) { diff --git a/lib/bcoin/http/server.js b/lib/bcoin/http/server.js index 361d9854..37319f7d 100644 --- a/lib/bcoin/http/server.js +++ b/lib/bcoin/http/server.js @@ -49,21 +49,21 @@ function HTTPServer(options) { this.logger = options.logger || this.node.logger; this.loaded = false; this.apiKey = options.apiKey; + this.apiHash = null; this.rpc = null; - if (this.apiKey) { - if (typeof this.apiKey === 'string') { - assert(utils.isHex(this.apiKey), 'API key must be a hex string.'); - this.apiKey = new Buffer(this.apiKey, 'hex'); - } - assert(Buffer.isBuffer(this.apiKey)); - assert(this.apiKey.length === 32, 'API key must be 32 bytes.'); - } else { - this.apiKey = bcoin.ec.random(32); - } + if (!this.apiKey) + this.apiKey = utils.toBase58(bcoin.ec.random(20)); - if (options.noAuth) + assert(typeof this.apiKey === 'string', 'API key must be a string.'); + assert(this.apiKey.length <= 200, 'API key must be under 200 bytes.'); + + this.apiHash = hash256(this.apiKey); + + if (options.noAuth) { this.apiKey = null; + this.apiHash = null; + } options.sockets = true; @@ -124,7 +124,6 @@ HTTPServer.prototype._init = function _init() { auth = new Buffer(parts[1], 'base64').toString('utf8'); parts = auth.split(':'); - assert(parts.length >= 2, 'Invalid auth token.'); req.username = parts.shift(); req.password = parts.join(':'); @@ -132,17 +131,37 @@ HTTPServer.prototype._init = function _init() { next(); }); + this.use(function(req, res, next, send) { + if (!self.apiHash) + return next(); + + if (utils.ccmp(hash256(req.password), self.apiHash)) + return next(); + + res.setHeader('WWW-Authenticate', 'Basic realm="node"'); + + if (req.method === 'POST' + && req.pathname === '/') { + send(401, { + result: null, + error: { + message: 'Bad auth.', + code: 1 + }, + id: req.body.id + }); + return; + } + + send(401, { error: 'Bad API key.' }); + }); + this.use(function(req, res, next, send) { var params, options; if (req.method === 'POST' && req.pathname === '/') { req.options = {}; - if (self.apiKey) { - assert(utils.isHex(req.password), 'API key must be a hex string.'); - assert(req.password.length === 64, 'API key must be 32 bytes.'); - req.password = new Buffer(req.password, 'hex'); - } return next(); } @@ -242,19 +261,10 @@ HTTPServer.prototype._init = function _init() { if (params.passphrase) options.passphrase = params.passphrase; - if (req.password) { - assert(utils.isHex(req.password), 'API key must be a hex string.'); - assert(req.password.length === 64, 'API key must be 32 bytes.'); - options.token = new Buffer(req.password, 'hex'); - } - - if (req.headers['x-bcoin-api-key']) - params.apiKey = req.headers['x-bcoin-api-key']; - - if (params.apiKey) { - assert(utils.isHex(params.apiKey), 'API key must be a hex string.'); - assert(params.apiKey.length === 64, 'API key must be 32 bytes.'); - options.apiKey = new Buffer(params.apiKey, 'hex'); + if (params.token) { + assert(utils.isHex(params.token), 'API key must be a hex string.'); + assert(params.token.length === 64, 'API key must be 32 bytes.'); + options.token = new Buffer(params.token, 'hex'); } req.options = options; @@ -263,18 +273,6 @@ HTTPServer.prototype._init = function _init() { }); this.use(function(req, res, next, send) { - if (req.method === 'POST' - && req.pathname === '/') { - return next(); - } - - if (self.apiKey) { - if (!utils.ccmp(req.options.apiKey, self.apiKey)) { - send(403, { error: 'Forbidden.' }); - return; - } - } - if (req.path.length < 2 || req.path[0] !== 'wallet') return next(); @@ -296,8 +294,7 @@ HTTPServer.prototype._init = function _init() { if (err) { self.logger.info('Auth failure for %s: %s.', req.options.id, err.message); - res.setHeader('WWW-Authenticate', 'Basic realm="wallet"'); - send(401, { error: err.message }); + send(403, { error: err.message }); return; } @@ -323,21 +320,6 @@ HTTPServer.prototype._init = function _init() { if (!(req.body.method && req.body.params)) return next(new Error('Method not found.')); - if (self.apiKey) { - if (!utils.ccmp(req.password, self.apiKey)) { - res.setHeader('WWW-Authenticate', 'Basic realm="rpc"'); - send(401, { - result: null, - error: { - message: 'Bad auth.', - code: 1 - }, - id: req.body.id - }); - return; - } - } - if (!self.rpc) { RPC = require('./rpc'); self.rpc = new RPC(self.node); @@ -548,10 +530,7 @@ HTTPServer.prototype._init = function _init() { if (err) return next(err); - json = wallet.toJSON(); - wallet.destroy(); - - send(200, json); + send(200, wallet.toJSON()); }); }); @@ -561,9 +540,6 @@ HTTPServer.prototype._init = function _init() { if (err) return next(err); - if (accounts.length === 0) - return send(404); - send(200, accounts); }); }); @@ -759,9 +735,6 @@ HTTPServer.prototype._init = function _init() { if (err) return next(err); - if (!txs.length) - return send(404); - req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -780,9 +753,6 @@ HTTPServer.prototype._init = function _init() { if (err) return next(err); - if (!txs.length) - return send(404); - req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -802,9 +772,6 @@ HTTPServer.prototype._init = function _init() { if (err) return next(err); - if (!txs.length) - return send(404); - req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -816,17 +783,14 @@ HTTPServer.prototype._init = function _init() { }); }); - // Wallet TXs within time range + // Last Wallet TXs this.get('/wallet/:id/tx/last', function(req, res, next, send) { var account = req.options.account; var limit = req.options.limit; - req.wallet.getRange(account, limit, function(err, txs) { + req.wallet.getLast(account, limit, function(err, txs) { if (err) return next(err); - if (!txs.length) - return send(404); - req.wallet.toDetails(txs, function(err, txs) { if (err) return next(err); @@ -885,19 +849,14 @@ HTTPServer.prototype._initIO = function _initIO() { socket.on('auth', function(apiKey, callback) { callback = utils.ensure(callback); - if (!self.apiKey) { + if (!self.apiHash) { self.logger.info('Successful auth.'); socket.bcoin.stopTimeout(); self.emit('websocket', socket); return callback(); } - if (!utils.isHex(apiKey)) - return callback({ error: 'Bad key.' }); - - apiKey = new Buffer(apiKey, 'hex'); - - if (!utils.ccmp(apiKey, self.apiKey)) + if (!utils.ccmp(hash256(apiKey), self.apiHash)) return callback({ error: 'Bad key.' }); self.logger.info('Successful auth.'); @@ -990,10 +949,12 @@ HTTPServer.prototype._initIO = function _initIO() { */ HTTPServer.prototype.open = function open(callback) { - if (this.apiKey) - this.logger.info('API key: %s', this.apiKey.toString('hex')); - else + if (this.apiKey) { + this.logger.info('API key: %s', this.apiKey); + this.apiKey = null; + } else if (!this.apiHash) { this.logger.warning('WARNING: Your http server is open to the world.'); + } this.server.open(callback); }; @@ -1103,6 +1064,18 @@ ClientSocket.prototype.destroy = function() { this.socket.disconnect(); }; +/* + * Helpers + */ + +function hash256(data) { + if (typeof data !== 'string') + return new Buffer(0); + if (data.length > 200) + return new Buffer(0); + return utils.hash256(new Buffer(data, 'utf8')); +} + /* * Expose */ diff --git a/lib/bcoin/http/wallet.js b/lib/bcoin/http/wallet.js index 6ecfefef..e5bfc040 100644 --- a/lib/bcoin/http/wallet.js +++ b/lib/bcoin/http/wallet.js @@ -91,14 +91,13 @@ HTTPWallet.prototype._init = function _init() { HTTPWallet.prototype.open = function open(options, callback) { var self = this; - if (Buffer.isBuffer(options.token)) - options.token = options.token.toString('hex'); - this.id = options.id; if (options.token) { - this.client.auth = { username: 'x', password: options.token }; - this.token = new Buffer(options.token, 'hex'); + this.token = options.token; + if (Buffer.isBuffer(this.token)) + this.token = this.token.toString('hex'); + this.client.token = this.token; } this.client.open(function(err) { @@ -272,6 +271,14 @@ HTTPWallet.prototype.getAccounts = function getAccounts(callback) { return this.client.getWalletAccounts(this.id, callback); }; +/** + * @see Wallet#getAccount + */ + +HTTPWallet.prototype.getAccount = function getAccount(options, callback) { + return this.client.getWalletAccount(this.id, options, callback); +}; + /** * @see Wallet#createAccount */ @@ -298,8 +305,8 @@ HTTPWallet.prototype.retoken = function retoken(passphrase, callback) { if (err) return callback(err); - self.client.auth = { username: 'x', password: token }; - self.token = new Buffer(token, 'hex'); + self.token = token; + self.client.token = token; return callback(null, token); }); diff --git a/test/http-test.js b/test/http-test.js index fdc25f49..0d5251d5 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -29,20 +29,19 @@ var dummyInput = { describe('HTTP', function() { var request = bcoin.http.request; - var apiKey = utils.hash256(new Buffer([])); var w, addr, hash; this.timeout(15000); var node = new bcoin.fullnode({ network: 'regtest', - apiKey: apiKey, + apiKey: 'foo', walletAuth: true }); var wallet = new bcoin.http.wallet({ network: 'regtest', - apiKey: apiKey + apiKey: 'foo' }); node.on('error', function() {});