Skip to content

Commit fdaede4

Browse files
authored
Merge pull request #40 from HackAssistant/recaptcha
Recaptcha
2 parents dd10169 + 3b0ac19 commit fdaede4

File tree

7 files changed

+69
-17
lines changed

7 files changed

+69
-17
lines changed

app/settings.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
'django.contrib.sessions',
4949
'django.contrib.messages',
5050
'django.contrib.staticfiles',
51+
'captcha',
5152
'django_tables2',
5253
'django_filters',
5354
'django_jwt',
@@ -219,9 +220,16 @@
219220
message_constants.ERROR: 'danger',
220221
}
221222

222-
# Google recaptcha
223-
GOOGLE_RECAPTCHA_SECRET_KEY = os.environ.get('GOOGLE_RECAPTCHA_SECRET_KEY', '')
224-
GOOGLE_RECAPTCHA_SITE_KEY = os.environ.get('GOOGLE_RECAPTCHA_SITE_KEY', '')
223+
# Google Recaptcha configuration
224+
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', '')
225+
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', '')
226+
RECAPTCHA_WIDGET = os.environ.get('RECAPTCHA_WIDGET', 'ReCaptchaV2Checkbox')
227+
RECAPTCHA_REGISTER = True
228+
RECAPTCHA_LOGIN = False
229+
try:
230+
RECAPTCHA_REQUIRED_SCORE = float(os.environ.get('RECAPTCHA_REQUIRED_SCORE', "0.85"))
231+
except ValueError:
232+
RECAPTCHA_REQUIRED_SCORE = 0.85
225233

226234
# Login tries
227235
LOGIN_TRIES = 1000 if DEBUG else 4

app/static/css/main.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,7 @@ footer {
139139
background-color: #f7f8f9;
140140
color: black;
141141
}
142+
143+
.g-recaptcha {
144+
display: inline-block;
145+
}

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ django-filter==22.1
1818
django-ipware==4.0.2
1919
django-jwt-oidc==0.3.9
2020
django-libsass==0.9
21+
django-recaptcha==3.0.0
2122
django-tables2==2.4.1
2223
flake8==4.0.1
2324
idna==3.3

review/views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,9 @@ def get_context_data(self, **kwargs):
121121
context = super().get_context_data(**kwargs)
122122
application = self.get_application()
123123
if application is not None:
124-
details = {_('Full Name'): application.user.get_full_name(), _('Status'): application.get_status_display(),
125-
_('Promotion'): application.promotional_code.name}
124+
details = {_('Full Name'): application.user.get_full_name(), _('Status'): application.get_status_display()}
125+
if application.promotional_code:
126+
details[_('Promotion')] = application.promotional_code.name
126127
ApplicationForm = self.get_form(application.type)
127128
for name, value in application.form_data.items():
128129
if isinstance(value, FileField):

user/forms.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import re
22

3+
from captcha.fields import ReCaptchaField
4+
from captcha import widgets as captcha_widgets
35
from django import forms
46
from django.conf import settings
57
from django.contrib.auth import password_validation
@@ -8,10 +10,34 @@
810
from django.utils.safestring import mark_safe
911

1012
from app.mixins import BootstrapFormMixin
13+
from app.utils import get_theme
1114
from user.models import User
1215
from django.utils.translation import gettext_lazy as _
1316

1417

18+
class RecaptchaForm(forms.Form):
19+
@classmethod
20+
def active(cls):
21+
return getattr(settings, 'RECAPTCHA_PUBLIC_KEY', False) and getattr(settings, 'RECAPTCHA_PRIVATE_KEY', False)
22+
23+
def __init__(self, *args, **kwargs):
24+
request = kwargs.pop('request', None)
25+
widget_setting = getattr(settings, 'RECAPTCHA_WIDGET', 'ReCaptchaV2Checkbox')
26+
widget_class = getattr(captcha_widgets, widget_setting, captcha_widgets.ReCaptchaV2Checkbox)
27+
if widget_class == captcha_widgets.ReCaptchaBase or not issubclass(widget_class, captcha_widgets.ReCaptchaBase):
28+
widget_class = captcha_widgets.ReCaptchaV2Checkbox
29+
theme = get_theme(request) if request is not None else 'light'
30+
self.base_fields['captcha'] = ReCaptchaField(
31+
widget=widget_class(attrs={'data-theme': theme}),
32+
error_messages={'required': _('You must pass the reCAPTCHA challenge!')})
33+
super().__init__(*args, **kwargs)
34+
35+
captcha = ReCaptchaField(
36+
widget=captcha_widgets.ReCaptchaV2Checkbox(),
37+
error_messages={'required': _('You must pass the reCAPTCHA challenge!')}
38+
)
39+
40+
1541
class LoginForm(BootstrapFormMixin, forms.Form):
1642
bootstrap_field_info = {'': {'fields': [{'name': 'email', 'space': 12}, {'name': 'password', 'space': 12}]}}
1743

user/templates/auth.html

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
{% csrf_token %}
1414
<div class="content p-4">
1515
{% include 'components/bootstrap5_form.html' %}
16-
{% if auth == 'register' %}
17-
{% if captcha_site_key %}
18-
<script src='https://www.google.com/recaptcha/api.js'></script>
19-
<div class="g-recaptcha" data-sitekey="{{ captcha_site_key }}"></div>
20-
{% endif %}
21-
{% else %}
16+
{% if auth != 'register' %}
2217
<p><a class="text-{% if theme == 'dark' %}white{% else %}black{% endif %}" style="text-decoration: none" href="{% url 'forgot_password' %}">{% translate 'Forgot your password?' %}</a></p>
2318
{% endif %}
19+
{% for field in recaptcha_form %}
20+
<div class="text-center mb-1">
21+
{{ field }}
22+
</div>
23+
{% endfor %}
2424
<div class="row justify-content-around">
2525
<div class="col-12 col-lg-6 d-grid d-md-block">
2626
<button type="submit" class="btn btn-primary col-12">{{ auth|title }}</button>
@@ -35,7 +35,6 @@
3535
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
3636
</div>
3737
</div>
38-
3938
{% endif %}
4039
</div>
4140
</div>

user/views.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from axes.helpers import get_client_ip_address, get_cool_off
33
from axes.models import AccessAttempt
44
from axes.utils import reset_request
5+
from django.conf import settings
56
from django.contrib import auth, messages
67
from django.shortcuts import redirect
78
from django.urls import reverse, resolve
@@ -13,7 +14,7 @@
1314
from app.mixins import TabsViewMixin
1415
from user import emails
1516
from user.forms import LoginForm, UserProfileForm, ForgotPasswordForm, SetPasswordForm, \
16-
RegistrationForm
17+
RegistrationForm, RecaptchaForm
1718
from user.mixins import LoginRequiredMixin, EmailNotVerifiedMixin
1819
from user.models import User
1920
from user.tokens import AccountActivationTokenGenerator
@@ -58,8 +59,18 @@ def get_form(self):
5859
def get_context_data(self, **kwargs):
5960
context = super().get_context_data(**kwargs)
6061
context.update({'form': self.get_form(), 'auth': self.names.get(self.get_url_name, 'register')})
62+
if getattr(settings, 'RECAPTCHA_%s' % self.get_url_name.upper(), False) and RecaptchaForm.active():
63+
context.update({'recaptcha_form': RecaptchaForm(request=self.request)})
6164
return context
6265

66+
def forms_are_valid(self, form, context):
67+
if getattr(settings, 'RECAPTCHA_%s' % self.get_url_name.upper(), False) and RecaptchaForm.active():
68+
recaptcha_form = RecaptchaForm(self.request.POST, request=self.request)
69+
if not recaptcha_form.is_valid():
70+
context.update({'recaptcha_form': recaptcha_form})
71+
return False
72+
return form.is_valid()
73+
6374

6475
class Login(AuthTemplateViews):
6576
def add_axes_context(self, context):
@@ -79,7 +90,7 @@ def get_context_data(self, **kwargs):
7990
def post(self, request, **kwargs):
8091
form = LoginForm(request.POST)
8192
context = self.get_context_data(**kwargs)
82-
if form.is_valid():
93+
if self.forms_are_valid(form, context):
8394
email = form.cleaned_data['email']
8495
password = form.cleaned_data['password']
8596
user = auth.authenticate(email=email, password=password, request=request)
@@ -100,19 +111,21 @@ def post(self, request, **kwargs):
100111
class Register(Login):
101112
def get_context_data(self, **kwargs):
102113
context = super().get_context_data(**kwargs)
114+
context.update({'form': RegistrationForm(), 'auth': 'register'})
103115
context.pop("blocked_message", None)
104116
return context
105117

106118
def post(self, request, **kwargs):
119+
context = self.get_context_data(**kwargs)
107120
form = RegistrationForm(request.POST)
108-
if form.is_valid() and request.recaptcha_is_valid:
121+
recaptcha = RecaptchaForm(request.POST, request=request)
122+
if self.forms_are_valid(form, context):
109123
user = form.save()
110124
auth.login(request, user)
111125
emails.send_verification_email(request=request, user=user)
112126
messages.success(request, _('Successfully registered!'))
113127
return self.redirect_successful()
114-
context = self.get_context_data(**kwargs)
115-
context.update({'form': form})
128+
context.update({'form': form, 'recaptcha_form': recaptcha})
116129
return self.render_to_response(context)
117130

118131

0 commit comments

Comments
 (0)