Browse Source

Fix ratelimit cache key prefix: strip Django version/prefix mangling

django-ratelimit (the @ratelimit view decorator) writes its counters
through Django's cache framework.  Django's default key function produces
'{KEY_PREFIX}:{VERSION}:{key}', so with KEY_PREFIX='' and VERSION=1
(defaults) the decorator keys appear in Redis as ':1:rl:{hash}' — an
ugly leading colon that makes them look distinct from the clean 'rl:*'
keys written by RateLimitMiddleware via get_redis_connection().

Add make_ratelimit_cache_key() to sapl/middleware/ratelimit.py (a simple
pass-through) and wire it into the 'ratelimit' cache config via
KEY_FUNCTION.  Both key families now share the same 'rl:*' namespace:

  decorator keys    →  rl:{hash}
  middleware keys   →  rl:ip:{ip}:reqs
                       rl:{ns}:user:{uid}:reqs
                       rl:{ns}:ip:{ip}:w:{bucket}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rate-limiter-2026
Edward Ribeiro 3 weeks ago
parent
commit
1839af015d
  1. 16
      sapl/middleware/ratelimit.py
  2. 7
      sapl/settings.py

16
sapl/middleware/ratelimit.py

@ -77,6 +77,22 @@ _INCR_LUA = """
""" """
def make_ratelimit_cache_key(key, key_prefix, version):
"""
Pass-through cache key function for the 'ratelimit' Django cache backend.
Django's default key function produces '{KEY_PREFIX}:{VERSION}:{key}',
which turns django-ratelimit's own keys (already prefixed 'rl:{hash}')
into ':1:rl:{hash}' an ugly leading colon and version number that does
not match the clean 'rl:*' keys written directly by RateLimitMiddleware.
Setting KEY_FUNCTION to this function makes both key namespaces consistent:
django-ratelimit decorator keys rl:{hash}
RateLimitMiddleware keys rl:ip:{ip}:reqs / rl:{ns}:user:{uid}:reqs /
"""
return key
def _sha256(s): def _sha256(s):
return hashlib.sha256(s.encode()).hexdigest() return hashlib.sha256(s.encode()).hexdigest()

7
sapl/settings.py

@ -252,13 +252,18 @@ CACHES = {
} }
), ),
}, },
# DB1 — rate-limiter counters (raw keys, no KEY_PREFIX) # DB1 — rate-limiter counters (raw keys, no KEY_PREFIX / version mangling)
'ratelimit': { 'ratelimit': {
'BACKEND': ( 'BACKEND': (
'django_redis.cache.RedisCache' if _redis_ready 'django_redis.cache.RedisCache' if _redis_ready
else 'django.core.cache.backends.filebased.FileBasedCache' else 'django.core.cache.backends.filebased.FileBasedCache'
), ),
'LOCATION': REDIS_URL + '/1' if _redis_ready else '/var/tmp/django_ratelimit_cache', 'LOCATION': REDIS_URL + '/1' if _redis_ready else '/var/tmp/django_ratelimit_cache',
# Pass-through key function so django-ratelimit decorator keys ('rl:{hash}')
# are stored as-is, matching the 'rl:*' keys written directly by
# RateLimitMiddleware via get_redis_connection(). Without this, Django's
# default key function would produce ':1:rl:{hash}' (empty prefix + version).
'KEY_FUNCTION': 'sapl.middleware.ratelimit.make_ratelimit_cache_key',
**( **(
{ {
'OPTIONS': { 'OPTIONS': {

Loading…
Cancel
Save