Rename hoptcha_required to hoptcha_protected

This commit is contained in:
Ivan Nikolskiy 2025-05-28 00:01:02 +02:00
parent 64269ee1f8
commit 8dbf0f9df6
3 changed files with 25 additions and 20 deletions

View File

@ -81,9 +81,9 @@ CAPTCHA_PRIVATE_KEY = 'your-secret-key'
Import the decorator and apply it to any Django view: Import the decorator and apply it to any Django view:
```python ```python
from django_hoptcha.decorators import hoptcha_required from django_hoptcha.decorators import hoptcha_protected
@hoptcha_required() @hoptcha_protected()
def my_view(request): def my_view(request):
return JsonResponse({'success': True}) return JsonResponse({'success': True})
``` ```
@ -156,7 +156,7 @@ def ip_and_student_id(request):
Apply it like this: Apply it like this:
```python ```python
@hoptcha_required(threshold=5, timeout=300, key_func=ip_and_student_id) @hoptcha_protected(threshold=5, timeout=300, key=ip_and_student_id)
def send_reset_code(request): def send_reset_code(request):
... ...
``` ```
@ -169,15 +169,15 @@ This package works seamlessly with [`django-ratelimit`](https://pypi.org/project
```python ```python
from ratelimit.decorators import ratelimit from ratelimit.decorators import ratelimit
from django_hoptcha.decorators import hoptcha_required from django_hoptcha.decorators import hoptcha_protected
@ratelimit(key='ip', rate='15/m', method='POST', block=True) @ratelimit(key='ip', rate='15/m', method='POST', block=True)
@hoptcha_required(threshold=5, timeout=300) @hoptcha_protected(threshold=5, timeout=300)
def secure_view(request): def secure_view(request):
... ...
``` ```
The `@ratelimit` decorator enforces strict rate limits while `@hoptcha_required` provides a soft CAPTCHA fallback. The `@ratelimit` decorator enforces strict rate limits while `@hoptcha_protected` provides a soft CAPTCHA fallback.
### Custom CAPTCHA UI Rendering ### Custom CAPTCHA UI Rendering
@ -196,10 +196,10 @@ hoptchaPost('/endpoint', payload, onSuccess, onError, function renderCustom(url,
## 🔧 Customization ## 🔧 Customization
| Parameter | Description | Default | | Parameter | Description | Default |
| ----------- | ----------------------------------------------------- | ------------- | |-------------|--------------------------------------------------------------|---------------|
| `threshold` | Number of allowed attempts before CAPTCHA is required | `5` | | `threshold` | Number of allowed attempts before CAPTCHA is required | `5` |
| `timeout` | Time in seconds to reset attempt count | `300` (5 min) | | `timeout` | Time in seconds to reset attempt count | `300` (5 min) |
| `key_func` | Function to identify requestor (IP, user ID, etc.) | IP address | | `key` | Function or string to identify requestor (IP, user ID, etc.) | `ip` |
--- ---

View File

@ -28,20 +28,26 @@ from django.http import JsonResponse
from .validators import verify_token from .validators import verify_token
from .settings import GENERATE_URL, PUBLIC_KEY from .settings import GENERATE_URL, PUBLIC_KEY
def default_key_func(request): def get_builtin_key_func(name):
return request.META.get('REMOTE_ADDR', 'unknown-ip') if name == 'ip':
return lambda request: request.META.get('REMOTE_ADDR', 'unknown-ip')
raise ValueError(f"Unknown built-in key: '{name}'")
def hoptcha_required(threshold=5, timeout=300, key_func=default_key_func): def hoptcha_protected(threshold=5, timeout=300, key='ip'):
""" """
Decorator that applies CAPTCHA verification after `threshold` failed attempts, Decorator that applies CAPTCHA verification after `threshold` failed attempts,
tracked via cache using a customizable `key_func`. tracked via cache using a customizable key. The `key` can be:
- a string like 'ip' to use a built-in method
- a callable that accepts a Django request and returns a string
""" """
key_func = get_builtin_key_func(key) if isinstance(key, str) else key
def decorator(view_func): def decorator(view_func):
@wraps(view_func) @wraps(view_func)
def _wrapped_view(request, *args, **kwargs): def _wrapped_view(request, *args, **kwargs):
key = key_func(request) key_val = key_func(request)
cache_key = f"hoptcha-attempts:{key}" cache_key = f"hoptcha-attempts:{key_val}"
attempts = cache.get(cache_key, 0) attempts = cache.get(cache_key, 0)
if attempts >= threshold: if attempts >= threshold:
@ -53,7 +59,6 @@ def hoptcha_required(threshold=5, timeout=300, key_func=default_key_func):
"key": PUBLIC_KEY "key": PUBLIC_KEY
}, status=400) }, status=400)
# Increment and store attempt count unless the view says otherwise
cache.set(cache_key, attempts + 1, timeout=timeout) cache.set(cache_key, attempts + 1, timeout=timeout)
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
return _wrapped_view return _wrapped_view

View File

@ -25,10 +25,10 @@ SOFTWARE.
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django_hoptcha.decorators import hoptcha_required from django_hoptcha.decorators import hoptcha_protected
@csrf_exempt @csrf_exempt
@hoptcha_required(threshold=3, timeout=300) @hoptcha_protected(threshold=3, timeout=300)
def protected_form_submit(request): def protected_form_submit(request):
name = request.POST.get("name") name = request.POST.get("name")
if not name: if not name: