Comparing version 0.1.1 to 0.1.2
116
index.js
@@ -1,63 +0,83 @@ | ||
const Util = require('util'); | ||
const {isArray} = require('util'); | ||
const Rule = require('./rule'); | ||
const DEFAULT_RULES = [{ regexp: '.*' }]; | ||
const DEFAULT_CHECK_INTERVAL = 1000; // One second | ||
const DEFAULT_RULES = [{regexp: '.*'}]; | ||
function DDDoS(options) { | ||
if (typeof options === 'null' || typeof options === 'undefined') { | ||
options = {}; | ||
class DDDoS { | ||
constructor(options = {}) { | ||
if (options === null) { | ||
options = {}; | ||
} | ||
let checkInterval = Number(options.checkInterval); | ||
if (isNaN(checkInterval) || checkInterval <= 0) { | ||
checkInterval = DEFAULT_CHECK_INTERVAL; | ||
} | ||
this.checkInterval = checkInterval; | ||
this.paths = new Map(); | ||
this.rules = []; | ||
const rules = isArray(options.rules) ? options.rules : DEFAULT_RULES; | ||
rules.forEach(rule => this._addRule(rule, options)); | ||
setInterval(this._check.bind(this), checkInterval); | ||
} | ||
this.checkInterval = +options.checkInterval; | ||
if (isNaN(this.checkInterval) || this.checkInterval <= 0) { | ||
this.checkInterval = 1000; //NOTE: One second. | ||
} | ||
this.paths = new Map(); | ||
this.rules = []; | ||
(Util.isArray(options.rules) ? options.rules : DEFAULT_RULES).forEach((rule) => { | ||
if (typeof rule.regexp === 'string') { | ||
this.rules.push(new Rule(rule)); | ||
} else if (rule.string) { | ||
this.paths.set(rule.string, new Rule(rule)); | ||
request(ip, path, ddos, next) { | ||
if (typeof path !== 'string') { | ||
path = ''; | ||
} | ||
}); | ||
setInterval(this._check.bind(this), this.checkInterval); | ||
} | ||
DDDoS.prototype._check = function() { | ||
this.paths.forEach((path) => { path.check(); }); | ||
this.rules.forEach((rule) => { rule.check(); }); | ||
}; | ||
const rule = this.paths.get(path); | ||
DDDoS.prototype.request = function(ip, path, ddos, next) { | ||
if (typeof path !== 'string') { | ||
path = ''; | ||
} | ||
let rule = this.paths.get(path); | ||
if (rule) { | ||
return rule.use(ip, path, ddos, next); | ||
} | ||
this.rules.some((rule) => { | ||
let matches = path.match(rule.regexp); | ||
if (matches) { | ||
if (rule) { | ||
rule.use(ip, path, ddos, next); | ||
return; | ||
} | ||
return matches; | ||
}); | ||
}; | ||
DDDoS.prototype.express = function(ipPropertyName, pathPropertyName) { | ||
if (typeof ipPropertyName !== 'string' || !ipPropertyName) { | ||
ipPropertyName = 'ip'; | ||
this.rules.some(rule => { | ||
const matches = path.match(rule.regexp); | ||
if (matches) { | ||
rule.use(ip, path, ddos, next); | ||
} | ||
return matches; | ||
}); | ||
} | ||
if (typeof pathPropertyName !== 'string' || !pathPropertyName) { | ||
pathPropertyName = 'path'; | ||
express(ipPropertyName, pathPropertyName) { | ||
if (typeof ipPropertyName !== 'string' || !ipPropertyName) { | ||
ipPropertyName = 'ip'; | ||
} | ||
if (typeof pathPropertyName !== 'string' || !pathPropertyName) { | ||
pathPropertyName = 'path'; | ||
} | ||
return (req, res, next) => { | ||
this.request(req[ipPropertyName], req[pathPropertyName], (errorCode, errorData) => { | ||
res.status(errorCode).send(errorData); | ||
}, next); | ||
}; | ||
} | ||
return (req, res, next) => { | ||
this.request(req[ipPropertyName], req[pathPropertyName], (errorCode, errorData) => { | ||
res.status(errorCode).send(errorData); | ||
}, next); | ||
}; | ||
}; | ||
_addRule(rule, options) { | ||
if (typeof rule.regexp === 'string') { | ||
this.rules.push(new Rule(rule, options)); | ||
} else if (typeof rule.string === 'string') { | ||
this.paths.set(rule.string, new Rule(rule, options)); | ||
} | ||
} | ||
_check() { | ||
this.paths.forEach(path => path.check()); | ||
this.rules.forEach(rule => rule.check()); | ||
} | ||
} | ||
module.exports = DDDoS; |
{ | ||
"name": "dddos", | ||
"version": "0.1.1", | ||
"description": "", | ||
"version": "0.1.2", | ||
"description": "Configurable Denial-Of-Service prevention library", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"lint": "eslint ./*.js", | ||
"test": "mocha ./tests/*" | ||
}, | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.org/" | ||
}, | ||
"repository": { | ||
@@ -18,3 +22,3 @@ "type": "git", | ||
], | ||
"author": "Andrey Bogdanov <ololoepepe@gmail.com> (https://ololoepepe.me)", | ||
"author": "Andrey Bogdanov <ololoepepe@gmail.com> (https://ololoepepe.ru)", | ||
"license": "MIT", | ||
@@ -24,3 +28,8 @@ "bugs": { | ||
}, | ||
"homepage": "https://github.com/ololoepepe/dddos" | ||
"homepage": "https://github.com/ololoepepe/dddos", | ||
"devDependencies": { | ||
"eslint": "^4.8.0", | ||
"eslint-config-xo": "^0.18.2", | ||
"mocha": "^3.4.2" | ||
} | ||
} |
@@ -7,4 +7,4 @@ # Configurable Denial-Of-Service prevention library | ||
``` | ||
/* With Express*/ | ||
```js | ||
/* With Express */ | ||
@@ -64,3 +64,3 @@ let expresss = require('express'); | ||
``` | ||
```js | ||
rules: [ | ||
@@ -67,0 +67,0 @@ { /*Allow 4 requests accessing the application API per checkInterval*/ |
188
rule.js
@@ -1,52 +0,80 @@ | ||
function setProperty(name, options, defOptions, defValue) { | ||
this[name] = +options[name] || +defOptions[name] || defValue; | ||
if (isNaN(this[name]) || this[name] < 0) { | ||
this[name] = defValue; | ||
const HTTP_TOO_MANY_REQUESTS = 429; | ||
const DEFAULT_WEIGHT = 1; | ||
const DEFAULT_MAX_WEIGHT = 10; | ||
const DEFAULT_ERROR_CODE = HTTP_TOO_MANY_REQUESTS; | ||
const DEFAULT_QUEUE_SIZE = 0; // No queue. | ||
const DEFAULT_ERROR_DATA = 'Not so fast!'; | ||
class Rule { | ||
static get DEFAULT_WEIGHT() { | ||
return DEFAULT_WEIGHT; | ||
} | ||
} | ||
function Rule(options, defOptions) { | ||
if (typeof options === 'null' || typeof options === 'undefined') { | ||
options = {}; | ||
static get DEFAULT_MAX_WEIGHT() { | ||
return DEFAULT_MAX_WEIGHT; | ||
} | ||
if (typeof defOptions === 'null' || typeof defOptions === 'undefined') { | ||
defOptions = {}; | ||
static get DEFAULT_ERROR_CODE() { | ||
return DEFAULT_ERROR_CODE; | ||
} | ||
if (typeof options.string === 'string') { | ||
this.string = options.string; | ||
} else if (typeof options.regexp === 'string') { | ||
this.regexp = new RegExp(options.regexp, options.flags); | ||
static get DEFAULT_QUEUE_SIZE() { | ||
return DEFAULT_QUEUE_SIZE; | ||
} | ||
setProperty.call(this, 'weight', options, defOptions, 1); | ||
setProperty.call(this, 'maxWeight', options, defOptions, 10); | ||
setProperty.call(this, 'errorCode', options, defOptions, 429); //NOTE: 429 - Too Many Requests. | ||
setProperty.call(this, 'queueSize', options, defOptions, 0); //NOTE: No queue. | ||
if (typeof options.errorData !== 'undefined') { | ||
this.errorData = options.errorData; | ||
} else if (typeof defOptions.errorData !== 'undefined') { | ||
this.errorData = defOptions.errorData; | ||
} else { | ||
this.errorData = 'Not so fast!'; | ||
static get DEFAULT_ERROR_DATA() { | ||
return DEFAULT_ERROR_DATA; | ||
} | ||
if (typeof defOptions.logFunction === 'function') { | ||
this.logFunction = defOptions.logFunction; | ||
constructor(rule = {}, options = {}) { | ||
if (rule === null) { | ||
rule = {}; | ||
} | ||
if (options === null) { | ||
options = {}; | ||
} | ||
const {string, regexp} = rule; | ||
this.string = string; | ||
this.regexp = regexp; | ||
this.weight = _getNumber('weight', rule, options, DEFAULT_WEIGHT); | ||
this.maxWeight = _getNumber('maxWeight', rule, options, DEFAULT_MAX_WEIGHT); | ||
this.errorCode = _getNumber('errorCode', rule, options, DEFAULT_ERROR_CODE); | ||
this.queueSize = _getNumber('queueSize', rule, options, DEFAULT_QUEUE_SIZE); | ||
this.errorData = _getNonUndefined('errorData', rule, options, DEFAULT_ERROR_DATA); | ||
this.logFunction = _getFunction('logFunction', rule, options); | ||
this.users = new Map(); | ||
} | ||
this.users = new Map(); | ||
} | ||
Rule.prototype.use = function(ip, path, ddos, next) { | ||
let user = this.users.get(ip); | ||
if (!user) { | ||
user = { weight: 0 }; | ||
if (this.queueSize > 0) { | ||
user.queue = []; | ||
use(ip, path, ddos, next) { | ||
let user = this.users.get(ip); | ||
if (user) { | ||
user.weight += this.weight; | ||
} else { | ||
user = { | ||
weight: this.weight, | ||
queue: [] | ||
}; | ||
this.users.set(ip, user); | ||
} | ||
this.users.set(ip, user); | ||
} | ||
user.weight += this.weight; | ||
if (user.weight > this.maxWeight) { | ||
if (user.weight <= this.maxWeight) { | ||
if (typeof next === 'function') { | ||
next(); | ||
} | ||
return; | ||
} | ||
if (typeof this.logFunction === 'function') { | ||
this.logFunction(ip, path, user.weight, this.maxWeight, this.regexp || this.string); | ||
} | ||
if (user.queue && user.queue.length < this.queueSize) { | ||
if (user.queue.length < this.queueSize) { | ||
user.queue.push(next); | ||
@@ -56,26 +84,68 @@ } else if (typeof ddos === 'function') { | ||
} | ||
return; | ||
} | ||
if (typeof next === 'function') { | ||
next(); | ||
} | ||
}; | ||
Rule.prototype.check = function() { | ||
this.users.forEach((user, ip) => { | ||
user.weight -= this.maxWeight; | ||
let count = this.maxWeight - user.weight; | ||
if (user.queue && user.queue.length > 0 && count > 0) { | ||
let items = user.queue.splice(0, count); | ||
if (typeof next === 'function') { | ||
items.forEach((next) => { setImmediate(next); }); | ||
check() { | ||
this.users.forEach((user, ip) => { | ||
user.weight -= this.maxWeight; | ||
const count = this.maxWeight - user.weight; | ||
if ((count > 0) && (user.queue.length > 0)) { | ||
user.queue | ||
.splice(0, count) | ||
.filter(next => typeof next === 'function') | ||
.forEach(next => setImmediate(next)); | ||
user.weight += count; | ||
} | ||
user.weight += count; | ||
} | ||
if (user.weight <= 0) { | ||
this.users.delete(ip); | ||
} | ||
}); | ||
}; | ||
if (user.weight <= 0) { | ||
this.users.delete(ip); | ||
} | ||
}); | ||
} | ||
} | ||
function _getNumber(name, rule, options, defValue) { | ||
const value = Number(rule[name]) || Number(options[name]); | ||
if (isNaN(value) || (value < 0)) { | ||
return defValue; | ||
} | ||
return value; | ||
} | ||
function _getNonUndefined(name, rule, options, defValue) { | ||
let value = rule[name]; | ||
if (value !== undefined) { | ||
return value; | ||
} | ||
value = options[name]; | ||
if (value !== undefined) { | ||
return value; | ||
} | ||
return defValue; | ||
} | ||
function _getFunction(name, rule, options, defValue) { | ||
let value = rule[name]; | ||
if (typeof value === 'function') { | ||
return value; | ||
} | ||
value = options[name]; | ||
if (typeof value === 'function') { | ||
return value; | ||
} | ||
return defValue; | ||
} | ||
module.exports = Rule; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
14461
8
264
1
3
1