diff --git a/rowers/forms.py b/rowers/forms.py
index ad865dd6..e00c49b7 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -1066,7 +1066,8 @@ class RegistrationFormSex(RegistrationFormUniqueEmail):
month=4,
day=15))
- url = HoneypotField(label="URL")
+ hp_field = HoneypotField(label="URL")
+ timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
def clean_birthdate(self):
dob = self.cleaned_data['birthdate']
@@ -1087,6 +1088,22 @@ class RegistrationFormSex(RegistrationFormUniqueEmail):
adaptiveclass = forms.ChoiceField(label='Adaptive Classification',
choices=adaptivecategories, initial='None', required=False)
+ captcha = ReCaptchaField(widget=ReCaptchaV3(
+ attrs={
+ 'required_score': 0.85,
+ }))
+
+
+ def clean(self):
+ cleaned_data = super().clean()
+ timestamp = cleaned_data.get('timestamp')
+ if timestamp:
+ submission_time = timezone.now()
+ form_render_time = timezone.datetime.fromtimestamp(float(timestamp), tz=timezone.get_current_timezone())
+ if (submission_time - form_render_time).seconds < 3:
+ raise forms.ValidationError("Form submitted too quickly. Are you a bot?")
+ return cleaned_data
+
class PowerIntervalUpdateForm(forms.Form):
selectorchoices = (
diff --git a/rowers/templates/registration_form.html b/rowers/templates/registration_form.html
index 29328f81..1c3cce24 100644
--- a/rowers/templates/registration_form.html
+++ b/rowers/templates/registration_form.html
@@ -31,6 +31,8 @@
+
+
Terms of Service
diff --git a/rowers/tests/test_newusers.py b/rowers/tests/test_newusers.py
index 138643cb..a47e7b8c 100644
--- a/rowers/tests/test_newusers.py
+++ b/rowers/tests/test_newusers.py
@@ -5,7 +5,8 @@ from __future__ import unicode_literals
#from __future__ import print_function
from .statements import *
-
+from django_recaptcha.client import RecaptchaResponse
+from django_recaptcha.fields import ReCaptchaField
nu = datetime.datetime.now()
#@pytest.mark.django_db
@@ -22,7 +23,11 @@ class NewUserRegistrationTest(TestCase):
pass
@patch('rowers.dataprep.workout_summary_to_df',side_effect=mock_workout_summaries)
- def test_newuser(self,mock_workout_summaries):
+ @patch("django_recaptcha.fields.client.submit")
+ def test_newuser(self,mock_submit, mock_workout_summaries):
+ mock_response = RecaptchaResponse(is_valid=True, extra_data={'score': 0.95})
+ mock_submit.return_value = mock_response
+
form_data = {
'first_name':'Jan',
'last_name':'Roeiert',
@@ -30,16 +35,21 @@ class NewUserRegistrationTest(TestCase):
'username':'janderoeiert',
'password1':'Aapindewei2',
'password2':'Aapindewei2',
- 'url': '',
+ 'hp_field': '',
'tos':True,
'weightcategory':'hwt',
'adaptiveclass': 'None',
'sex':'male',
+ 'captcha': 'PASSED',
+ 'g-recaptcha-response': 'PASSED',
'next':'/rowers/list-workouts',
'birthdate':datetime.datetime(year=1970,month=4,day=2)
}
form = RegistrationFormSex(form_data)
+ if not form.is_valid():
+ print(form.errors)
+
self.assertTrue(form.is_valid())
response = self.c.post('/rowers/register/', form_data, follow=True)
@@ -118,7 +128,7 @@ class NewUserRegistrationTest(TestCase):
'password1':'aapindewei2',
'password2':'aapindewei2',
'tos':True,
- 'url': '',
+ 'hp_field': '',
'weightcategory':'hwt',
'adaptiveclass': 'None',
'sex':'male',
@@ -126,6 +136,7 @@ class NewUserRegistrationTest(TestCase):
'birthdate':datetime.datetime(year=1970,month=4,day=2)
}
+
form = RegistrationFormSex(form_data)
self.assertFalse(form.is_valid())
@@ -138,7 +149,7 @@ class NewUserRegistrationTest(TestCase):
'username':'janderoeiert',
'password1':'Aapindewei2',
'password2':'Aapindewei2',
- 'url': 'http://example.com',
+ 'hp_field': 'http://example.com',
'tos':True,
'weightcategory':'hwt',
'adaptiveclass': 'None',
diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz
index 322910d9..b442ff52 100644
Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ
diff --git a/rowers/views/paymentviews.py b/rowers/views/paymentviews.py
index fc04b4bc..54808712 100644
--- a/rowers/views/paymentviews.py
+++ b/rowers/views/paymentviews.py
@@ -5,6 +5,7 @@ from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.contrib.auth.backends import ModelBackend
from rowers.views.statements import *
from django.core.mail import EmailMessage
+from django_ratelimit.decorators import ratelimit
from rowers import credits
@@ -888,6 +889,7 @@ def useractivate(request, uidb64, token): # pragma: no cover
# User registration
+@ratelimit(key='ip', rate='5/h', method='POST')
def rower_register_view(request):
nextpage = request.GET.get('next', '/rowers/list-workouts/')
@@ -896,7 +898,7 @@ def rower_register_view(request):
if request.method == 'POST':
# Check if honeypot was triggered (optional logging)
- honeypot_value = request.POST.get('url', '')
+ honeypot_value = request.POST.get('hp_field', '')
if honeypot_value:
# bot user, do not register
messages.error(request, "Registration failed. Please try again.")
@@ -985,13 +987,15 @@ def rower_register_view(request):
return render(request,
"registration_form.html",
{'form': form,
- 'next': nextpage, })
+ 'next': nextpage,
+ 'timestamp': timezone.now().timestamp(),})
else:
form = RegistrationFormSex()
return render(request,
"registration_form.html",
{'form': form,
- 'next': nextpage, })
+ 'next': nextpage,
+ 'timestamp': timezone.now().timestamp(),})
# User registration