Skip to content

Instantly share code, notes, and snippets.

@claustres
Last active June 4, 2018 03:41
Show Gist options
  • Save claustres/5e5a707b063f086baafe7b0f0e73d466 to your computer and use it in GitHub Desktop.
Save claustres/5e5a707b063f086baafe7b0f0e73d466 to your computer and use it in GitHub Desktop.
Rate limiting with FeathersJS
import { RateLimiter as SocketLimiter } from 'limiter'
import HttpLimiter from 'express-rate-limit'
import { TooManyRequests } from 'feathers-errors'
import makeDebug from 'debug'
...
const debug = makeDebug('debug')
function setupSockets (app) {
const apiLimiter = app.get('apiLimiter')
const authConfig = app.get('authentication')
const authLimiter = authConfig.limiter
return io => {
io.on('connection', socket => {
debug('New socket connection', socket.id, socket.conn.remoteAddress)
socket.on('disconnect', () => {
debug('Socket disconnection', socket.id, socket.conn.remoteAddress)
})
if (apiLimiter && apiLimiter.websocket) {
const { tokensPerInterval, interval } = apiLimiter.websocket
socket.socketLimiter = new SocketLimiter(tokensPerInterval, interval)
socket.use((packet, next) => {
// Message are formatted like this 'service_path::service_method'
let [ path, method ] = packet[0].split('::')
debug(socket.socketLimiter.getTokensRemaining() + ' remaining API token for socket', socket.id, socket.conn.remoteAddress)
if (!socket.socketLimiter.tryRemoveTokens(1)) { // if exceeded
const message = 'Too many requests in a given amount of time (rate limiting)'
debug(message)
socket.emit('rate-limit', new TooManyRequests(message))
// Add a timeout so that error message is correctly handled
setTimeout(() => socket.disconnect(true), 3000)
return
}
next()
})
}
if (authLimiter && authLimiter.websocket) {
const { tokensPerInterval, interval } = authLimiter.websocket
socket.authSocketLimiter = new SocketLimiter(tokensPerInterval, interval)
socket.on('authenticate', (data) => {
// We only limit password guessing
if (data.strategy === 'local') {
debugLimiter(socket.authSocketLimiter.getTokensRemaining() + ' remaining authentication token for socket', socket.id, socket.conn.remoteAddress)
if (!socket.authSocketLimiter.tryRemoveTokens(1)) { // if exceeded
const message = 'Too many authentication requests in a given amount of time (rate limiting)'
debug(message)
socket.emit('rate-limit', new TooManyRequests(message))
// Add a timeout so that error message is correctly handled
setTimeout(() => socket.disconnect(true), 3000)
}
}
})
}
})
}
}
// On the server side
// ------------------
let app = feathers()
// Load app configuration first
app.configure(configuration())
// REST limiters before any built-in service initialization
const apiLimiter = app.get('apiLimiter')
if (apiLimiter && apiLimiter.http) {
app.use('/', new HttpLimiter(apiLimiter.http))
}
const authConfig = app.get('authentication')
const authLimiter = authConfig.limiter
if (authLimiter && authLimiter.http) {
app.use(authConfig.path, new HttpLimiter(authLimiter.http))
}
// Set up providers
app.configure(rest())
app.configure(socketio(setupSockets(app)))
// On the client side
// ------------------
let api = feathers()
api.socket = io(url)
api.configure(feathers.socketio(api.socket))
// Retrieve our specific errors on rate-limiting and alert user
api.socket.on('rate-limit', (error) => ...)
api.socket.on('disconnect', () => ...)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment