@@ -62,9 +62,9 @@ type CampaignListItem = CampaignRecord & { progress: CampaignProgress };
6262
6363const CAMPAIGN_STATUSES : CampaignStatus [ ] = [ 'open' , 'funded' , 'claimed' , 'failed' ] ;
6464const CONTRACT_AMOUNT_DECIMALS = Number ( process . env . CONTRACT_AMOUNT_DECIMALS ?? 2 ) ;
65- const RATE_LIMIT_WINDOW_MS = 60_000 ;
66- const RATE_LIMIT_MAX_REQUESTS = 120 ;
67- const WRITE_RATE_LIMIT_MAX_REQUESTS = 40 ;
65+ const RATE_LIMIT_WINDOW_MS = Number ( process . env . RATE_LIMIT_WINDOW_MS ?? 60000 ) ;
66+ const RATE_LIMIT_MAX_REQUESTS = Number ( process . env . RATE_LIMIT_READ_LIMIT ?? process . env . RATE_LIMIT_MAX_REQUESTS ?? 120 ) ;
67+ const WRITE_RATE_LIMIT_MAX_REQUESTS = Number ( process . env . RATE_LIMIT_WRITE_LIMIT ?? process . env . WRITE_RATE_LIMIT_MAX_REQUESTS ?? 20 ) ;
6868const CAMPAIGN_DETAIL_PLEDGE_PREVIEW_LIMIT = 5 ;
6969
7070app . use (
@@ -103,33 +103,44 @@ if (process.env.NODE_ENV === "production") {
103103
104104const rateLimitBuckets = new Map < string , { count : number ; resetAt : number } > ( ) ;
105105
106- function applyRateLimit ( maxRequests : number ) {
106+ export function applyRateLimit ( limitOverride ? : number ) {
107107 return ( req : Request , res : Response , next : express . NextFunction ) => {
108- const key = `${ req . ip } :${ req . path } :${ maxRequests } ` ;
108+ if ( ( req as any ) . rateLimitedProcessed ) {
109+ return next ( ) ;
110+ }
111+ ( req as any ) . rateLimitedProcessed = true ;
112+
113+ const isWrite = [ "POST" , "PUT" , "PATCH" , "DELETE" ] . includes ( req . method ) ;
114+ const maxRequests = limitOverride ?? ( isWrite ? WRITE_RATE_LIMIT_MAX_REQUESTS : RATE_LIMIT_MAX_REQUESTS ) ;
115+
116+ const key = `${ req . ip } :${ isWrite ? "write" : "read" } ` ;
109117 const now = Date . now ( ) ;
110118 const current = rateLimitBuckets . get ( key ) ;
111119
112- if ( ! current || now >= current . resetAt ) {
113- rateLimitBuckets . set ( key , {
114- count : 1 ,
115- resetAt : now + RATE_LIMIT_WINDOW_MS ,
116- } ) ;
117- return next ( ) ;
120+ let count = 1 ;
121+ let resetAt = now + RATE_LIMIT_WINDOW_MS ;
122+
123+ if ( current && now < current . resetAt ) {
124+ count = current . count + 1 ;
125+ resetAt = current . resetAt ;
118126 }
119127
120- if ( current . count >= maxRequests ) {
128+ res . setHeader ( "X-RateLimit-Limit" , String ( maxRequests ) ) ;
129+ res . setHeader ( "X-RateLimit-Remaining" , String ( Math . max ( 0 , maxRequests - count ) ) ) ;
130+ res . setHeader ( "X-RateLimit-Reset" , String ( Math . ceil ( resetAt / 1000 ) ) ) ;
131+
132+ if ( current && now < current . resetAt && current . count >= maxRequests ) {
121133 const retryAfterSec = Math . max ( 1 , Math . ceil ( ( current . resetAt - now ) / 1000 ) ) ;
122- res . setHeader ( ' Retry-After' , String ( retryAfterSec ) ) ;
123- throw new AppError ( ' Rate limit exceeded. Please retry shortly.' , 429 , ' RATE_LIMITED' ) ;
134+ res . setHeader ( " Retry-After" , String ( retryAfterSec ) ) ;
135+ throw new AppError ( " Rate limit exceeded. Please retry shortly." , 429 , " RATE_LIMITED" ) ;
124136 }
125137
126- current . count += 1 ;
127- rateLimitBuckets . set ( key , current ) ;
138+ rateLimitBuckets . set ( key , { count, resetAt } ) ;
128139 return next ( ) ;
129140 } ;
130141}
131142
132- app . use ( applyRateLimit ( RATE_LIMIT_MAX_REQUESTS ) ) ;
143+ app . use ( applyRateLimit ( ) ) ;
133144
134145app . use ( requestIdMiddleware ) ;
135146
0 commit comments