IP blacklist functionality -- re: #4367
Squashed commit of the following: commit 5c42fd732d091fa66cf5b45a2af5e1697cc1efcd Author: Julian Lam <julian@nodebb.org> Date: Mon Mar 14 17:29:45 2016 -0400 allowing blacklist.test to be called synchronously commit 979faf2dba5e6f6e2ae1bd07341e63678438daf1 Author: Julian Lam <julian@nodebb.org> Date: Mon Mar 14 17:01:14 2016 -0400 added plain ipv6 support and finished middleware logic commit d4b72fc1aadff34df3ed7dec52ca8d3c3728a078 Author: Julian Lam <julian@designcreateplay.com> Date: Fri Mar 11 16:05:31 2016 -0500 WIP IP Banning logic middleware commit f08b2553890c5522b6a1eaf521fe4e94df40574a Author: Julian Lam <julian@designcreateplay.com> Date: Fri Mar 11 15:26:27 2016 -0500 tweaks to ACP, rule validator commit 868abacaa494e6b8a88bd4ea429b1b066a9ecb2e Author: Julian Lam <julian@designcreateplay.com> Date: Fri Mar 11 13:50:05 2016 -0500 IP Banning ACP page, styling, save&load functionalityv1.18.x
parent
71c697037d
commit
184a2c4540
@ -0,0 +1,5 @@
|
||||
#blacklist-rules {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
/* globals $, app, socket, templates */
|
||||
|
||||
define('admin/manage/ip-blacklist', ['settings'], function(Settings) {
|
||||
|
||||
var Blacklist = {};
|
||||
|
||||
Blacklist.init = function() {
|
||||
var blacklist = ace.edit("blacklist-rules");
|
||||
|
||||
blacklist.on('change', function(e) {
|
||||
$('#blacklist-rules-holder').val(blacklist.getValue());
|
||||
});
|
||||
|
||||
Settings.load('blacklist', $('.blacklist-settings'), function(err, settings) {
|
||||
blacklist.setValue(settings.rules);
|
||||
});
|
||||
|
||||
$('[data-action="apply"]').on('click', function() {
|
||||
Settings.save('blacklist', $('.blacklist-settings'), function() {
|
||||
app.alert({
|
||||
type: 'success',
|
||||
alert_id: 'blacklist-saved',
|
||||
title: 'Blacklist Applied',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('[data-action="test"]').on('click', function() {
|
||||
socket.emit('admin.blacklist.validate', {
|
||||
rules: blacklist.getValue()
|
||||
}, function(err, data) {
|
||||
templates.parse('admin/partials/blacklist-validate', data, function(html) {
|
||||
bootbox.alert(html);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return Blacklist;
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
var blacklistController = {};
|
||||
|
||||
blacklistController.get = function(req, res, next) {
|
||||
res.render('admin/manage/ip-blacklist', {});
|
||||
};
|
||||
|
||||
module.exports = blacklistController;
|
@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
|
||||
var ip = require('ip'),
|
||||
winston = require('winston'),
|
||||
async = require('async');
|
||||
|
||||
var meta = module.parent.exports;
|
||||
|
||||
var Blacklist = {
|
||||
_rules: []
|
||||
};
|
||||
|
||||
Blacklist.load = function(callback) {
|
||||
async.waterfall([
|
||||
async.apply(meta.settings.getOne, 'blacklist', 'rules'),
|
||||
async.apply(Blacklist.validate)
|
||||
], function(err, rules) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
winston.verbose('[meta/blacklist] Loading ' + rules.valid.length + ' blacklist rules');
|
||||
if (rules.invalid.length) {
|
||||
winston.warn('[meta/blacklist] ' + rules.invalid.length + ' invalid blacklist rule(s) were ignored.');
|
||||
}
|
||||
|
||||
Blacklist._rules = {
|
||||
ipv4: rules.ipv4,
|
||||
ipv6: rules.ipv6,
|
||||
cidr: rules.cidr
|
||||
};
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
Blacklist.test = function(clientIp, callback) {
|
||||
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 cidr range
|
||||
) {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
var err = new Error('[[error:blacklisted-ip]]');
|
||||
err.code = 'blacklisted-ip';
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(err);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blacklist.validate = function(rules, callback) {
|
||||
var rules = (rules || '').split('\n'),
|
||||
ipv4 = [],
|
||||
ipv6 = [],
|
||||
cidr = [],
|
||||
invalid = [];
|
||||
|
||||
var isCidrSubnet = /^(([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]))$/;
|
||||
|
||||
// Filter out blank lines and lines starting with the hash character (comments)
|
||||
rules = rules.map(function(rule) {
|
||||
rule = rule.trim();
|
||||
return rule.length && !rule.startsWith('#') ? rule : null;
|
||||
}).filter(Boolean);
|
||||
|
||||
// Filter out invalid rules
|
||||
rules = rules.filter(function(rule) {
|
||||
if (ip.isV4Format(rule)) {
|
||||
ipv4.push(rule);
|
||||
return true;
|
||||
} else if (ip.isV6Format(rule)) {
|
||||
ipv6.push(rule);
|
||||
return true;
|
||||
} else if (isCidrSubnet.test(rule)) {
|
||||
cidr.push(rule);
|
||||
return true;
|
||||
} else {
|
||||
invalid.push(rule);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
callback(null, {
|
||||
numRules: rules.length + invalid.length,
|
||||
ipv4: ipv4,
|
||||
ipv6: ipv6,
|
||||
cidr: cidr,
|
||||
valid: rules,
|
||||
invalid: invalid
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Blacklist;
|
@ -0,0 +1,31 @@
|
||||
<div class="flags">
|
||||
<div class="col-lg-12">
|
||||
<p class="lead">
|
||||
Configure your IP blacklist here.
|
||||
</p>
|
||||
<p>
|
||||
Occasionally, a user account ban is not enough of a deterrant. Other times, restricting access to the forum to a specific IP or a range of IPs
|
||||
is the best way to protect a forum. In these scenarios, you can add troublesome IP addresses or entire CIDR blocks to this blacklist, and
|
||||
they will be prevented from logging in to or registering a new account.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div id="blacklist-rules"></div>
|
||||
<form class="blacklist-settings">
|
||||
<input type="hidden" id="blacklist-rules-holder" value="" name="rules" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Active Rules</div>
|
||||
<div class="panel-body">
|
||||
<button type="button" class="btn btn-warning" data-action="test"><i class="fa fa-bomb"></i> Validate Blacklist</button>
|
||||
<button type="button" class="btn btn-primary" data-action="apply"><i class="fa fa-save"></i> Apply Blacklist</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -0,0 +1,14 @@
|
||||
<p class="lead">
|
||||
<strong>{valid.length}</strong> out of <strong>{numRules}</strong> rule(s) valid.
|
||||
</p>
|
||||
|
||||
<!-- IF invalid.length -->
|
||||
<p>
|
||||
The following <strong>{invalid.length}</strong> rules are invalid:
|
||||
</p>
|
||||
<ul>
|
||||
<!-- BEGIN invalid -->
|
||||
<li><code>@value</code></li>
|
||||
<!-- END invalid -->
|
||||
</ul>
|
||||
<!-- ENDIF invalid.length -->
|
Loading…
Reference in New Issue