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

66 lines
1.5 KiB
TypeScript
Raw Normal View History

2022-08-29 09:58:53 +00:00
import IORedis from 'ioredis'
2022-09-15 08:15:41 +00:00
import { BaseContext } from 'koa'
2022-08-31 08:55:04 +00:00
import {
RateLimiterMemory,
RateLimiterRedis,
RateLimiterRes,
} from 'rate-limiter-flexible'
2022-08-29 09:58:53 +00:00
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,
2023-06-15 14:29:29 +00:00
keyPrefix: 'rate-limiter',
2022-08-29 09:58:53 +00:00
}),
2023-06-15 14:29:29 +00:00
keyPrefix: 'rate-limiter',
2022-08-29 09:58:53 +00:00
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
})
2022-09-15 08:15:41 +00:00
export const rateLimiterMiddleware = async (
ctx: BaseContext,
next: () => Promise<unknown>
) => {
2022-08-29 09:58:53 +00:00
try {
await rateLimiter.consume(ctx.ip)
} catch (rejRes) {
ctx.status = 429
ctx.body = 'Too Many Requests'
2022-08-31 08:55:04 +00:00
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(),
})
}
2022-08-29 09:58:53 +00:00
return
}
2022-09-15 08:15:41 +00:00
return await next()
2022-08-29 09:58:53 +00:00
}
2022-08-31 08:55:04 +00:00
const isRateLimiterRes = (val: unknown): val is RateLimiterRes => {
return !!(
val &&
typeof val === 'object' &&
[
'msBeforeNext',
'remainingPoints',
'consumedPoints',
'isFirstInDuration',
].every((s) => s in val)
)
}