All files / src/hooks hooks.service.js

71.43% Statements 30/42
53.57% Branches 15/28
100% Functions 5/5
71.43% Lines 30/42
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 731x 1x 1x   1x     1x   1x 3x     3x 3x 3x 3x     3x       3x 3x 3x 1x     2x         6x 3x     3x 3x 3x   3x 3x   3x 3x                               3x     3x 1x     2x      
import { TooManyRequests, Forbidden } from '@feathersjs/errors'
import { RateLimiter } from 'limiter'
import makeDebug from 'debug'
 
const debug = makeDebug('kalisio:kCore:service:hooks')
 
export function rateLimit (options) {
  const limiter = new RateLimiter(options.tokensPerInterval, options.interval)
 
  return function (hook) {
    Iif (hook.type !== 'before') {
      throw new Error(`The 'rateLimit' hook should only be used as a 'before' hook.`)
    }
    const operation = hook.method
    const service = hook.service.name
    let rateLimit = true
    Iif (options.operation && (options.operation !== operation)) {
      rateLimit = false
    }
    Iif (options.service && (options.service !== service)) {
      rateLimit = false
    }
 
    Eif (rateLimit) {
      debug(limiter.getTokensRemaining() + ' remaining token for rateLimit hook on service ' + service)
      if (!limiter.tryRemoveTokens(1)) { // if exceeded
        throw new TooManyRequests('Too many requests in a given amount of time (rate limiting) on service ' + service, { translation: { key: 'RATE_LIMITING' } })
      }
    }
    return hook
  }
}
 
export function countLimit (options) {
  return async function (hook) {
    Iif (hook.type !== 'before') {
      throw new Error(`The 'countLimit' hook should only be used as a 'before' hook.`)
    }
    let app = hook.app
    const customMax = (typeof options.max === 'function')
    const max = (customMax ? await options.max(hook) : options.max)
    // -1 means no limit
    Eif (max >= 0) {
      let count = options.max
      // Either we build a count request using a service or the caller has its own count routine
      const customCount = (typeof options.count === 'function')
      Iif (!customCount) {
        let service
        if (typeof options.service === 'function') {
          service = await options.service(hook)
        } else {
          service = app.getService(options.service, hook.service.context)
        }
        // Indicate we'd only like to count
        let query = { $limit: 0 }
        if (typeof options.query === 'function') {
          Object.assign(query, options.query(hook))
        } else {
          Object.assign(query, options.query)
        }
        count = service.find({ query }).total
      } else {
        count = await options.count(hook)
      }
 
      if (count > max) {
        throw new Forbidden('Resource quota exceeded (count limiting) on service ' + options.service, { translation: { key: 'COUNT_LIMITING' } })
      }
    }
    return hook
  }
}