matiaslopezd
2/26/2019 - 12:26 AM

Rate limiting for FeathersJS HTTP (REST API) and Web Sockets connections (Express, Node.js)

Rate limiting for FeathersJS HTTP (REST API) and Web Sockets connections (Express, Node.js)

'use strict'

const bodyParser = require('body-parser')
const compress = require('compression')
const configuration = require('feathers-configuration')
const cors = require('cors')
const favicon = require('serve-favicon')
const feathers = require('feathers')
const hooks = require('feathers-hooks')
const limiter = require('limiter').RateLimiter // Generic limiter used for authentication attempts inside web socket connection
const middleware = require('./middleware')
const path = require('path')
const rateLimit = require('express-rate-limit') // Express middleware limiter used for HTTP requests
const rest = require('feathers-rest')
const serveStatic = require('feathers').static
const services = require('./services')
const socketio = require('feathers-socketio')

const app = feathers()

app.configure(configuration(path.join(__dirname, '..')))

const authLimiter = new rateLimit({
  windowMs: 15*60*1000, // 15 minutes window
  delayAfter: 1, // begin slowing down responses after the first request
  delayMs: 3*1000, // slow down subsequent responses by 3 seconds per request 
  max: 5 // start blocking after 5 requests
})

app.use(compress())
  .use('/auth/', authLimiter) // limit authentication attempts via REST API
  .use('/socket.io/', authLimiter) // limit web socket connections
  .options('*', cors())
  .use(cors())
  .use(favicon(path.join(app.get('public'), 'favicon.ico')))
  .use('/', serveStatic(app.get('public')))
  .use(bodyParser.json())
  .use(bodyParser.urlencoded({ extended: true }))
  .configure(hooks())
  .configure(rest())
  .configure(socketio(io => {
    io.on('connection', socket => {
      const socketLimiter = new limiter(1, 3000) // allow 1 authentication attempt every 3 seconds inside current web socket connection
      socket.on('authenticate', () => {
        if(!socketLimiter.tryRemoveTokens(1)) { // if exceeded, connection is dropped
          console.log('Too many socket.io auth attempts from %s, disconnecting.', socket.conn.remoteAddress)
          socket.send('Too many authentication attempts from you, disconnecting.')
          socket.disconnect()
        }
      })
    })
  }))
  .configure(services)
  .configure(middleware)

module.exports = app