@ -87,8 +87,6 @@ RL_INDEX_API_BLOCKED_IPS = 'rl:index:api_blocked_ips'
# TTL set only on first INCR (Lua); daily=24h, weekly=7d — cleanup only,
# resets are implicit in the date/week embedded in the key name.
# ---------------------------------------------------------------------------
QUOTA_USER_DAILY = ' quota: {ns} :daily: {date} :user: {uid} '
QUOTA_USER_WEEKLY = ' quota: {ns} :weekly: {week} :user: {uid} '
QUOTA_IP_DAILY = ' quota: {ns} :daily: {date} :ip: {ip} '
QUOTA_IP_WEEKLY = ' quota: {ns} :weekly: {week} :ip: {ip} '
@ -310,8 +308,6 @@ class RateLimitMiddleware:
]
self . api_quota_anon_daily = settings . API_QUOTA_ANON_DAILY
self . api_quota_anon_weekly = settings . API_QUOTA_ANON_WEEKLY
self . api_quota_auth_daily = settings . API_QUOTA_AUTH_DAILY
self . api_quota_auth_weekly = settings . API_QUOTA_AUTH_WEEKLY
self . api_rate_limit_enabled = getattr ( settings , ' API_RATE_LIMIT_ENABLED ' , True )
self . api_threshold = getattr ( settings , ' API_RATE_LIMIT_THRESHOLD ' , 60 )
self . api_window = getattr ( settings , ' API_RATE_LIMIT_WINDOW_SECONDS ' , 60 )
@ -326,11 +322,9 @@ class RateLimitMiddleware:
[ p . pattern for p in self . _bypass_paths ] or ' (none) ' ,
)
logger . info (
' [API QUOTAS] daily_anon= %s weekly_anon= %s daily_auth= %s weekly_auth= %s ' ,
' [API QUOTAS] daily_anon= %s weekly_anon= %s (auth: governed by per-user 240/min rate) ' ,
settings . API_QUOTA_ANON_DAILY ,
settings . API_QUOTA_ANON_WEEKLY ,
settings . API_QUOTA_AUTH_DAILY ,
settings . API_QUOTA_AUTH_WEEKLY ,
)
logger . info (
' [API RATE LIMIT] enabled= %s threshold= %s window= %s s block= %s s same_origin_bypass= %s ' ,
@ -569,34 +563,28 @@ class RateLimitMiddleware:
def _check_api_quota ( self , request ) :
"""
Increment per - consumer daily and weekly API quota counters .
Returns ' daily ' or ' weekly ' if the respective limit is exceeded , else None .
Increment per - IP daily and weekly API quota counters for anonymous callers .
Authenticated users are rate - limited per - user by _evaluate ( 240 / min ) and
are not subject to a daily quota — returns None immediately for auth users .
Fails open ( returns None ) if Redis / cache is unavailable .
Consumer identity : authenticated users by pk , anonymous by masked IP .
"""
user = getattr ( request , ' user ' , None )
if user and user . is_authenticated :
return None
today = date . today ( )
iso = today . isocalendar ( )
date_str = today . isoformat ( )
week_str = f ' { iso [ 0 ] } -W { iso [ 1 ] : 02d } '
user = getattr ( request , ' user ' , None )
if user and user . is_authenticated :
uid = str ( user . pk )
d_key = QUOTA_USER_DAILY . format ( ns = _NAMESPACE , date = date_str , uid = uid )
w_key = QUOTA_USER_WEEKLY . format ( ns = _NAMESPACE , week = week_str , uid = uid )
d_limit = self . api_quota_auth_daily
w_limit = self . api_quota_auth_weekly
else :
ip = get_client_ip ( request )
d_key = QUOTA_IP_DAILY . format ( ns = _NAMESPACE , date = date_str , ip = ip )
w_key = QUOTA_IP_WEEKLY . format ( ns = _NAMESPACE , week = week_str , ip = ip )
d_limit = self . api_quota_anon_daily
w_limit = self . api_quota_anon_weekly
ip = get_client_ip ( request )
d_key = QUOTA_IP_DAILY . format ( ns = _NAMESPACE , date = date_str , ip = ip )
w_key = QUOTA_IP_WEEKLY . format ( ns = _NAMESPACE , week = week_str , ip = ip )
try :
if _incr_with_ttl ( d_key , 86400 ) > d_limit :
if _incr_with_ttl ( d_key , 86400 ) > self . api_quota_anon_daily :
return ' daily '
if _incr_with_ttl ( w_key , 7 * 86400 ) > w_limit :
if _incr_with_ttl ( w_key , 7 * 86400 ) > self . api_quota_anon_weekly :
return ' weekly '
except Exception :
pass # fail open — quota not enforced when Redis unavailable