Skip to content

Commit 2f7d82c

Browse files
committed
Merge branch 'release/0.4.0-beta'
2 parents 29f45b9 + 4a6f625 commit 2f7d82c

35 files changed

+1249
-542
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9+
## [0.4.0] - 2018-02-03
10+
11+
### Added
12+
- API basic HTTP auth
13+
14+
### Changed
15+
- Fixed orphaned neighbors check.
16+
- Fixed API security bug.
17+
918
## [0.3.22] - 2018-01-29
1019

1120
### Changed

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ You can provide one or more of the following options in your ini file. Example:
203203
name = My Nelson Node
204204
cycleInterval = 60
205205
epochInterval = 300
206+
apiAuth = username:password
206207
apiPort = 18600
207208
apiHostname = 127.0.0.1
208209
port = 16600
@@ -247,6 +248,7 @@ Some have additional short versions.
247248
| --name | Name your node. This identifier will appear in API/webhooks and for your neighbors ||
248249
| --neighbors, -n | space-separated list of entry Nelson neighbors ||
249250
| --getNeighbors | Downloads a list of entry Nelson neighbors. If no URL is provided, will use a default URL (https://raw.githubusercontent.com/SemkoDev/nelson.cli/master/ENTRYNODES). If this option is not set, no neighbors will be downloaded. This option can be used together with ````--neighbors`` |false|
251+
| --apiAuth| Add basic HTTP auth to API. Provide username and password in `user:pass` format||
250252
| --apiPort, -a | Nelson API port to request current node status data|18600|
251253
| --apiHostname, -o | Nelson API hostname to request current node status data. Default value will only listen to local connections|127.0.0.1|
252254
| --port, -p | TCP port, on which to start your Nelson instance|16600|
@@ -397,6 +399,13 @@ curl http://localhost:18600/peer-stats
397399
}
398400
```
399401

402+
if you use `apiAuth` option to protect your API, you will need to provide the authentication details
403+
in your requests:
404+
405+
```
406+
curl -u username:password http://localhost:18600
407+
```
408+
400409
### Webhooks
401410

402411
You can provide Nelson a list of webhook URLs that have to be regularly called back with all the node stats data.

dist/api/index.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict';
2+
3+
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
4+
5+
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6+
7+
var express = require('express');
8+
var helmet = require('helmet');
9+
var bodyParser = require('body-parser');
10+
var basicAuth = require('express-basic-auth');
11+
12+
var _require = require('./node'),
13+
getNodeStats = _require.getNodeStats,
14+
getSummary = _require.getSummary;
15+
16+
var _require2 = require('./peer'),
17+
getPeerStats = _require2.getPeerStats;
18+
19+
var _require3 = require('./webhooks'),
20+
startWebhooks = _require3.startWebhooks;
21+
22+
var DEFAULT_OPTIONS = {
23+
node: null,
24+
webhooks: [],
25+
webhookInterval: 30,
26+
username: null,
27+
password: null,
28+
apiPort: 18600,
29+
apiHostname: '127.0.0.1'
30+
};
31+
32+
/**
33+
* Creates an Express APP instance, also starts regular webhooks callbacks.
34+
* @param options
35+
* @returns {*|Function}
36+
*/
37+
function createAPI(options) {
38+
var opts = _extends({}, DEFAULT_OPTIONS, options);
39+
40+
// Start webhook callbacks
41+
if (opts.webhooks && opts.webhooks.length) {
42+
startWebhooks(opts.node, opts.webhooks, opts.webhookInterval);
43+
}
44+
45+
// Start API server
46+
var app = express();
47+
app.set('node', opts.node);
48+
49+
// Basic app protection
50+
app.use(helmet());
51+
52+
// Enable basic HTTP Auth
53+
if (opts.username && opts.password) {
54+
app.use(basicAuth({
55+
users: _defineProperty({}, opts.username, opts.password)
56+
}));
57+
}
58+
59+
// parse application/x-www-form-urlencoded
60+
app.use(bodyParser.urlencoded({ extended: false }));
61+
62+
// parse application/json
63+
app.use(bodyParser.json());
64+
65+
//////////////////////// ENDPOINTS ////////////////////////
66+
67+
app.get('/', function (req, res) {
68+
res.json(getNodeStats(opts.node));
69+
});
70+
71+
app.get('/peer-stats', function (req, res) {
72+
res.json(getSummary(opts.node));
73+
});
74+
75+
app.get('/peers', function (req, res) {
76+
res.json(opts.node.list.all().map(getPeerStats));
77+
});
78+
79+
return app.listen(opts.apiPort, opts.apiHostname);
80+
}
81+
82+
module.exports = {
83+
createAPI: createAPI,
84+
DEFAULT_OPTIONS: DEFAULT_OPTIONS
85+
};

dist/api/node.js

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
'use strict';
2+
3+
var _require = require('./peer'),
4+
getPeerStats = _require.getPeerStats;
5+
6+
var version = require('../../package.json').version;
7+
8+
/**
9+
* Returns summary of the node stats
10+
* @param {Node} node
11+
* @returns {{newNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}, activeNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}}}
12+
*/
13+
function getSummary(node) {
14+
var now = new Date();
15+
var hour = 3600000;
16+
var hourAgo = new Date(now - hour);
17+
var fourAgo = new Date(now - hour * 4);
18+
var twelveAgo = new Date(now - hour * 12);
19+
var dayAgo = new Date(now - hour * 24);
20+
var weekAgo = new Date(now - hour * 24 * 7);
21+
return {
22+
newNodes: {
23+
hourAgo: node.list.all().filter(function (p) {
24+
return p.data.dateCreated >= hourAgo;
25+
}).length,
26+
fourAgo: node.list.all().filter(function (p) {
27+
return p.data.dateCreated >= fourAgo;
28+
}).length,
29+
twelveAgo: node.list.all().filter(function (p) {
30+
return p.data.dateCreated >= twelveAgo;
31+
}).length,
32+
dayAgo: node.list.all().filter(function (p) {
33+
return p.data.dateCreated >= dayAgo;
34+
}).length,
35+
weekAgo: node.list.all().filter(function (p) {
36+
return p.data.dateCreated >= weekAgo;
37+
}).length
38+
},
39+
activeNodes: {
40+
hourAgo: node.list.all().filter(function (p) {
41+
return p.data.dateLastConnected >= hourAgo;
42+
}).length,
43+
fourAgo: node.list.all().filter(function (p) {
44+
return p.data.dateLastConnected >= fourAgo;
45+
}).length,
46+
twelveAgo: node.list.all().filter(function (p) {
47+
return p.data.dateLastConnected >= twelveAgo;
48+
}).length,
49+
dayAgo: node.list.all().filter(function (p) {
50+
return p.data.dateLastConnected >= dayAgo;
51+
}).length,
52+
weekAgo: node.list.all().filter(function (p) {
53+
return p.data.dateLastConnected >= weekAgo;
54+
}).length
55+
}
56+
};
57+
}
58+
59+
/**
60+
* Returns clean node stats to be used in the API
61+
* @param {Node} node
62+
* @returns {{name, version, ready: (boolean|*|null), isIRIHealthy: (*|boolean), iriStats: *, peerStats: {newNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}, activeNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}}, totalPeers, connectedPeers: Array, config: {cycleInterval: (Command.opts.cycleInterval|*), epochInterval: (Command.opts.epochInterval|*), beatInterval: (Command.opts.beatInterval|*), dataPath: (Command.opts.dataPath|*), port: (Command.opts.port|*), apiPort: (Command.opts.apiPort|*), IRIPort: (Command.opts.IRIPort|*), TCPPort: (Command.opts.TCPPort|*), UDPPort: (Command.opts.UDPPort|*), IRIProtocol: (Command.opts.IRIProtocol|*), isMaster: (Command.opts.isMaster|*), temporary: (Command.opts.temporary|*)}, heart: {lastCycle: (heart.lastCycle|Heart.lastCycle|_require2.Heart.lastCycle), lastEpoch: (heart.lastEpoch|Heart.lastEpoch|_require2.Heart.lastEpoch), personality: (heart.personality|Heart.personality|_require2.Heart.personality), currentCycle: (heart.currentCycle|Heart.currentCycle|_require2.Heart.currentCycle), currentEpoch: (heart.currentEpoch|Heart.currentEpoch|_require2.Heart.currentEpoch), startDate: (heart.startDate|Heart.startDate|_require2.Heart.startDate)}}}
63+
*/
64+
function getNodeStats(node) {
65+
var _node$opts = node.opts,
66+
cycleInterval = _node$opts.cycleInterval,
67+
epochInterval = _node$opts.epochInterval,
68+
beatInterval = _node$opts.beatInterval,
69+
dataPath = _node$opts.dataPath,
70+
port = _node$opts.port,
71+
apiPort = _node$opts.apiPort,
72+
IRIPort = _node$opts.IRIPort,
73+
TCPPort = _node$opts.TCPPort,
74+
UDPPort = _node$opts.UDPPort,
75+
isMaster = _node$opts.isMaster,
76+
IRIProtocol = _node$opts.IRIProtocol,
77+
temporary = _node$opts.temporary;
78+
var _node$heart = node.heart,
79+
lastCycle = _node$heart.lastCycle,
80+
lastEpoch = _node$heart.lastEpoch,
81+
personality = _node$heart.personality,
82+
currentCycle = _node$heart.currentCycle,
83+
currentEpoch = _node$heart.currentEpoch,
84+
startDate = _node$heart.startDate;
85+
86+
var totalPeers = node.list.all().length;
87+
var isIRIHealthy = node.iri && node.iri.isHealthy;
88+
var iriStats = node.iri && node.iri.iriStats;
89+
var connectedPeers = Array.from(node.sockets.keys()).filter(function (p) {
90+
return node.sockets.get(p).readyState === 1;
91+
}).map(getPeerStats);
92+
93+
return {
94+
name: node.opts.name,
95+
version: version,
96+
ready: node._ready,
97+
isIRIHealthy: isIRIHealthy,
98+
iriStats: iriStats,
99+
peerStats: getSummary(node),
100+
totalPeers: totalPeers,
101+
connectedPeers: connectedPeers,
102+
config: {
103+
cycleInterval: cycleInterval,
104+
epochInterval: epochInterval,
105+
beatInterval: beatInterval,
106+
dataPath: dataPath,
107+
port: port,
108+
apiPort: apiPort,
109+
IRIPort: IRIPort,
110+
TCPPort: TCPPort,
111+
UDPPort: UDPPort,
112+
IRIProtocol: IRIProtocol,
113+
isMaster: isMaster,
114+
temporary: temporary
115+
},
116+
heart: {
117+
lastCycle: lastCycle,
118+
lastEpoch: lastEpoch,
119+
personality: personality,
120+
currentCycle: currentCycle,
121+
currentEpoch: currentEpoch,
122+
startDate: startDate
123+
}
124+
};
125+
}
126+
127+
module.exports = {
128+
getSummary: getSummary,
129+
getNodeStats: getNodeStats
130+
};

dist/api/peer.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use strict";
2+
3+
/**
4+
* Returns a clean Peer object that can be used in the API
5+
* @param {Peer} peer
6+
* @returns {{name, hostname, ip, port, TCPPort, UDPPort, protocol, IRIProtocol, seen, connected, tried, weight, dateTried, dateLastConnected, dateCreated, isTrusted, lastConnections}}
7+
*/
8+
function getPeerStats(peer) {
9+
var _peer$data = peer.data,
10+
name = _peer$data.name,
11+
hostname = _peer$data.hostname,
12+
ip = _peer$data.ip,
13+
port = _peer$data.port,
14+
TCPPort = _peer$data.TCPPort,
15+
UDPPort = _peer$data.UDPPort,
16+
protocol = _peer$data.protocol,
17+
seen = _peer$data.seen,
18+
connected = _peer$data.connected,
19+
tried = _peer$data.tried,
20+
weight = _peer$data.weight,
21+
dateTried = _peer$data.dateTried,
22+
dateLastConnected = _peer$data.dateLastConnected,
23+
dateCreated = _peer$data.dateCreated,
24+
IRIProtocol = _peer$data.IRIProtocol,
25+
isTrusted = _peer$data.isTrusted,
26+
lastConnections = _peer$data.lastConnections;
27+
28+
return {
29+
name: name,
30+
hostname: hostname,
31+
ip: ip,
32+
port: port,
33+
TCPPort: TCPPort,
34+
UDPPort: UDPPort,
35+
protocol: protocol,
36+
IRIProtocol: IRIProtocol,
37+
seen: seen,
38+
connected: connected,
39+
tried: tried,
40+
weight: weight,
41+
dateTried: dateTried,
42+
dateLastConnected: dateLastConnected,
43+
dateCreated: dateCreated,
44+
isTrusted: isTrusted,
45+
lastConnections: lastConnections
46+
};
47+
}
48+
49+
module.exports = {
50+
getPeerStats: getPeerStats
51+
};

dist/api/utils.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"use strict";

dist/api/webhooks.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
var request = require('request');
4+
5+
var _require = require('./node'),
6+
getNodeStats = _require.getNodeStats;
7+
8+
function startWebhooks(node, webhooks, webhookInterval) {
9+
var interval = setInterval(function () {
10+
webhooks.forEach(function (uri) {
11+
return request({
12+
uri: uri,
13+
method: 'POST',
14+
json: getNodeStats(node)
15+
}, function (err) {
16+
if (err) {
17+
node.log(('Webhook returned error: ' + uri).yellow);
18+
}
19+
});
20+
});
21+
}, webhookInterval * 1000);
22+
return {
23+
stop: function stop() {
24+
clearInterval(interval);
25+
}
26+
};
27+
}
28+
29+
module.exports = {
30+
startWebhooks: startWebhooks
31+
};

dist/index.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require('colors');
88
var request = require('request');
99
var terminal = require('./node/tools/terminal');
1010
var node = require('./node').node;
11-
var api = require('./node').api;
11+
var api = require('./api/index');
1212
var utils = require('./node').utils;
1313

1414
// Some general TODOs:
@@ -30,7 +30,15 @@ module.exports = _extends({
3030
opts.gui && terminal.init(opts.name, utils.getVersion(), terminate);
3131

3232
_node.start().then(function (n) {
33-
api.createAPI(n, opts.webhooks, opts.webhookInterval);
33+
api.createAPI({
34+
node: n,
35+
webhooks: opts.webhooks,
36+
webhookInterval: opts.webhookInterval,
37+
apiPort: opts.apiPort,
38+
apiHostname: opts.apiHostname,
39+
username: opts.apiAuth && opts.apiAuth.username,
40+
password: opts.apiAuth && opts.apiAuth.password
41+
});
3442
terminal.ports(n.opts);
3543
n.log(('Nelson v.' + utils.getVersion() + ' initialized').green.bold);
3644
});

0 commit comments

Comments
 (0)