index.js 3.71 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 IP_WHITE_LIST = [
    '106.38.38.146', // 北京办公区域
    '106.38.38.147', // 北京办公区域
    '106.39.86.227', // 北京办公区域
    '218.94.75.58', // 南京办公区域
    '218.94.75.50', // 南京办公区域
    '218.94.77.166', // 南京办公区域

    // '222.73.196.18', // B站合作方单击次数快加白名单
    '123.206.73.107', // 腾讯云出口IP
    '139.199.35.21', // 腾讯云出口IP
    '139.199.29.44', // 腾讯云出口IP
    '123.206.21.19' // 腾讯云出口IP
];

const IP_WHITE_SEGMENT = [
    '10.66.', // 内网IP段
    '192.168.' // 内网IP段
];

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

// 排除条件:ip白名单/路径白名单/异步请求/登录用户
const _excluded = (req) => {
    let remoteIp = req.yoho.clientIp || '';
    let remoteIpSegment = `${remoteIp.split('.').slice(0, 2).join('.')}.`;

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

        return Boolean(
            atWhiteList ||
            _.includes(IP_WHITE_LIST, remoteIp) ||
            _.includes(IP_WHITE_SEGMENT, remoteIpSegment) ||
            _.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, 'pc.sys.noLimiter');

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

    return 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) {
            logger.info(`limit remote ip: ${remoteIp}; ua: ${req.header('User-Agent')}; uid: ${req.user.uid}`);

            policy(req, res, next);
        } else {
            return next();
        }
    })().catch((err) => {
        logger.error(err);
        return next();
    });
};