Code coverage report for esecurity/lib/middleware/zoneLimit.js

Statements: 100% (41 / 41)      Branches: 100% (30 / 30)      Functions: 100% (7 / 7)      Lines: 100% (38 / 38)      Ignored: none     

All files » esecurity/lib/middleware/ » zoneLimit.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69  1   1   8 8 8 8 11 8   8   8   8 11   11 8   11   11 9   9   9     8 14 14 3 3 2 1 1     3       8     12 1   11     11     11 11 2 2     9        
 
var utils = require('../utils');
 
module.exports = function zoneLimitConstructor(opts) {
 
    opts = opts || {};
    opts.rate = parseInt(opts.rate) || 100;
    opts.window = parseInt(opts.window) || 5;
    opts.delayGc = parseInt(opts.delayGc) || 20;
    opts.keyZone = opts.keyZone || function (req) { return req.ip; };
    opts.log = opts.log || false;
 
    var counterConnections = {};
    
    var isLogEnable = "function" === typeof opts.log;
      
    var isLimitReached = function (key) {
        var currentTime = parseInt(Date.now() / 1E3);
    
        if (!counterConnections[key])
            counterConnections[key] = { t: currentTime, v: 0 };
        
        var isInWindow = currentTime - counterConnections[key].t < opts.window;
    
        if (isInWindow && counterConnections[key].v + 1 > opts.rate) return true;
        else if (!isInWindow) counterConnections[key] = { t: currentTime, v: 0 };
    
        ++counterConnections[key].v;
    
        return false;
    }
 
    var timer, garbageCollector = function () {
        timer && clearTimeout(timer);
        timer = setTimeout(function () {
            var currentTime = parseInt(Date.now() / 1E3);
            Object.keys(counterConnections).forEach(function (v) {
                if (currentTime - counterConnections[v].t >= opts.window) {
                    counterConnections[v] = null;
                    delete counterConnections[v];
                }
            });
            garbageCollector();
        }, opts.delayGc * 1E3);
    };
 
    return function zoneLimit(req, res, next) {
 
        // self-awareness
        if (req._esecurity_zoneLimit)
            return next();
        
        req._esecurity_zoneLimit = true;
      
        // start garbage collector
        garbageCollector();
 
        // limit by rate
        var keyZone;
        if (isLimitReached(keyZone = opts.keyZone(req))) {
            isLogEnable && opts.log('[' + req.ip + '] - 429 - Rate limit exceeded for keyzone:' + keyZone, req);
            return next(utils.error(429, 'Rate limit exceeded'));
        }
        
        next();
    };
};