@@ -3,6 +3,32 @@ const redisConfig = require('../config/redis');
33const securityConfig = require ( '../config/security' ) ;
44const logger = require ( '../utils/logger' ) ;
55
6+ const ONE_MINUTE = 60 * 1000 ;
7+
8+ const publicRateLimitTiers = {
9+ strict : {
10+ windowMs : ONE_MINUTE ,
11+ max : 5 ,
12+ message : 'Too many authentication attempts, please try again after a minute' ,
13+ keyPrefix : 'rl:public:strict:' ,
14+ keyByUser : false ,
15+ } ,
16+ moderate : {
17+ windowMs : ONE_MINUTE ,
18+ max : 30 ,
19+ message : 'Too many content write requests, please try again after a minute' ,
20+ keyPrefix : 'rl:public:moderate:' ,
21+ keyByUser : true ,
22+ } ,
23+ liberal : {
24+ windowMs : ONE_MINUTE ,
25+ max : 100 ,
26+ message : 'Too many read requests, please try again after a minute' ,
27+ keyPrefix : 'rl:public:liberal:' ,
28+ keyByUser : false ,
29+ } ,
30+ } ;
31+
632/**
733 * Custom Simple Redis Store for express-rate-limit
834 */
@@ -21,12 +47,11 @@ class RedisStore {
2147 multi . expire ( fullKey , this . expiry ) ;
2248 const results = await multi . exec ( ) ;
2349
24- const currentCount = results [ 0 ] ;
25- const ttl = results [ 1 ] ;
50+ const currentCount = Array . isArray ( results [ 0 ] ) ? results [ 0 ] [ 1 ] : results [ 0 ] ;
2651
2752 return {
2853 totalHits : currentCount ,
29- resetTime : new Date ( Date . now ( ) + ( ttl * 1000 || this . expiry * 1000 ) )
54+ resetTime : new Date ( Date . now ( ) + ( this . expiry * 1000 ) )
3055 } ;
3156 } catch ( error ) {
3257 logger . error ( `RedisStore error for key ${ fullKey } :` , error ) ;
@@ -63,6 +88,7 @@ const createRateLimiter = (options = {}) => {
6388 max = securityConfig . tiers . default . max ,
6489 message = securityConfig . tiers . default . message ,
6590 keyPrefix = 'rl:' ,
91+ keyByUser = true ,
6692 } = options ;
6793
6894 const securityService = require ( '../services/securityService' ) ;
@@ -75,7 +101,7 @@ const createRateLimiter = (options = {}) => {
75101 message,
76102 } ,
77103 standardHeaders : true ,
78- legacyHeaders : false ,
104+ legacyHeaders : true ,
79105 store : new RedisStore ( {
80106 prefix : keyPrefix ,
81107 expiry : Math . ceil ( windowMs / 1000 ) ,
@@ -94,11 +120,10 @@ const createRateLimiter = (options = {}) => {
94120 res . status ( options . statusCode ) . send ( options . message ) ;
95121 } ,
96122 keyGenerator : ( req ) => {
97- // Use user ID if authenticated, else fallback to IP
98- if ( req . user && ( req . user . id || req . user . sub ) ) {
99- return `${ keyPrefix } ${ req . user . id || req . user . sub } ` ;
123+ if ( keyByUser && req . user && ( req . user . id || req . user . sub || req . user . userId ) ) {
124+ return `user:${ req . user . id || req . user . sub || req . user . userId } ` ;
100125 }
101- return req . ip ;
126+ return `ip: ${ req . ip } ` ;
102127 } ,
103128 skip : ( req ) => {
104129 // Skip whitelisted IPs or in test environment (unless explicitly testing security)
@@ -141,9 +166,7 @@ const adminLimiter = createRateLimiter({
141166
142167// Endpoint-specific limiters
143168const authLimiter = createRateLimiter ( {
144- windowMs : securityConfig . endpoints . auth . windowMs ,
145- max : securityConfig . endpoints . auth . max ,
146- message : securityConfig . endpoints . auth . message ,
169+ ...publicRateLimitTiers . strict ,
147170 keyPrefix : 'rl:auth:' ,
148171} ) ;
149172
@@ -161,6 +184,32 @@ const ipfsLimiter = createRateLimiter({
161184 keyPrefix : 'rl:ipfs:' ,
162185} ) ;
163186
187+ const strictLimiter = createRateLimiter ( publicRateLimitTiers . strict ) ;
188+
189+ const moderateLimiter = createRateLimiter ( publicRateLimitTiers . moderate ) ;
190+
191+ const liberalLimiter = createRateLimiter ( publicRateLimitTiers . liberal ) ;
192+
193+ const contentWriteLimiter = createRateLimiter ( {
194+ ...publicRateLimitTiers . moderate ,
195+ keyPrefix : 'rl:content:write:' ,
196+ } ) ;
197+
198+ const readLimiter = createRateLimiter ( {
199+ ...publicRateLimitTiers . liberal ,
200+ keyPrefix : 'rl:public:read:' ,
201+ } ) ;
202+
203+ const searchWriteLimiter = createRateLimiter ( {
204+ ...publicRateLimitTiers . moderate ,
205+ keyPrefix : 'rl:search:write:' ,
206+ } ) ;
207+
208+ const courseWriteLimiter = createRateLimiter ( {
209+ ...publicRateLimitTiers . moderate ,
210+ keyPrefix : 'rl:courses:write:' ,
211+ } ) ;
212+
164213/**
165214 * Middleware to select rate limiter based on user role
166215 */
@@ -189,5 +238,13 @@ module.exports = {
189238 authLimiter,
190239 transactionLimiter,
191240 ipfsLimiter,
241+ strictLimiter,
242+ moderateLimiter,
243+ liberalLimiter,
244+ contentWriteLimiter,
245+ readLimiter,
246+ searchWriteLimiter,
247+ courseWriteLimiter,
248+ publicRateLimitTiers,
192249 createRateLimiter
193250} ;
0 commit comments