From 5677964651fdb32777365b03aecca2a65c8da480 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Wed, 9 Sep 2015 16:39:21 -0400 Subject: [PATCH 1/2] add https to web service --- lib/services/web.js | 42 ++++++++++++++- test/services/web.unit.js | 105 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/lib/services/web.js b/lib/services/web.js index b5b2bca5..af74a1c7 100644 --- a/lib/services/web.js +++ b/lib/services/web.js @@ -1,6 +1,7 @@ 'use strict'; var http = require('http'); +var https = require('https'); var express = require('express'); var bodyParser = require('body-parser'); var socketio = require('socket.io'); @@ -8,10 +9,13 @@ var BaseService = require('../service'); var inherits = require('util').inherits; var index = require('../'); var log = index.log; +var fs = require('fs'); var WebService = function(options) { var self = this; this.node = options.node; + this.https = options.https; + this.httpsOptions = options.httpsOptions; this.port = options.port || this.node.port || 3456; this.node.on('ready', function() { @@ -24,14 +28,24 @@ var WebService = function(options) { inherits(WebService, BaseService); -WebService.dependencies = []; +WebService.dependencies = ['bitcoind']; WebService.prototype.start = function(callback) { var self = this; this.app = express(); this.app.use(bodyParser.json()); - this.server = http.createServer(this.app); + // If https options are not specified, default to https if bitcoind is using https + if(this.https === undefined && this.node.services.bitcoind.configuration.rpcssl) { + this.https = true; + } + + if(this.https) { + this.deriveHttpsOptions(); + this.server = https.createServer(this.httpsOptions, this.app); + } else { + this.server = http.createServer(this.app); + } this.io = socketio.listen(this.server); this.io.on('connection', this.socketHandler.bind(this)); @@ -186,4 +200,28 @@ WebService.prototype.socketMessageHandler = function(message, socketCallback) { } }; +WebService.prototype.deriveHttpsOptions = function() { + var options = {}; + + var keyFile; + var certFile; + + if(this.httpsOptions) { + keyFile = this.httpsOptions.key; + certFile = this.httpsOptions.cert; + } else { + keyFile = this.node.services.bitcoind.configuration.rpcsslprivatekeyfile; + certFile = this.node.services.bitcoind.configuration.rpcsslcertificatechainfile; + } + + if(!keyFile || !certFile) { + throw new Error('Missing https options'); + } + + options.key = fs.readFileSync(keyFile); + options.cert = fs.readFileSync(certFile); + + this.httpsOptions = options; +}; + module.exports = WebService; diff --git a/test/services/web.unit.js b/test/services/web.unit.js index b0c34eaf..4987ea1f 100644 --- a/test/services/web.unit.js +++ b/test/services/web.unit.js @@ -2,17 +2,65 @@ var should = require('chai').should(); var sinon = require('sinon'); -var WebService = require('../../lib/services/web'); var EventEmitter = require('events').EventEmitter; +var proxyquire = require('proxyquire'); + +var httpStub = { + createServer: sinon.spy() +}; +var httpsStub = { + createServer: sinon.spy() +}; +var fsStub = { + readFileSync: function(arg1) { + return arg1 + '-buffer'; + } +}; + +var WebService = proxyquire('../../lib/services/web', {http: httpStub, https: httpsStub, fs: fsStub}); describe('WebService', function() { var defaultNode = new EventEmitter(); describe('#start', function() { - it('should call the callback with no error', function(done) { - var web = new WebService({node: defaultNode}); + beforeEach(function() { + httpStub.createServer.reset(); + httpsStub.createServer.reset(); + }); + it('should create an http server if no options are specified and bitcoind is not configured for https', function(done) { + var node = new EventEmitter(); + node.services = { + bitcoind: { + configuration: { + rpcssl: 0 + } + } + }; + + var web = new WebService({node: node}); + web.deriveHttpsOptions = sinon.spy(); web.start(function(err) { should.not.exist(err); + httpStub.createServer.called.should.equal(true); + done(); + }); + }); + + it('should create an https server if no options are specified and bitcoind is configured for https', function(done) { + var node = new EventEmitter(); + node.services = { + bitcoind: { + configuration: { + rpcssl: 1 + } + } + }; + + var web = new WebService({node: node}); + web.deriveHttpsOptions = sinon.spy(); + web.start(function(err) { + should.not.exist(err); + httpsStub.createServer.called.should.equal(true); done(); }); }); @@ -291,4 +339,55 @@ describe('WebService', function() { }); }); + describe('#deriveHttpsOptions', function() { + it('should use the httpsOptions from the config if specified', function() { + var web = new WebService({ + node: defaultNode, + https: true, + httpsOptions: { + key: 'key', + cert: 'cert' + } + }); + + web.deriveHttpsOptions(); + web.httpsOptions.key.should.equal('key-buffer'); + web.httpsOptions.cert.should.equal('cert-buffer'); + }); + + it('should use bitcoind\'s https options if there is no config', function() { + var node = new EventEmitter(); + node.services = { + bitcoind: { + configuration: { + rpcssl: 1, + rpcsslprivatekeyfile: 'rpckey', + rpcsslcertificatechainfile: 'rpccert' + } + } + }; + var web = new WebService({ + node: node + }); + + web.deriveHttpsOptions(); + web.httpsOptions.key.should.equal('rpckey-buffer'); + web.httpsOptions.cert.should.equal('rpccert-buffer'); + }); + + it('should throw an error if https is specified but key or cert is not specified', function() { + var web = new WebService({ + node: defaultNode, + https: true, + httpsOptions: { + key: 'key' + } + }); + + (function() { + web.deriveHttpsOptions(); + }).should.throw('Missing https options'); + }); + }); + }); \ No newline at end of file From 8b0b401d5225dd435721ba6bad49a38856adbe79 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 10 Sep 2015 11:06:37 -0400 Subject: [PATCH 2/2] inherit https options from node --- lib/node.js | 2 + lib/scaffold/create.js | 9 +--- lib/services/bitcoind.js | 8 +++- lib/services/web.js | 38 +++++------------ test/scaffold/create.integration.js | 22 ---------- test/services/bitcoind.unit.js | 30 +++++++++++++ test/services/web.unit.js | 66 ++++++++++------------------- 7 files changed, 73 insertions(+), 102 deletions(-) diff --git a/lib/node.js b/lib/node.js index ef8a90aa..908f0978 100644 --- a/lib/node.js +++ b/lib/node.js @@ -32,6 +32,8 @@ function Node(config) { $.checkState(config.datadir, 'Node config expects "datadir"'); this.datadir = config.datadir; this.port = config.port; + this.https = config.https; + this.httpsOptions = config.httpsOptions; this._setNetwork(config); diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index b698e6a9..12491b92 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -25,8 +25,6 @@ var BASE_PACKAGE = { } }; -var BASE_BITCOIN_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n'; - /** * Will create a directory and bitcoin.conf file for Bitcoin. * @param {String} dataDir - The absolute path @@ -38,12 +36,9 @@ function createBitcoinDirectory(datadir, done) { throw err; } - try { - fs.writeFileSync(datadir + '/bitcoin.conf', BASE_BITCOIN_CONFIG); - } catch(e) { - done(e); - } done(); + + // Don't create the configuration yet }); } diff --git a/lib/services/bitcoind.js b/lib/services/bitcoind.js index 5cd3319f..2eca3cb4 100644 --- a/lib/services/bitcoind.js +++ b/lib/services/bitcoind.js @@ -46,7 +46,13 @@ Bitcoin.prototype._loadConfiguration = function() { } if (!fs.existsSync(configPath)) { - fs.writeFileSync(configPath, Bitcoin.DEFAULT_CONFIG); + var defaultConfig = Bitcoin.DEFAULT_CONFIG; + if(this.node.https && this.node.httpsOptions) { + defaultConfig += 'rpcssl=1\n'; + defaultConfig += 'rpcsslprivatekeyfile=' + this.node.httpsOptions.key + '\n'; + defaultConfig += 'rpcsslcertificatechainfile=' + this.node.httpsOptions.cert + '\n'; + } + fs.writeFileSync(configPath, defaultConfig); } var file = fs.readFileSync(configPath); diff --git a/lib/services/web.js b/lib/services/web.js index af74a1c7..28372953 100644 --- a/lib/services/web.js +++ b/lib/services/web.js @@ -14,8 +14,8 @@ var fs = require('fs'); var WebService = function(options) { var self = this; this.node = options.node; - this.https = options.https; - this.httpsOptions = options.httpsOptions; + this.https = options.https || this.node.https; + this.httpsOptions = options.httpsOptions || this.node.httpsOptions; this.port = options.port || this.node.port || 3456; this.node.on('ready', function() { @@ -28,20 +28,15 @@ var WebService = function(options) { inherits(WebService, BaseService); -WebService.dependencies = ['bitcoind']; +WebService.dependencies = []; WebService.prototype.start = function(callback) { var self = this; this.app = express(); this.app.use(bodyParser.json()); - // If https options are not specified, default to https if bitcoind is using https - if(this.https === undefined && this.node.services.bitcoind.configuration.rpcssl) { - this.https = true; - } - if(this.https) { - this.deriveHttpsOptions(); + this.transformHttpsOptions(); this.server = https.createServer(this.httpsOptions, this.app); } else { this.server = http.createServer(this.app); @@ -200,28 +195,15 @@ WebService.prototype.socketMessageHandler = function(message, socketCallback) { } }; -WebService.prototype.deriveHttpsOptions = function() { - var options = {}; - - var keyFile; - var certFile; - - if(this.httpsOptions) { - keyFile = this.httpsOptions.key; - certFile = this.httpsOptions.cert; - } else { - keyFile = this.node.services.bitcoind.configuration.rpcsslprivatekeyfile; - certFile = this.node.services.bitcoind.configuration.rpcsslcertificatechainfile; - } - - if(!keyFile || !certFile) { +WebService.prototype.transformHttpsOptions = function() { + if(!this.httpsOptions || !this.httpsOptions.key || !this.httpsOptions.cert) { throw new Error('Missing https options'); } - options.key = fs.readFileSync(keyFile); - options.cert = fs.readFileSync(certFile); - - this.httpsOptions = options; + this.httpsOptions = { + key: fs.readFileSync(this.httpsOptions.key), + cert: fs.readFileSync(this.httpsOptions.cert) + }; }; module.exports = WebService; diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js index ffa01224..10378806 100644 --- a/test/scaffold/create.integration.js +++ b/test/scaffold/create.integration.js @@ -70,11 +70,9 @@ describe('#create', function() { var configPath = testDir + '/mynode/bitcore-node.json'; var packagePath = testDir + '/mynode/package.json'; - var bitcoinConfig = testDir + '/mynode/data/bitcoin.conf'; should.equal(fs.existsSync(configPath), true); should.equal(fs.existsSync(packagePath), true); - should.equal(fs.existsSync(bitcoinConfig), true); var config = JSON.parse(fs.readFileSync(configPath)); config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']); @@ -103,26 +101,6 @@ describe('#create', function() { }); - it('will not create bitcoin.conf if it already exists', function() { - - create({ - cwd: testDir, - dirname: 'mynode2', - name: 'My Node 2', - isGlobal: false, - datadir: '../.bitcoin' - }, function(err) { - if (err) { - throw err; - } - - var bitcoinConfig = testDir + '/.bitcoin/bitcoin.conf'; - should.equal(fs.existsSync(bitcoinConfig), false); - - }); - - }); - it('will not create a package.json if globally installed', function() { create({ diff --git a/test/services/bitcoind.unit.js b/test/services/bitcoind.unit.js index 0704c1ce..87633edc 100644 --- a/test/services/bitcoind.unit.js +++ b/test/services/bitcoind.unit.js @@ -75,6 +75,36 @@ describe('Bitcoin Service', function() { bitcoind._loadConfiguration({datadir: './test'}); }).should.throw('Txindex option'); }); + it('should set https options if node https options are set', function() { + var writeFileSync = function(path, config) { + config.should.equal('whitelist=127.0.0.1\ntxindex=1\nrpcssl=1\nrpcsslprivatekeyfile=key.pem\nrpcsslcertificatechainfile=cert.pem\n'); + }; + var TestBitcoin = proxyquire('../../lib/services/bitcoind', { + fs: { + writeFileSync: writeFileSync, + readFileSync: readFileSync, + existsSync: sinon.stub().returns(false) + }, + mkdirp: { + sync: sinon.stub() + } + }); + var config = { + node: { + datadir: 'testdir', + network: { + name: 'regtest' + }, + https: true, + httpsOptions: { + key: 'key.pem', + cert: 'cert.pem' + } + } + }; + var bitcoind = new TestBitcoin(config); + bitcoind._loadConfiguration({datadir: process.env.HOME + '/.bitcoin'}); + }); describe('reindex', function() { var log = require('../../lib/').log; var stub; diff --git a/test/services/web.unit.js b/test/services/web.unit.js index 4987ea1f..736bed2d 100644 --- a/test/services/web.unit.js +++ b/test/services/web.unit.js @@ -17,6 +17,20 @@ var fsStub = { } }; +var fakeSocketListener = new EventEmitter(); +var fakeSocket = new EventEmitter(); + +fakeSocket.on('test/event1', function(data) { + data.should.equal('testdata'); + done(); +}); + +fakeSocketListener.emit('connection', fakeSocket); + +fakeSocket.emit('subscribe', 'test/event1'); + + + var WebService = proxyquire('../../lib/services/web', {http: httpStub, https: httpsStub, fs: fsStub}); describe('WebService', function() { @@ -27,17 +41,8 @@ describe('WebService', function() { httpStub.createServer.reset(); httpsStub.createServer.reset(); }); - it('should create an http server if no options are specified and bitcoind is not configured for https', function(done) { - var node = new EventEmitter(); - node.services = { - bitcoind: { - configuration: { - rpcssl: 0 - } - } - }; - - var web = new WebService({node: node}); + it('should create an http server if no options are specified and node is not configured for https', function(done) { + var web = new WebService({node: defaultNode}); web.deriveHttpsOptions = sinon.spy(); web.start(function(err) { should.not.exist(err); @@ -46,18 +51,12 @@ describe('WebService', function() { }); }); - it('should create an https server if no options are specified and bitcoind is configured for https', function(done) { + it('should create an https server if no options are specified and node is configured for https', function(done) { var node = new EventEmitter(); - node.services = { - bitcoind: { - configuration: { - rpcssl: 1 - } - } - }; + node.https = true; var web = new WebService({node: node}); - web.deriveHttpsOptions = sinon.spy(); + web.transformHttpsOptions = sinon.spy(); web.start(function(err) { should.not.exist(err); httpsStub.createServer.called.should.equal(true); @@ -340,7 +339,7 @@ describe('WebService', function() { }); describe('#deriveHttpsOptions', function() { - it('should use the httpsOptions from the config if specified', function() { + it('should read key and cert from files specified', function() { var web = new WebService({ node: defaultNode, https: true, @@ -350,31 +349,10 @@ describe('WebService', function() { } }); - web.deriveHttpsOptions(); + web.transformHttpsOptions(); web.httpsOptions.key.should.equal('key-buffer'); web.httpsOptions.cert.should.equal('cert-buffer'); }); - - it('should use bitcoind\'s https options if there is no config', function() { - var node = new EventEmitter(); - node.services = { - bitcoind: { - configuration: { - rpcssl: 1, - rpcsslprivatekeyfile: 'rpckey', - rpcsslcertificatechainfile: 'rpccert' - } - } - }; - var web = new WebService({ - node: node - }); - - web.deriveHttpsOptions(); - web.httpsOptions.key.should.equal('rpckey-buffer'); - web.httpsOptions.cert.should.equal('rpccert-buffer'); - }); - it('should throw an error if https is specified but key or cert is not specified', function() { var web = new WebService({ node: defaultNode, @@ -385,7 +363,7 @@ describe('WebService', function() { }); (function() { - web.deriveHttpsOptions(); + web.transformHttpsOptions(); }).should.throw('Missing https options'); }); });