@lock-dev/rate-limit
Version:
Rate limiter module for lock.dev security framework
484 lines (469 loc) • 20.7 kB
TypeScript
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 };