Merge remote-tracking branch 'refs/remotes/origin/master' into develop

# Conflicts:
#	package.json
#	src/views/admin/manage/ip-blacklist.tpl
This commit is contained in:
Barış Soner Uşaklı
2017-08-24 19:29:15 -04:00
371 changed files with 961 additions and 179 deletions

View File

@@ -211,3 +211,9 @@ Analytics.getErrorAnalytics = function (callback) {
}, callback);
};
Analytics.getBlacklistAnalytics = function (callback) {
async.parallel({
daily: async.apply(Analytics.getDailyStatsForSet, 'analytics:blacklist', Date.now(), 7),
hourly: async.apply(Analytics.getHourlyStatsForSet, 'analytics:blacklist', Date.now(), 24),
}, callback);
};

View File

@@ -2,19 +2,22 @@
var async = require('async');
var meta = require('../../meta');
var analytics = require('../../analytics');
var blacklistController = module.exports;
blacklistController.get = function (req, res, next) {
async.waterfall([
function (next) {
meta.blacklist.get(next);
},
function (rules) {
res.render('admin/manage/ip-blacklist', {
rules: rules,
title: '[[pages:ip-blacklist]]',
});
},
], next);
// Analytics.getBlacklistAnalytics
async.parallel({
rules: async.apply(meta.blacklist.get),
analytics: async.apply(analytics.getBlacklistAnalytics),
}, function (err, data) {
if (err) {
return next(err);
}
res.render('admin/manage/ip-blacklist', Object.assign(data, {
title: '[[pages:ip-blacklist]]',
}));
});
};

View File

@@ -288,7 +288,6 @@ authenticationController.doLogin = function (req, uid, callback) {
};
authenticationController.onSuccessfulLogin = function (req, uid, callback) {
callback = callback || function () {};
var uuid = utils.generateUUID();
req.session.meta = {};
@@ -308,6 +307,7 @@ authenticationController.onSuccessfulLogin = function (req, uid, callback) {
});
async.waterfall([
async.apply(meta.blacklist.test, req.ip),
function (next) {
async.parallel([
function (next) {
@@ -330,7 +330,17 @@ authenticationController.onSuccessfulLogin = function (req, uid, callback) {
plugins.fireHook('action:user.loggedIn', { uid: uid, req: req });
next();
},
], callback);
], function (err) {
if (err) {
req.session.destroy();
}
if (typeof callback === 'function') {
callback(err);
} else {
return false;
}
});
};
authenticationController.localLogin = function (req, username, password, next) {

View File

@@ -11,7 +11,7 @@ var meta = require('../meta');
var helpers = require('./helpers');
var pagination = require('../pagination');
var recentController = {};
var recentController = module.exports;
var validFilter = { '': true, new: true, watched: true };
@@ -47,49 +47,44 @@ recentController.get = function (req, res, next) {
topics.getRecentTopics(cid, req.uid, start, stop, filter, next);
},
], function (err, data) {
if (err) {
return next(err);
}
function (data) {
data.categories = categoryData.categories;
data.selectedCategory = categoryData.selectedCategory;
data.nextStart = stop + 1;
data.set = 'topics:recent';
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
data.rssFeedUrl = nconf.get('relative_path') + '/recent.rss';
data.title = '[[pages:recent]]';
data.filters = [{
name: '[[unread:all-topics]]',
url: 'recent',
selected: filter === '',
filter: '',
}, {
name: '[[unread:new-topics]]',
url: 'recent/new',
selected: filter === 'new',
filter: 'new',
}, {
name: '[[unread:watched-topics]]',
url: 'recent/watched',
selected: filter === 'watched',
filter: 'watched',
}];
data.categories = categoryData.categories;
data.selectedCategory = categoryData.selectedCategory;
data.nextStart = stop + 1;
data.set = 'topics:recent';
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
data.rssFeedUrl = nconf.get('relative_path') + '/recent.rss';
data.title = '[[pages:recent]]';
data.filters = [{
name: '[[unread:all-topics]]',
url: 'recent',
selected: filter === '',
filter: '',
}, {
name: '[[unread:new-topics]]',
url: 'recent/new',
selected: filter === 'new',
filter: 'new',
}, {
name: '[[unread:watched-topics]]',
url: 'recent/watched',
selected: filter === 'watched',
filter: 'watched',
}];
data.selectedFilter = data.filters.find(function (filter) {
return filter && filter.selected;
});
data.selectedFilter = data.filters.find(function (filter) {
return filter && filter.selected;
});
var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
data.pagination = pagination.create(page, pageCount, req.query);
var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
data.pagination = pagination.create(page, pageCount, req.query);
if (req.path.startsWith('/api/recent') || req.path.startsWith('/recent')) {
data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[recent:title]]' }]);
}
if (req.path.startsWith('/api/recent') || req.path.startsWith('/recent')) {
data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[recent:title]]' }]);
}
data.querystring = cid ? ('?cid=' + validator.escape(String(cid))) : '';
res.render('recent', data);
});
data.querystring = cid ? ('?cid=' + validator.escape(String(cid))) : '';
res.render('recent', data);
},
], next);
};
module.exports = recentController;

View File

@@ -41,11 +41,11 @@ module.exports = function (db, module) {
var query = { _key: { $in: keys } };
if (min !== '-inf') {
query.score = { $gte: min };
query.score = { $gte: parseFloat(min) };
}
if (max !== '+inf') {
query.score = query.score || {};
query.score.$lte = max;
query.score.$lte = parseFloat(max);
}
db.collection('objects').remove(query, function (err) {

View File

@@ -14,9 +14,9 @@ module.exports = function (Messaging) {
return next();
}
var keys = uids.map(function (uid) {
return 'uid:' + uid + ':chat:room:' + roomId + 'mids';
return 'uid:' + uid + ':chat:room:' + roomId + ':mids';
});
db.sortedSetsRemove(keys, roomId, next);
db.sortedSetsRemove(keys, mid, next);
},
function (next) {
db.delete('message:' + mid, next);

View File

@@ -1,12 +1,13 @@
'use strict';
var ip = require('ip');
var ipRangeCheck = require('ip-range-check');
var ipaddr = require('ipaddr.js');
var winston = require('winston');
var async = require('async');
var db = require('../database');
var pubsub = require('../pubsub');
var plugins = require('../plugins');
var analytics = require('../analytics');
var Blacklist = {
_rules: [],
@@ -54,23 +55,38 @@ Blacklist.get = function (callback) {
};
Blacklist.test = function (clientIp, callback) {
// Some handy test addresses
// clientIp = '2001:db8:85a3:0:0:8a2e:370:7334'; // IPv6
// clientIp = '127.0.15.1'; // IPv4
var addr = ipaddr.parse(clientIp);
if (
Blacklist._rules.ipv4.indexOf(clientIp) === -1 && // not explicitly specified in ipv4 list
Blacklist._rules.ipv6.indexOf(clientIp) === -1 && // not explicitly specified in ipv6 list
!Blacklist._rules.cidr.some(function (subnet) {
return ip.cidrSubnet(subnet).contains(clientIp);
}) && // not in a blacklisted IPv4 cidr range
!ipRangeCheck(clientIp, Blacklist._rules.cidr6) // not in a blacklisted IPv6 cidr range
return addr.match(ipaddr.parseCIDR(subnet));
// return ip.cidrSubnet(subnet).contains(clientIp);
}) // not in a blacklisted IPv4 or IPv6 cidr range
) {
if (typeof callback === 'function') {
setImmediate(callback);
} else {
return false;
}
plugins.fireHook('filter:blacklist.test', { // To return test failure, pass back an error in callback
ip: clientIp,
}, function (err) {
if (err) {
analytics.increment('blacklist');
}
if (typeof callback === 'function') {
callback(err);
} else {
return !!err;
}
});
} else {
var err = new Error('[[error:blacklisted-ip]]');
err.code = 'blacklisted-ip';
analytics.increment('blacklist');
if (typeof callback === 'function') {
setImmediate(callback, err);
} else {
@@ -84,11 +100,8 @@ Blacklist.validate = function (rules, callback) {
var ipv4 = [];
var ipv6 = [];
var cidr = [];
var cidr6 = [];
var invalid = [];
var isIPv4CidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/;
var isIPv6CidrSubnet = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/;
var inlineCommentMatch = /#.*$/;
var whitelist = ['127.0.0.1', '::1', '::ffff:0:127.0.0.1'];
@@ -101,29 +114,39 @@ Blacklist.validate = function (rules, callback) {
// Filter out invalid rules
rules = rules.filter(function (rule) {
if (whitelist.indexOf(rule) !== -1) {
var addr;
var isRange = false;
try {
addr = ipaddr.parse(rule);
} catch (e) {
// Do nothing
}
try {
addr = ipaddr.parseCIDR(rule);
isRange = true;
} catch (e) {
// Do nothing
}
if (!addr || whitelist.indexOf(rule) !== -1) {
invalid.push(rule);
return false;
}
if (ip.isV4Format(rule)) {
ipv4.push(rule);
return true;
}
if (ip.isV6Format(rule)) {
ipv6.push(rule);
return true;
}
if (isIPv4CidrSubnet.test(rule)) {
if (!isRange) {
if (addr.kind() === 'ipv4' && ipaddr.IPv4.isValid(rule)) {
ipv4.push(rule);
return true;
}
if (addr.kind() === 'ipv6' && ipaddr.IPv6.isValid(rule)) {
ipv6.push(rule);
return true;
}
} else {
cidr.push(rule);
return true;
}
if (isIPv6CidrSubnet.test(rule)) {
cidr.push(rule);
return true;
}
invalid.push(rule);
return false;
});
@@ -132,7 +155,6 @@ Blacklist.validate = function (rules, callback) {
ipv4: ipv4,
ipv6: ipv6,
cidr: cidr,
cidr6: cidr6,
valid: rules,
invalid: invalid,
});

View File

@@ -46,6 +46,7 @@ module.exports = function (middleware) {
res.locals.config = res.locals.config || {};
var templateValues = {
title: meta.config.title || '',
'title:url': meta.config['title:url'] || '',
description: meta.config.description || '',
'cache-buster': meta.config['cache-buster'] || '',
'brand:logo': meta.config['brand:logo'] || '',

View File

@@ -9,7 +9,26 @@
<div class="row">
<div class="col-sm-6">
<textarea class="form-control" id="blacklist-rules">{rules}</textarea>
<div class="panel panel-default">
<div class="panel-body">
<div><canvas id="blacklist:hourly" height="250"></canvas></div>
</div>
<div class="panel-footer"><small>[[admin/manage/ip-blacklist:analytics.blacklist-hourly]]</small></div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-body">
<div><canvas id="blacklist:daily" height="250"></canvas></div>
</div>
<div class="panel-footer"><small>[[admin/manage/ip-blacklist:analytics.blacklist-daily]]</small></div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<textarea id="blacklist-rules">{rules}</textarea>
</div>
<div class="col-sm-6">
<div class="panel panel-default">

View File

@@ -9,6 +9,12 @@
<label>[[admin/settings/general:title]]</label>
<input class="form-control" type="text" placeholder="[[admin/settings/general:title.name]]" data-field="title" />
<label for="title:url">[[admin/settings/general:title.url]]</label>
<input id ="title:url" type="text" class="form-control" placeholder="[[admin/settings/general:title.url-placeholder]]" data-field="title:url" />
<p class="help-block">
[[admin/settings/general:title.url-help]]
</p>
<div class="checkbox">
<label for="showSiteTitle" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" class="mdl-switch__input" id="showSiteTitle" data-field="showSiteTitle" name="showSiteTitle" />