UNPKG

@lock-dev/rate-limit

Version:

Rate limiter module for lock.dev security framework

484 lines (469 loc) 20.7 kB
import * as _lock_dev_core from '@lock-dev/core'; import { SecurityContext } from '@lock-dev/core'; type RateLimitStrategy = 'fixed-window' | 'sliding-window' | 'token-bucket' | 'leaky-bucket' | 'adaptive'; type RateLimitStorage = 'memory' | 'redis' | 'upstash'; type GeoProvider = 'ipapi' | 'maxmind'; declare enum RateLimitEventType { RATE_LIMITED = "rate_limited", RATE_LIMIT_WARNING = "rate_limit_warning", RATE_LIMIT_ADAPTIVE_ESCALATION = "rate_limit_adaptive_escalation", DDOS_PROTECTION_TRIGGERED = "ddos_protection_triggered" } interface RateLimitKey { identifier: string; resource?: string; country?: string; } interface RateLimitRecord { count: number; firstRequest: number; lastRequest: number; history?: number[]; tokens?: number; warningIssued?: boolean; ddosScore?: number; } interface RateLimitResult { passed: boolean; remaining?: number; limit?: number; retry?: number; reason?: string; data?: any; } interface RateLimitConfig { limit: number; windowMs: number; strategy?: RateLimitStrategy; storage?: RateLimitStorage; resources?: { [resource: string]: { limit: number; windowMs: number; }; }; countryLimits?: { [country: string]: { limit: number; windowMs: number; }; }; geoProvider?: { type: GeoProvider; dbPath?: string; cacheSize?: number; cacheTtl?: number; }; adaptive?: { enabled: boolean; thresholds: { normal: number; elevated: number; high: number; extreme: number; }; escalationPeriod: number; cooldownPeriod: number; }; ddosPrevention?: { enabled: boolean; requestRateThreshold: number; burstThreshold: number; banDurationMs: number; ipReputation?: boolean; behavioralAnalysis?: boolean; challengeMode?: boolean; }; headers?: boolean; standardHeaders?: boolean; headerLimit?: string; headerRemaining?: string; headerReset?: string; statusCode?: number; message?: string; ipHeaders?: string[]; useRemoteAddress?: boolean; redis?: { url?: string; host?: string; port?: number; password?: string; username?: string; database?: number; keyPrefix?: string; }; upstash?: { url: string; token: string; keyPrefix?: string; }; memoryOptions?: { max: number; ttl: number; }; skipFunction?: (context: SecurityContext) => boolean | Promise<boolean>; keyGenerator?: (context: SecurityContext) => string | Promise<string>; handler?: (context: SecurityContext, info: any) => void | Promise<void>; } interface RateLimitStore { init(): Promise<void>; get(key: string): Promise<RateLimitRecord | null>; set(key: string, value: RateLimitRecord): Promise<void>; increment(key: string, value?: number): Promise<RateLimitRecord>; reset(key: string): Promise<void>; close(): Promise<void>; addToHistory?(key: string, timestamp: number): Promise<void>; getWindowHistory?(key: string, startTime: number): Promise<number[]>; } interface GeoLocationProvider { lookupCountry(ip: string): Promise<string | null>; } /** * Fixed In-memory store implementation for rate limiting. * This implementation uses an LRU (Least Recently Used) cache to manage rate limit records, * providing methods to initialize, get, set, increment, reset, and manage the history of requests. * * @class MemoryStore * @implements {RateLimitStore} */ declare class MemoryStore implements RateLimitStore { private cache; /** * Creates an instance of MemoryStore. * * @param {Object} options - Configuration options for the store. * @param {number} options.max - Maximum number of items the cache can hold. * @param {number} options.ttl - Time-to-live for each cache entry in milliseconds. */ constructor(options: { max: number; ttl: number; }); /** * Initializes the MemoryStore. * This method is called to set up the store and can be used to perform any asynchronous initialization tasks. * * @returns {Promise<void>} A promise that resolves when the store is initialized. */ init(): Promise<void>; /** * Retrieves a rate limit record from the store for the given key. * * @param {string} key - The key identifying the rate limit record. * @returns {Promise<RateLimitRecord | null>} A promise that resolves with the record if found, or null if not. */ get(key: string): Promise<RateLimitRecord | null>; /** * Stores a rate limit record in the store under the given key. * * @param {string} key - The key under which to store the record. * @param {RateLimitRecord} value - The rate limit record to store. * @returns {Promise<void>} A promise that resolves once the record is stored. */ set(key: string, value: RateLimitRecord): Promise<void>; /** * Increments the rate limit counter for a given key. * If the key does not exist, a new record is created. * The function updates the record's count, lastRequest timestamp, and history. * * @param {string} key - The key identifying the rate limit record. * @param {number} [value=1] - The value to increment the counter by. * @returns {Promise<RateLimitRecord>} A promise that resolves with the updated rate limit record. */ increment(key: string, value?: number): Promise<RateLimitRecord>; /** * Resets the rate limit record for a given key by removing it from the store. * * @param {string} key - The key identifying the rate limit record to reset. * @returns {Promise<void>} A promise that resolves once the record is removed. */ reset(key: string): Promise<void>; /** * Closes the store. * This method can be used to perform any necessary cleanup operations. * * @returns {Promise<void>} A promise that resolves when the store is closed. */ close(): Promise<void>; /** * Adds a specific timestamp to the history of requests for a given key. * If the record exists, the timestamp is appended to its history. * * @param {string} key - The key identifying the rate limit record. * @param {number} timestamp - The timestamp to add to the record's history. * @returns {Promise<void>} A promise that resolves once the timestamp is added. */ addToHistory(key: string, timestamp: number): Promise<void>; /** * Retrieves the history of timestamps for a given key, filtered to include only those * entries that occurred after a specified start time. * * @param {string} key - The key identifying the rate limit record. * @param {number} startTime - The start time (inclusive) for filtering the history. * @returns {Promise<number[]>} A promise that resolves with an array of timestamps from the history that meet the criteria. */ getWindowHistory(key: string, startTime: number): Promise<number[]>; } declare class RedisStore implements RateLimitStore { private client; private keyPrefix; private config; constructor(config: RateLimitConfig); init(): Promise<void>; get(key: string): Promise<RateLimitRecord | null>; set(key: string, value: RateLimitRecord): Promise<void>; increment(key: string, value?: number): Promise<RateLimitRecord>; reset(key: string): Promise<void>; close(): Promise<void>; addToHistory(key: string, timestamp: number): Promise<void>; getWindowHistory(key: string, startTime: number): Promise<number[]>; } declare class UpstashStore implements RateLimitStore { private client; private keyPrefix; private config; constructor(config: RateLimitConfig); init(): Promise<void>; get(key: string): Promise<RateLimitRecord | null>; set(key: string, value: RateLimitRecord): Promise<void>; increment(key: string, value?: number): Promise<RateLimitRecord>; reset(key: string): Promise<void>; close(): Promise<void>; addToHistory(key: string, timestamp: number): Promise<void>; getWindowHistory(key: string, startTime: number): Promise<number[]>; } /** * Creates the appropriate storage implementation based on configuration * @param config Rate limiter configuration * @returns Initialized storage implementation */ declare function createStore(config: RateLimitConfig): Promise<RateLimitStore>; /** * Fixed Window Rate Limit Strategy Implementation * * Algorithm Overview: * 1. Get the current timestamp. * 2. Retrieve the rate limit record for the given key from the store. * 3. If no record exists or the current time exceeds the window duration since the first request: * - Reset the window by creating a new record with the current timestamp. * - Set the counter to 1 and store the record. * - Allow the request and return the remaining limit. * 4. Otherwise, if the record exists and the window is still valid: * - Increment the record count. * - Calculate the elapsed time and the remaining window duration. * - If the incremented count exceeds the defined limit: * - Deny the request, return rate-limited response with retry time. * - Otherwise: * - Allow the request and update the remaining limit. * * @class FixedWindowStrategy * @implements {RateLimitStrategyImplementation} */ declare class FixedWindowStrategy implements RateLimitStrategyImplementation { /** * Checks the rate limit for a given key and returns the result. * * @param {string} key - The unique key representing the request (often an IP or identifier). * @param {SecurityContext} context - The security context containing the request data. * @param {RateLimitConfig} config - The configuration object containing limit and window settings. * @param {RateLimitStore} store - The store instance to get, set, or increment the rate limit record. * @returns {Promise<RateLimitResult>} A promise that resolves with the rate limit result including status, remaining quota, limit, and retry time. */ check(key: string, context: SecurityContext, config: RateLimitConfig, store: RateLimitStore): Promise<RateLimitResult>; } /** * Leaky Bucket rate limiting implementation. * Processes requests at a fixed outflow rate with a limited bucket capacity, * simulating a "leaky bucket" where excess requests are discarded or delayed. * * Algorithm Overview: * 1. Determine the outflow rate (i.e. the rate at which requests "leak" out of the bucket) * by dividing the limit by the window duration (requests per millisecond). * 2. Retrieve the current rate limit record from the store using the provided key. * 3. If no record exists: * - Initialize a new record with a count of 1 (representing the first request), * and set both the firstRequest and lastRequest timestamps to the current time. * - Store the new record and return a successful result with the remaining capacity. * 4. If a record exists: * - Calculate the time elapsed since the last request. * - Compute the number of leaked requests based on the outflow rate and time elapsed. * - Update the bucket level by subtracting the leaked requests (ensuring it does not fall below 0). * - If the updated level is below the bucket capacity: * - Accept the current request by incrementing the bucket count, * - Update the lastRequest timestamp, store the record, and return success with the remaining capacity. * - If the updated level equals or exceeds the bucket capacity: * - The bucket is full, so calculate the wait time required for the bucket to leak enough requests * to accommodate one new request. * - Update the lastRequest timestamp, store the record, and return a rate limited result * with the appropriate retry time. * * @class LeakyBucketStrategy * @implements {RateLimitStrategyImplementation} */ declare class LeakyBucketStrategy implements RateLimitStrategyImplementation { check(key: string, context: SecurityContext, config: RateLimitConfig, store: RateLimitStore): Promise<RateLimitResult>; } /** * Sliding Window rate limiting implementation. * More accurate counting of requests in a rolling time window. * * Algorithm Overview: * 1. Obtain the current timestamp and compute the start of the sliding window (now - windowMs). * 2. Retrieve the current rate limit record from the store using the provided key. * 3. If no record exists: * - Create a new record with: * - count set to 1, * - firstRequest and lastRequest set to the current timestamp, * - history array initialized with the current timestamp. * - Save the new record in the store and return a successful rate limit result. * 4. If a record exists: * - Ensure the record has a history array. * - Append the current timestamp to the history and update lastRequest. * - Filter the history array to include only timestamps within the sliding window. * - Update the record count based on the filtered history. * - Save the updated record back to the store. * 5. Determine if the request count exceeds the configured limit: * - If the count exceeds the limit, compute the retry time based on the oldest timestamp in the window, * then return a failure response with the reason "RATE_LIMITED" and the computed retry time. * - Otherwise, return a successful response including the remaining request count and the retry time. * * @class SlidingWindowStrategy * @implements {RateLimitStrategyImplementation} */ declare class SlidingWindowStrategy implements RateLimitStrategyImplementation { check(key: string, context: SecurityContext, config: RateLimitConfig, store: RateLimitStore): Promise<RateLimitResult>; } /** * Token Bucket rate limiting implementation. * Allows for bursts of traffic while maintaining an average rate. * * Algorithm Overview: * 1. Determine the refill rate in tokens per millisecond and the maximum tokens (bucket capacity) * based on the configuration. * 2. Retrieve the current rate limit record for the given key from the store. * 3. If no record exists: * - Initialize a new record with a full bucket (max tokens), consume one token for the current request, * and save the record. * - Return a successful rate limit result with the updated token count. * 4. If a record exists: * - Calculate the number of tokens to refill based on the elapsed time since the last request. * - Update the token count, ensuring it does not exceed the maximum capacity. * - Attempt to consume a token: * - If a token is available, deduct one token, update the request count and timestamp, then return success. * - If no token is available, calculate the wait time required to generate one token, * update the record, and return a failure result indicating rate limiting. * * @class TokenBucketStrategy * @implements {RateLimitStrategyImplementation} */ declare class TokenBucketStrategy implements RateLimitStrategyImplementation { check(key: string, context: SecurityContext, config: RateLimitConfig, store: RateLimitStore): Promise<RateLimitResult>; } /** * Adaptive rate limiting implementation. * Adjusts rate limits dynamically based on current traffic patterns and potential suspicious activity. * * Algorithm Overview: * 1. Check if adaptive rate limiting is enabled in the configuration; if not, fall back to the fixed window strategy. * 2. Retrieve the rate limit record from the store for the given key. * - If no record exists, create a new record with the current timestamp and initialize the history. * 3. Add the current request timestamp to the record’s history. * 4. Calculate the number of requests within the sliding window by filtering the history based on the window duration. * 5. Compute the current request rate (requests per second). * * 6. Calculate burst behavior using standard deviation of request intervals: * - Standard deviation (σ) measures how much the intervals between requests vary. * - Formula: σ = sqrt(variance), where variance = (1/n) * Σ(xᵢ - μ)² * - xᵢ = each interval between requests * - μ = average interval = (Σxᵢ)/n * - n = number of intervals * - A lower standard deviation indicates that requests are arriving at a more uniform rate, * which can be a sign of automated or bot traffic. * * 7. Based on predefined adaptive thresholds: * - Adjust the effective rate limit dynamically (reduce the limit if request rate exceeds thresholds). * - Set an escalation level indicating the severity (e.g., elevated, high, or extreme). * 8. Update the record with the current count and check if the dynamic limit is exceeded: * - If exceeded, log an escalation event if appropriate, and return a rate-limited response including a retry time. * - Otherwise, allow the request and return the remaining quota based on the dynamic limit. * * Importance of Standard Deviation in Adaptive Limiting: * Standard deviation is used here to detect burstiness or uniformity in request intervals. * - A low standard deviation relative to the mean interval (i.e., intervals are very consistent) * may indicate automated or scripted traffic. * - This metric is combined with the overall request rate to decide if the limit should be lowered dynamically. * * @class AdaptiveStrategy * @implements {RateLimitStrategyImplementation} */ declare class AdaptiveStrategy implements RateLimitStrategyImplementation { check(key: string, context: SecurityContext, config: RateLimitConfig, store: RateLimitStore): Promise<RateLimitResult>; } interface RateLimitStrategyImplementation { check(key: string, context: SecurityContext, config: RateLimitConfig, store: RateLimitStore): Promise<RateLimitResult>; } /** * Creates the appropriate rate limit strategy implementation based on configuration. * * @param {RateLimitConfig} config - The rate limiter configuration object. * @returns {RateLimitStrategyImplementation} The strategy implementation instance. */ declare function createStrategy(config: RateLimitConfig): RateLimitStrategyImplementation; /** * @class DDoSProtection */ declare class DDoSProtection { private config; private blacklist; private enabled; private requestRateThreshold; private burstThreshold; private banDurationMs; private windowMs; private readonly CRITICAL_THRESHOLD; private readonly HIGH_THRESHOLD; private readonly MEDIUM_THRESHOLD; private readonly LOW_THRESHOLD; /** * Creates an instance of DDoSProtection. */ constructor(config: RateLimitConfig); /** * Fast check to determine if IP is a known threat * This is a quick first-pass filter before more expensive analysis */ isKnownThreat(ip: string): { isThreat: boolean; level?: string; banDuration?: number; }; /** * Optimized analysis algorithm for DDoS threat detection * Employs early exits and efficient calculations */ analyze(key: string, context: SecurityContext, store: RateLimitStore): Promise<{ isThreat: boolean; level: 'none' | 'low' | 'medium' | 'high' | 'critical'; score: number; banDuration?: number; }>; /** * Optimized blacklist check with efficient expiration handling */ isBlacklisted(ip: string): boolean; /** * Get approximate blacklist size */ getBlacklistSize(): number; /** * Clear expired blacklist entries * Call periodically for memory management */ cleanBlacklist(): number; } /** * Create a rate limiting security module * @param config Module configuration */ declare const rateLimit: (config?: Partial<RateLimitConfig> | undefined) => _lock_dev_core.SecurityModule; export { AdaptiveStrategy, DDoSProtection, FixedWindowStrategy, type GeoLocationProvider, type GeoProvider, LeakyBucketStrategy, MemoryStore, type RateLimitConfig, RateLimitEventType, type RateLimitKey, type RateLimitRecord, type RateLimitResult, type RateLimitStorage, type RateLimitStore, type RateLimitStrategy, type RateLimitStrategyImplementation, RedisStore, SlidingWindowStrategy, TokenBucketStrategy, UpstashStore, createStore, createStrategy, rateLimit };