// limiter.js // Copyright (C) 2024 DTP Technologies, LLC // All Rights Reserved 'use strict'; import path from 'node:path'; import expressLimiter from 'express-limiter'; import { SiteService, SiteError } from '../../lib/site-lib.js'; export default class LimiterService extends SiteService { static get slug ( ) { return 'limiter'; } static get name ( ) { return 'LimiterService'; } constructor (dtp) { super(dtp, LimiterService); this.handlers = { lookup: this.limiterLookup.bind(this), whitelist: this.limiterWhitelist.bind(this), }; } async start ( ) { this.config = (await import(path.resolve(this.dtp.config.root, 'config', 'limiter.js'))).default; this.limiter = expressLimiter(this.dtp.app, this.dtp.redis); } create (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')); }, }; // this.log.debug('creating rate limiter', { options }); 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) { if ((process.env.NODE_ENV === 'local') && (process.env.DTP_RATE_LIMITER === 'disabled')) { return true; } return req.user && req.user.flags.isAdmin; } }