UNPKG

@universis/janitor

Version:

Universis api plugin for handling user authorization and rate limiting

101 lines (99 loc) 5.3 kB
import { ApplicationService, TraceUtils } from '@themost/common'; import { rateLimit } from 'express-rate-limit'; import express from 'express'; import path from 'path'; export class RateLimitService extends ApplicationService { /** * @param {import('@themost/express').ExpressDataApplication} app */ constructor(app) { super(app); app.serviceRouter.subscribe(serviceRouter => { if (serviceRouter == null) { return; } try { const addRouter = express.Router(); let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/rateLimit') || { profiles: [], paths: [] }; if (serviceConfiguration.extends) { // get additional configuration const configurationPath = app.getConfiguration().getConfigurationPath(); const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends); TraceUtils.log(`@universis/janitor#RateLimitService will try to extend service configuration from ${extendsPath}`); serviceConfiguration = require(extendsPath); } const pathsArray = serviceConfiguration.paths || []; const profilesArray = serviceConfiguration.profiles || []; // create maps const paths = new Map(pathsArray); const profiles = new Map(profilesArray); if (paths.size === 0) { TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.'); } // get proxy address forwarding option let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding'); if (typeof proxyAddressForwarding !== 'boolean') { proxyAddressForwarding = false; } paths.forEach((value, path) => { let profile; // get profile if (value.profile) { profile = profiles.get(value.profile); } else { // or options defined inline profile = value } if (profile != null) { const rateLimitOptions = Object.assign({ windowMs: 5 * 60 * 1000, // 5 minutes limit: 50, // 50 requests legacyHeaders: true // send headers }, profile, { keyGenerator: (req) => { let remoteAddress; if (proxyAddressForwarding) { // get proxy headers or remote address remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress); } else { // get remote address remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress); } return `${path}:${remoteAddress}`; } }); if (typeof rateLimitOptions.store === 'string') { // load store const store = rateLimitOptions.store.split('#'); let StoreClass; if (store.length === 2) { const storeModule = require(store[0]); if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) { StoreClass = storeModule[store[1]]; rateLimitOptions.store = new StoreClass(this, rateLimitOptions); } else { throw new Error(`${store} cannot be found or is inaccessible`); } } else { StoreClass = require(store[0]); // create store rateLimitOptions.store = new StoreClass(this, rateLimitOptions); } } addRouter.use(path, rateLimit(rateLimitOptions)); } }); if (addRouter.stack.length) { serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack); } } catch (err) { TraceUtils.error('An error occurred while validating rate limit configuration.'); TraceUtils.error(err); TraceUtils.warn('Rate limit service is inactive due to an error occured while loading configuration.') } }); } }