mon-entreprise/api/source/rate-limiter.ts

66 lines
1.5 KiB
TypeScript

import IORedis from 'ioredis'
import { BaseContext } from 'koa'
import {
RateLimiterMemory,
RateLimiterRedis,
RateLimiterRes,
} from 'rate-limiter-flexible'
const Redis = IORedis.default
const rateLimiter =
process.env.NODE_ENV === 'production' && process.env.SCALINGO_REDIS_URL
? new RateLimiterRedis({
storeClient: new Redis(process.env.SCALINGO_REDIS_URL, {
enableOfflineQueue: false,
keyPrefix: 'rate-limiter',
}),
keyPrefix: 'rate-limiter',
points: 5, // 5 requests for ctx.ip
duration: 1, // per 1 second
})
: new RateLimiterMemory({
points: 5, // 5 requests for ctx.ip
duration: 1, // per 1 seconds
})
export const rateLimiterMiddleware = async (
ctx: BaseContext,
next: () => Promise<unknown>
) => {
try {
await rateLimiter.consume(ctx.ip)
} catch (rejRes) {
ctx.status = 429
ctx.body = 'Too Many Requests'
if (isRateLimiterRes(rejRes)) {
ctx.set({
'Retry-After': (rejRes.msBeforeNext / 1000).toString(),
'X-RateLimit-Limit': rateLimiter.points.toString(),
'X-RateLimit-Remaining': rejRes.remainingPoints.toString(),
'X-RateLimit-Reset': new Date(
Date.now() + rejRes.msBeforeNext
).toString(),
})
}
return
}
return await next()
}
const isRateLimiterRes = (val: unknown): val is RateLimiterRes => {
return !!(
val &&
typeof val === 'object' &&
[
'msBeforeNext',
'remainingPoints',
'consumedPoints',
'isFirstInDuration',
].every((s) => s in val)
)
}