UNPKG

hono-rate-limiter

Version:
244 lines (243 loc) 8.91 kB
import type { Context, Env, Input, Next } from "hono"; import type { StatusCode } from "hono/utils/http-status"; import type { WSContext } from "hono/ws"; /** * Data returned from the `Store` when a client's hit counter is incremented. * * @property totalHits {number} - The number of hits for that client so far. * @property resetTime {Date | undefined} - The time when the counter resets. */ export type ClientRateLimitInfo = { totalHits: number; resetTime?: Date; }; /** * Promisify<T> is a utility type that represents a value of type T or a Promise<T>. * This type is useful for converting synchronous functions to asynchronous functions. * @example * type getResult = Promisify<number>; // getResult can be number or Promise<number> * type getUser = Promisify<User>; // getUser can be User or Promise<User> */ export type Promisify<T> = T | Promise<T>; /** * The rate limit related information for each client included in the * Hono context object. */ export type RateLimitInfo = { limit: number; used: number; remaining: number; resetTime: Date | undefined; }; /** * Hono request handler that sends back a response when a client is * rate-limited. * * @param context {Context} - The Hono context object. * @param next {Next} - The Hono `next` function, can be called to skip responding. * @param optionsUsed {ConfigType} - The options used to set up the middleware. */ export type RateLimitExceededEventHandler<E extends Env = Env, P extends string = string, I extends Input = Input> = (c: Context<E, P, I>, next: Next, optionsUsed: ConfigType<E, P, I>) => void; /** * The configuration options for the rate limiter. */ export interface ConfigType<E extends Env = Env, P extends string = string, I extends Input = Input> { /** * How long we should remember the requests. * * Defaults to `60000` ms (= 1 minute). */ windowMs: number; /** * The maximum number of connections to allow during the `window` before * rate limiting the client. * * Can be the limit itself as a number or express middleware that parses * the request and then figures out the limit. * * Defaults to `5`. */ limit: number | ((c: Context<E, P, I>) => Promisify<number>); /** * The response body to send back when a client is rate limited. * * Defaults to `'Too many requests, please try again later.'` */ message: string | Record<string, unknown> | ((c: Context<E, P, I>) => Promisify<string | Record<string, unknown>>); /** * The HTTP status code to send back when a client is rate limited. * * Defaults to `HTTP 429 Too Many Requests` (RFC 6585). */ statusCode: StatusCode; /** * Whether to enable support for the standardized rate limit headers (`RateLimit-*`). * * Defaults to `draft-6`. */ standardHeaders: boolean | "draft-6" | "draft-7"; /** * The name of the property on the context object to store the rate limit info. * * Defaults to `rateLimit`. */ requestPropertyName: string; /** * The name of the property on the context object to store the Data Store instance. * * Defaults to `rateLimitStore`. */ requestStorePropertyName: string; /** * If `true`, the library will (by default) skip all requests that have a 4XX * or 5XX status. * * Defaults to `false`. */ skipFailedRequests: boolean; /** * If `true`, the library will (by default) skip all requests that have a * status code less than 400. * * Defaults to `false`. */ skipSuccessfulRequests: boolean; /** * Method to generate custom identifiers for clients. */ keyGenerator: (c: Context<E, P, I>) => Promisify<string>; /** * Hono request handler that sends back a response when a client is * rate-limited. * * By default, sends back the `statusCode` and `message` set via the options. */ handler: RateLimitExceededEventHandler<E, P, I>; /** * Method (in the form of middleware) to determine whether or not this request * counts towards a client's quota. * * By default, skips no requests. */ skip: (c: Context<E, P, I>) => Promisify<boolean>; /** * Method to determine whether or not the request counts as 'succesful'. Used * when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true. * * By default, requests with a response status code less than 400 are considered * successful. */ requestWasSuccessful: (c: Context<E, P, I>) => Promisify<boolean>; /** * The `Store` to use to store the hit count for each client. * * By default, the built-in `MemoryStore` will be used. */ store: Store<E, P, I>; } export type WSStatusCode = 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010; /** * Hono request handler that sends back a response when a client is * rate-limited. * * @param context {Context} - The Hono context object. * @param next {Next} - The Hono `next` function, can be called to skip responding. * @param optionsUsed {ConfigType} - The options used to set up the middleware. */ export type WSRateLimitExceededEventHandler<E extends Env = Env, P extends string = string, I extends Input = Input> = (event: unknown, ws: WSContext, optionsUsed: WSConfigType<E, P, I>) => void; /** * The configuration options for the rate limiter. */ export interface WSConfigType<E extends Env = Env, P extends string = string, I extends Input = Input> extends Omit<ConfigType<E, P, I>, "statusCode" | "standardHeaders" | "requestWasSuccessful" | "handler" | "skip"> { /** * The response body to send back when a client is rate limited. * * Defaults to `'Too many requests, please try again later.'` */ message: string; /** * The ws status code to send back when a client is rate limited. * * Defaults to `HTTP 1008 Terminating The Connection` (RFC 6455). */ statusCode: WSStatusCode; /** * Hono ws request handler that sends back a response when a client is * rate-limited. * * By default, sends back the `statusCode` and `message` set via the options. */ handler: WSRateLimitExceededEventHandler<E, P, I>; /** * Method (in the form of middleware) to determine whether or not this ws request * counts towards a client's quota. * * By default, skips no requests. */ skip: (event: unknown, ws: WSContext) => Promisify<boolean>; } /** * An interface that all hit counter stores must implement. */ export type Store<E extends Env = Env, P extends string = string, I extends Input = Input> = { /** * Method that initializes the store, and has access to the options passed to * the middleware too. * * @param options {ConfigType} - The options used to setup the middleware. */ init?: (options: ConfigType<E, P, I>) => void; /** * Method to fetch a client's hit count and reset time. * * @param key {string} - The identifier for a client. * * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client. */ get?: (key: string) => Promisify<ClientRateLimitInfo | undefined>; /** * Method to increment a client's hit counter. * * @param key {string} - The identifier for a client. * * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client. */ increment: (key: string) => Promisify<ClientRateLimitInfo>; /** * Method to decrement a client's hit counter. * * @param key {string} - The identifier for a client. */ decrement: (key: string) => Promisify<void>; /** * Method to reset a client's hit counter. * * @param key {string} - The identifier for a client. */ resetKey: (key: string) => Promisify<void>; /** * Method to reset everyone's hit counter. */ resetAll?: () => Promisify<void>; /** * Method to shutdown the store, stop timers, and release all resources. */ shutdown?: () => Promisify<void>; /** * Flag to indicate that keys incremented in one instance of this store can * not affect other instances. Typically false if a database is used, true for * MemoryStore. * * Used to help detect double-counting misconfigurations. */ localKeys?: boolean; /** * Optional value that the store prepends to keys * * Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes */ prefix?: string; }; export type GeneralConfigType<T extends { keyGenerator: unknown; }> = Pick<T, "keyGenerator"> & Partial<Omit<T, "keyGenerator">>;