mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-06-17 22:11:48 +02:00
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 functionality
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
"express-session": "^1.8.2",
|
||||
"express-useragent": "0.2.4",
|
||||
"html-to-text": "2.0.0",
|
||||
"ip": "1.1.2",
|
||||
"jimp": "0.2.21",
|
||||
"less": "^2.0.0",
|
||||
"logrotate-stream": "^0.2.3",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
"user-banned": "User banned",
|
||||
"user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
|
||||
"blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
|
||||
|
||||
"no-category": "Category does not exist",
|
||||
"no-topic": "Topic does not exist",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
@import "./manage/tags";
|
||||
@import "./manage/groups";
|
||||
@import "./manage/users";
|
||||
@import "./manage/blacklist";
|
||||
@import "./appearance/customise";
|
||||
@import "./appearance/themes";
|
||||
@import "./extend/plugins";
|
||||
|
||||
5
public/less/admin/manage/blacklist.less
Normal file
5
public/less/admin/manage/blacklist.less
Normal file
@@ -0,0 +1,5 @@
|
||||
#blacklist-rules {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
display: block;
|
||||
}
|
||||
41
public/src/admin/manage/ip-blacklist.js
Normal file
41
public/src/admin/manage/ip-blacklist.js
Normal file
@@ -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;
|
||||
});
|
||||
@@ -5,6 +5,7 @@ var adminController = {
|
||||
categories: require('./admin/categories'),
|
||||
tags: require('./admin/tags'),
|
||||
flags: require('./admin/flags'),
|
||||
blacklist: require('./admin/blacklist'),
|
||||
groups: require('./admin/groups'),
|
||||
appearance: require('./admin/appearance'),
|
||||
extend: {
|
||||
|
||||
9
src/controllers/admin/blacklist.js
Normal file
9
src/controllers/admin/blacklist.js
Normal file
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
var blacklistController = {};
|
||||
|
||||
blacklistController.get = function(req, res, next) {
|
||||
res.render('admin/manage/ip-blacklist', {});
|
||||
};
|
||||
|
||||
module.exports = blacklistController;
|
||||
@@ -26,6 +26,7 @@ var async = require('async'),
|
||||
require('./meta/tags')(Meta);
|
||||
require('./meta/dependencies')(Meta);
|
||||
Meta.templates = require('./meta/templates');
|
||||
Meta.blacklist = require('./meta/blacklist');
|
||||
|
||||
/* Assorted */
|
||||
Meta.userOrGroupExists = function(slug, callback) {
|
||||
|
||||
106
src/meta/blacklist.js
Normal file
106
src/meta/blacklist.js
Normal file
@@ -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;
|
||||
@@ -257,6 +257,13 @@ middleware.busyCheck = function(req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
middleware.applyBlacklist = function(req, res, next) {
|
||||
meta.blacklist.test(req.ip, function(err) {
|
||||
console.log('blacklist returned:', err);
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function(webserver) {
|
||||
app = webserver;
|
||||
middleware.admin = require('./admin')(webserver);
|
||||
|
||||
@@ -54,8 +54,8 @@ function addRoutes(router, middleware, controllers) {
|
||||
router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
|
||||
|
||||
router.get('/manage/tags', middlewares, controllers.admin.tags.get);
|
||||
|
||||
router.get('/manage/flags', middlewares, controllers.admin.flags.get);
|
||||
router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
|
||||
|
||||
router.get('/manage/users', middlewares, controllers.admin.users.sortByJoinDate);
|
||||
router.get('/manage/users/search', middlewares, controllers.admin.users.search);
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
}));
|
||||
});
|
||||
|
||||
router.post('/register', Auth.middleware.applyCSRF, controllers.authentication.register);
|
||||
router.post('/login', Auth.middleware.applyCSRF, controllers.authentication.login);
|
||||
router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register);
|
||||
router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login);
|
||||
router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout);
|
||||
|
||||
hotswap.replace('auth', router);
|
||||
|
||||
@@ -187,9 +187,14 @@ function handle404(app, middleware) {
|
||||
|
||||
function handleErrors(app, middleware) {
|
||||
app.use(function(err, req, res, next) {
|
||||
if (err.code === 'EBADCSRFTOKEN') {
|
||||
winston.error(req.path + '\n', err.message);
|
||||
return res.sendStatus(403);
|
||||
switch (err.code) {
|
||||
case 'EBADCSRFTOKEN':
|
||||
winston.error(req.path + '\n', err.message);
|
||||
return res.sendStatus(403);
|
||||
break;
|
||||
case 'blacklisted-ip':
|
||||
return res.status(403).type('text/plain').send(err.message);
|
||||
break;
|
||||
}
|
||||
|
||||
if (parseInt(err.status, 10) === 302 && err.path) {
|
||||
|
||||
@@ -33,7 +33,8 @@ var async = require('async'),
|
||||
settings: {},
|
||||
email: {},
|
||||
analytics: {},
|
||||
logs: {}
|
||||
logs: {},
|
||||
blacklist: {}
|
||||
};
|
||||
|
||||
SocketAdmin.before = function(socket, method, data, next) {
|
||||
@@ -273,5 +274,8 @@ SocketAdmin.deleteAllEvents = function(socket, data, callback) {
|
||||
events.deleteAll(callback);
|
||||
};
|
||||
|
||||
SocketAdmin.blacklist.validate = function(socket, data, callback) {
|
||||
meta.blacklist.validate(data.rules, callback);
|
||||
};
|
||||
|
||||
module.exports = SocketAdmin;
|
||||
|
||||
31
src/views/admin/manage/ip-blacklist.tpl
Normal file
31
src/views/admin/manage/ip-blacklist.tpl
Normal file
@@ -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>
|
||||
14
src/views/admin/partials/blacklist-validate.tpl
Normal file
14
src/views/admin/partials/blacklist-validate.tpl
Normal file
@@ -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 -->
|
||||
@@ -20,6 +20,7 @@
|
||||
<li><a href="{relative_path}/admin/manage/registration">Registration Queue</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">Groups</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/flags">Flags</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/ip-blacklist">IP Blacklist</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -173,6 +174,7 @@
|
||||
<li><a href="{relative_path}/admin/manage/registration">Registration Queue</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">Groups</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/flags">Flags</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/ip-blacklist">IP Blacklist</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown menu-item">
|
||||
|
||||
@@ -95,7 +95,8 @@ function initializeNodeBB(callback) {
|
||||
async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, 'nodebb.min.js'),
|
||||
async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, 'acp.min.js'),
|
||||
async.apply(!skipLess ? meta.css.minify : meta.css.getFromFile),
|
||||
async.apply(meta.sounds.init)
|
||||
async.apply(meta.sounds.init),
|
||||
async.apply(meta.blacklist.load)
|
||||
], next);
|
||||
},
|
||||
function(results, next) {
|
||||
|
||||
Reference in New Issue
Block a user