index.js 2.82 KB
'use strict';

const _ = require('lodash');
const logger = global.yoho.logger;
const ip = require('./rules/ip-list');
const userAgent = require('./rules/useragent');
const ipWhiteList = require('./rules/ip-white-list');
const pathWhiteList = require('./rules/path-white-list');
const qpsLimiter = require('./rules/qps-limit');
const co = Promise.coroutine;

// const asynchronous = require('./rules/asynchronous');
// const fakerLimiter = require('./rules/faker-limit');
const captchaPolicy = require('./policies/captcha');

// const reporterPolicy = require('./policies/reporter');

const limiter = (rule, policy, context) => {
    return rule(context, policy);
};

// 排除条件:ip白名单/路径白名单/异步请求/登录用户
const _excluded = (req) => {
    let remoteIp = req.yoho.clientIp || '';

    return co(function* () {
        let atIPWhiteList = yield ipWhiteList(remoteIp);

        return Boolean(
            atIPWhiteList ||
            _.includes(pathWhiteList(), req.path) ||
            req.xhr ||
            !_.isEmpty(_.get(req, 'user.uid'))
        );
    })();
};

module.exports = (req, res, next) => {
    const remoteIp = req.yoho.clientIp || '';
    const enabled = !_.get(req.app.locals, 'wap.sys.noLimiter');

    // 开关为关或者未获取到remoteIp,放行
    if (!enabled || !remoteIp) {
        logger.debug(`request remote ip: ${remoteIp}; enabled: ${enabled}`);
        return next();
    }

    co(function* () {
        let excluded = yield _excluded(req);

        logger.debug(`request remote ip: ${remoteIp}; excluded: ${excluded}; enabled: ${enabled}`);

        // 白名单,放行
        if (excluded) {
            return next();
        }
        const context = {
            req: req,
            res: res,
            next: next,
            remoteIp: remoteIp
        };

        let results = yield Promise.all([
            limiter(userAgent, captchaPolicy, context),
            limiter(ip, captchaPolicy, context),
            limiter(qpsLimiter, captchaPolicy, context)

            // limiter(asynchronous, captchaPolicy, context)
            // limiter(fakerLimiter, reporterPolicy, context)
        ]);

        let allPass = true, exclusion = false, policy = null;

        logger.debug('limiter result: ' + JSON.stringify(results));

        _.forEach(results, (result) => {
            if (typeof result === 'object' && !exclusion) {
                exclusion = result.exclusion;
            }

            if (typeof result === 'function') {
                allPass = false;
                policy = result;
            }
        });

        if (exclusion) {
            return next();
        } else if (!allPass && policy) {
            policy(req, res, next);
        } else {
            return next();
        }
    })().catch(err => {
        logger.error(err);
        return next();
    });
};