Files
OSINT/accounts/form_mixins.py
Iliyan Angelov ed94dd22dd update
2025-11-26 22:32:20 +02:00

142 lines
4.5 KiB
Python

"""
Form mixins for bot protection and validation.
"""
from django import forms
from django.core.cache import cache
from django.utils import timezone
from datetime import timedelta
import time
class HoneypotMixin:
"""
Honeypot field mixin - adds a hidden field that bots will fill but humans won't.
"""
# This field should be left empty by real users
website = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={'tabindex': '-1', 'autocomplete': 'off'}),
label='', # Empty label so screen readers skip it
)
def clean_website(self):
"""If this field is filled, it's likely a bot."""
website = self.cleaned_data.get('website')
if website:
raise forms.ValidationError('Bot detected. Please try again.')
return website
class TimeBasedValidationMixin:
"""
Time-based validation - prevents forms from being submitted too quickly (bot behavior).
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add a hidden timestamp field
self.fields['form_timestamp'] = forms.CharField(
required=False,
widget=forms.HiddenInput(),
initial=str(time.time())
)
def clean_form_timestamp(self):
"""Validate that form wasn't submitted too quickly."""
timestamp = self.cleaned_data.get('form_timestamp')
if not timestamp:
# If timestamp is missing, it might be a bot
raise forms.ValidationError('Invalid form submission.')
try:
submit_time = float(timestamp)
current_time = time.time()
elapsed = current_time - submit_time
# Forms submitted in less than 2 seconds are likely bots
if elapsed < 2:
raise forms.ValidationError('Form submitted too quickly. Please take your time.')
# Forms submitted after 1 hour are likely stale
if elapsed > 3600:
raise forms.ValidationError('Form session expired. Please refresh and try again.')
except (ValueError, TypeError):
raise forms.ValidationError('Invalid form submission.')
return timestamp
class RateLimitMixin:
"""
Rate limiting mixin - prevents too many form submissions from the same IP/user.
"""
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super().__init__(*args, **kwargs)
def clean(self):
cleaned_data = super().clean()
if self.request:
# Get client IP
ip = self.get_client_ip(self.request)
# Create a unique key for this form type
form_name = self.__class__.__name__
cache_key = f'form_submission_{form_name}_{ip}'
# Check submission count
submissions = cache.get(cache_key, 0)
# Limit: 10 submissions per hour per IP
if submissions >= 10:
raise forms.ValidationError(
'Too many submissions. Please wait before submitting again.'
)
# Increment counter
cache.set(cache_key, submissions + 1, 3600) # 1 hour
return cleaned_data
def get_client_ip(self, request):
"""Get client IP address."""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
class BrowserFingerprintMixin:
"""
Browser fingerprint validation - ensures form is submitted from a real browser.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['user_agent_hash'] = forms.CharField(
required=False,
widget=forms.HiddenInput()
)
def clean_user_agent_hash(self):
"""Validate user agent is present and reasonable."""
ua_hash = self.cleaned_data.get('user_agent_hash')
# If no user agent hash, it might be a bot
if not ua_hash:
raise forms.ValidationError('Invalid browser signature.')
return ua_hash
class BotProtectionMixin(HoneypotMixin, TimeBasedValidationMixin, RateLimitMixin):
"""
Combined bot protection mixin that includes:
- Honeypot field
- Time-based validation
- Rate limiting
"""
pass