http: better api key handling.
This commit is contained in:
parent
2b630ad99c
commit
30b7421ea7
@ -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
|
||||
*/
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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() {});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user