// limiter.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; const path = require('path'); const { RateLimiterRedis } = require('rate-limiter-flexible'); const expressLimiter = require('express-limiter'); const { SiteService, SiteError } = require('../../lib/site-lib'); class LimiterService extends SiteService { constructor (dtp) { super(dtp, module.exports); this.config = require(path.resolve(dtp.config.root, 'config', 'limiter.js')); this.limiter = expressLimiter(this.dtp.app, this.dtp.redis); this.handlers = { lookup: this.limiterLookup.bind(this), whitelist: this.limiterWhitelist.bind(this), }; this.rateLimiters = { }; } createMiddleware (config) { const options = { total: config.total, expire: config.expire, lookup: this.handlers.lookup, whitelist: this.handlers.whitelist, onRateLimited: async (req, res, next) => { this.emit('limiter:block', req); next(new SiteError(config.status || 429, config.message || 'Rate limit exceeded')); }, }; const middleware = this.limiter(options); return async (req, res, next) => { return middleware(req, res, next); }; } limiterLookup (req, res, options, next) { if (req.user) { options.lookup = 'user._id'; // req.user._id, populated by PassportJS session } else { options.lookup = 'ip'; // req.ip, populated by ExpressJS with trust_proxy=1 } return next(); } limiterWhitelist (req) { return req.user && req.user.flags.isAdmin; } createRateLimiter (id, config) { if (this.rateLimiters[id]) { return this.rateLimiters[id]; } config = Object.assign({ storeClient: this.dtp.redis, }, config); this.rateLimiters[id] = new RateLimiterRedis(config); return this.rateLimiters[id]; } } module.exports = { logId: 'svc:limiter', index: 'limiter', className: 'LimiterService', create: (dtp) => { return new LimiterService(dtp); }, };