Files
outline/server/middlewares/rateLimiter.ts
Tom Moor 85dab03820 docs
2022-08-16 19:43:50 +02:00

92 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Context, Next } from "koa";
import { defaults } from "lodash";
import RateLimiter from "@server/RateLimiter";
import env from "@server/env";
import { RateLimitExceededError } from "@server/errors";
import Metrics from "@server/logging/metrics";
import Redis from "@server/redis";
/**
* Middleware that limits the number of requests that are allowed within a given
* window. Should only be applied once to a server do not use on individual
* routes.
*
* @returns The middleware function.
*/
export function defaultRateLimiter() {
return async function rateLimiterMiddleware(ctx: Context, next: Next) {
if (!env.RATE_LIMITER_ENABLED) {
return next();
}
const key = RateLimiter.hasRateLimiter(ctx.path)
? `${ctx.path}:${ctx.ip}`
: `${ctx.ip}`;
const limiter = RateLimiter.getRateLimiter(ctx.path);
try {
await limiter.consume(key);
} catch (rateLimiterRes) {
ctx.set("Retry-After", `${rateLimiterRes.msBeforeNext / 1000}`);
ctx.set("RateLimit-Limit", `${limiter.points}`);
ctx.set("RateLimit-Remaining", `${rateLimiterRes.remainingPoints}`);
ctx.set(
"RateLimit-Reset",
`${new Date(Date.now() + rateLimiterRes.msBeforeNext)}`
);
Metrics.increment("rate_limit.exceeded", {
path: ctx.path,
});
throw RateLimitExceededError();
}
return next();
};
}
type RateLimiterConfig = {
/** The window for which this rate limiter is considered (defaults to 60s) */
duration?: number;
/** The number of requests per IP address that are allowed within the window */
requests: number;
};
/**
* Middleware that limits the number of requests per IP address that are allowed
* within a window, overrides default middleware when used on a route.
*
* @returns The middleware function.
*/
export function rateLimiter(config: RateLimiterConfig) {
return async function registerRateLimiterMiddleware(
ctx: Context,
next: Next
) {
if (!env.RATE_LIMITER_ENABLED) {
return next();
}
if (!RateLimiter.hasRateLimiter(ctx.path)) {
RateLimiter.setRateLimiter(
ctx.path,
defaults(
{
...config,
points: config.requests,
},
{
duration: 60,
points: env.RATE_LIMITER_REQUESTS,
keyPrefix: RateLimiter.RATE_LIMITER_REDIS_KEY_PREFIX,
storeClient: Redis.defaultClient,
}
)
);
}
return next();
};
}