Rename hoptcha_required to hoptcha_protected
This commit is contained in:
parent
64269ee1f8
commit
8dbf0f9df6
22
README.md
22
README.md
@ -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
|
||||||
|
|
||||||
@ -195,11 +195,11 @@ 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` |
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user