UNPKG

@universis/janitor

Version:

Universis api plugin for handling user authorization and rate limiting

112 lines (109 loc) 6.13 kB
import { ApplicationService, TraceUtils } from '@themost/common'; import slowDown from 'express-slow-down'; import express from 'express'; import path from 'path'; export class SpeedLimitService extends ApplicationService { 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/speedLimit') || { profiles: [], paths: [] }; if (serviceConfiguration.extends) { // get additional configuration const configurationPath = app.getConfiguration().getConfigurationPath(); const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends); TraceUtils.log(`@universis/janitor#SpeedLimitService 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#SpeedLimitService 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 slowDownOptions = Object.assign({ windowMs: 5 * 60 * 1000, // 5 minutes delayAfter: 20, // 20 requests delayMs: 500, // 500 ms maxDelayMs: 10000 // 10 seconds }, 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 (Array.isArray(slowDownOptions.randomDelayMs)) { slowDownOptions.delayMs = () => { const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]); return delayMs; } } if (Array.isArray(slowDownOptions.randomMaxDelayMs)) { slowDownOptions.maxDelayMs = () => { const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]); return maxDelayMs; } } if (typeof slowDownOptions.store === 'string') { // load store const store = slowDownOptions.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]]; slowDownOptions.store = new StoreClass(this, slowDownOptions); } else { throw new Error(`${store} cannot be found or is inaccessible`); } } else { StoreClass = require(store[0]); // create store slowDownOptions.store = new StoreClass(this, slowDownOptions); } } addRouter.use(path, slowDown(slowDownOptions)); } }); if (addRouter.stack.length) { serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack); } } catch (err) { TraceUtils.error('An error occurred while validating speed limit configuration.'); TraceUtils.error(err); TraceUtils.warn('Speed limit service is inactive due to an error occured while loading configuration.') } }); } }