@universis/janitor
Version:
Universis api plugin for handling user authorization and rate limiting
101 lines (99 loc) • 5.3 kB
JavaScript
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.')
}
});
}
}