adapt-authoring-ratelimiter/lib/RateLimiterModule.js

import { AbstractModule } from 'adapt-authoring-core'
import { RateLimiterMongo } from 'rate-limiter-flexible'
/**
 * Module for rate-limiting access to the API
 * @memberof ratelimiter
 * @extends {AbstractModule}
 */
class RateLimiterModule extends AbstractModule {
  /** @override */
  async init () {
    const mongodb = await this.app.waitForModule('mongodb')
    const { db } = await mongodb.getStats()
    /**
     * Reference to the rate limiter instance
     * @type {external:RateLimiterFlexible}
     */
    this.rateLimiter = new RateLimiterMongo({
      storeClient: mongodb.client,
      dbName: db,
      keyPrefix: 'ratelimiter',
      points: this.getConfig('apiRequestLimit'),
      duration: this.getConfig('apiRequestLimitDuration') / 1000
    })
    const server = await this.app.waitForModule('server')
    server.api.addMiddleware(this.middleware())
  }

  /**
   * Limits the number of requests that can be made to the API
   * @return {Function} An Express.js request handler
   */
  middleware () {
    return async (req, res, next) => {
      try {
        const data = await this.rateLimiter.consume(req.ip)
        res.set({
          'Retry-After': data.msBeforeNext / 1000,
          'X-RateLimit-Limit': this.getConfig('apiRequestLimit'),
          'X-RateLimit-Remaining': data.remainingPoints,
          'X-RateLimit-Reset': new Date(Date.now() + data.msBeforeNext)
        })
        next()
      } catch (e) {
        res.sendError(this.app.errors.RATE_LIMIT_EXCEEDED)
      }
    }
  }
}

export default RateLimiterModule