Skip to content

Commit a1df7e0

Browse files
authored
Merge pull request #75 from aboutcode-org/64-captcha
Add CAPTCHA challenge to user login, signup, and admin login
2 parents 6654153 + b79876a commit a1df7e0

File tree

9 files changed

+127
-4
lines changed

9 files changed

+127
-4
lines changed

Diff for: fedcode/forms.py

+25
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
#
99

1010
from django import forms
11+
from django.contrib.admin.forms import AdminAuthenticationForm
12+
from django.contrib.auth.forms import AuthenticationForm
1113
from django.contrib.auth.forms import UserCreationForm
1214
from django.contrib.auth.models import User
15+
from django_recaptcha.fields import ReCaptchaField
16+
from django_recaptcha.widgets import ReCaptchaV2Checkbox
1317

1418
from .models import Note
1519
from .models import Repository
@@ -58,6 +62,9 @@ def __init__(self, *args, **kwargs):
5862

5963

6064
class PersonSignUpForm(UserCreationForm):
65+
captcha = ReCaptchaField(
66+
error_messages={"required": ("Captcha is required")}, widget=ReCaptchaV2Checkbox
67+
)
6168
email = forms.EmailField(max_length=254)
6269

6370
class Meta:
@@ -141,3 +148,21 @@ class SearchRepositoryForm(forms.Form):
141148
},
142149
),
143150
)
151+
152+
153+
class UserLoginForm(AuthenticationForm):
154+
captcha = ReCaptchaField(
155+
error_messages={
156+
"required": ("Captcha is required"),
157+
},
158+
widget=ReCaptchaV2Checkbox,
159+
)
160+
161+
162+
class AdminLoginForm(AdminAuthenticationForm):
163+
captcha = ReCaptchaField(
164+
error_messages={
165+
"required": ("Captcha is required"),
166+
},
167+
widget=ReCaptchaV2Checkbox(),
168+
)

Diff for: fedcode/templates/admin_login.html

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{% extends "admin/base_site.html" %}
2+
{% load i18n static %}
3+
4+
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/login.css" %}">
5+
{{ form.media }}
6+
{% endblock %}
7+
8+
{% block bodyclass %}{{ block.super }} login{% endblock %}
9+
10+
{% block usertools %}{% endblock %}
11+
12+
{% block nav-global %}{% endblock %}
13+
14+
{% block nav-sidebar %}{% endblock %}
15+
16+
{% block content_title %}{% endblock %}
17+
18+
{% block nav-breadcrumbs %}{% endblock %}
19+
20+
{% block content %}
21+
22+
{% if form.errors %}
23+
{% for error in form.errors.values %}
24+
<p>{{ error }}</p>
25+
{% endfor %}
26+
{% endif %}
27+
28+
29+
<div id="content-main">
30+
31+
{% if user.is_authenticated %}
32+
<p class="errornote">
33+
{% blocktranslate trimmed %}
34+
You are authenticated as {{ username }}, but are not authorized to
35+
access this page. Would you like to login to a different account?
36+
{% endblocktranslate %}
37+
</p>
38+
{% endif %}
39+
40+
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
41+
<div class="form-row">
42+
{{ form.username.errors }}
43+
{{ form.username.label_tag }} {{ form.username }}
44+
</div>
45+
<div class="form-row">
46+
{{ form.password.errors }}
47+
{{ form.password.label_tag }} {{ form.password }}
48+
<input type="hidden" name="next" value="{{ next }}">
49+
</div>
50+
{% url 'admin_password_reset' as password_reset_url %}
51+
{% if password_reset_url %}
52+
<div class="password-reset-link">
53+
<a href="{{ password_reset_url }}">{% translate 'Forgotten your password or username?' %}</a>
54+
</div>
55+
{% endif %}
56+
<div class="field">
57+
<div class="control">
58+
{{ form.captcha }}
59+
</div>
60+
</div>
61+
<div class="submit-row">
62+
<input type="submit" value="{% translate 'Log in' %}">
63+
</div>
64+
</form>
65+
</div>
66+
{% endblock %}

Diff for: fedcode/templates/login.html

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
{% if form.errors %}
1111
<div class="notification is-danger">
1212
<button class="delete"></button>
13-
<strong>Error!</strong> Your username and password didn't match. Please try again.
13+
<strong>Error!</strong>
14+
{% for error in form.errors.values %}
15+
{{ error }}
16+
{% endfor %}
1417
</div>
1518
{% endif %}
1619

@@ -32,7 +35,11 @@ <h2 class="title">User Login</h2>
3235
autocomplete="current-password" required id="id_password">
3336
</div>
3437
</div>
35-
38+
<div class="field">
39+
<div class="control">
40+
{{ form.captcha }}
41+
</div>
42+
</div>
3643
<div class="field">
3744
<div class="control">
3845
<input class="button is-fullwidth is-info" type="submit" value="Log in">

Diff for: fedcode/templates/user_sign_up.html

+8-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
<p>Error</p>
1818
</div>
1919
<div class="message-body">
20-
{{ form.errors }}
20+
{% for error in form.errors.values %}
21+
{{ error }}
22+
{% endfor %}
2123
</div>
2224
</article>
2325
</div>
@@ -61,6 +63,11 @@ <h2 class="title">User Signup</h2>
6163
autocomplete="new-password" required id="id_password2">
6264
</div>
6365
</div>
66+
<div class="field">
67+
<div class="control">
68+
{{ form.captcha }}
69+
</div>
70+
</div>
6471
<input class="button is-fullwidth is-info" type="submit" value="Sign Up">
6572
</form>
6673
</div>

Diff for: fedcode/views.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import json
1111
import logging
1212
import os.path
13-
from urllib.parse import urlparse
1413

1514
import requests
1615
from django.contrib import messages
@@ -47,6 +46,7 @@
4746
from fedcode.activitypub import AP_CONTEXT
4847
from fedcode.activitypub import create_activity_obj
4948
from fedcode.activitypub import has_valid_header
49+
from fedcode.forms import AdminLoginForm
5050
from fedcode.forms import CreateGitRepoForm
5151
from fedcode.forms import CreateNoteForm
5252
from fedcode.forms import CreateReviewForm
@@ -57,6 +57,7 @@
5757
from fedcode.forms import SearchRepositoryForm
5858
from fedcode.forms import SearchReviewForm
5959
from fedcode.forms import SubscribePackageForm
60+
from fedcode.forms import UserLoginForm
6061
from fedcode.models import Follow
6162
from fedcode.models import Note
6263
from fedcode.models import Package
@@ -277,6 +278,7 @@ def post(self, request, repository_id):
277278
class UserLogin(LoginView):
278279
template_name = "login.html"
279280
next_page = "/"
281+
form_class = UserLoginForm
280282

281283
def dispatch(self, request, *args, **kwargs):
282284
# If user is already logged in, redirect to the next_page.
@@ -887,3 +889,8 @@ def revoke_token(request):
887889
},
888890
)
889891
return JsonResponse(json.loads(r.content), status=r.status_code, content_type=AP_CONTENT_TYPE)
892+
893+
894+
class AdminLoginView(LoginView):
895+
template_name = "admin_login.html"
896+
authentication_form = AdminLoginForm

Diff for: federatedcode/settings.py

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
FEDERATEDCODE_CLIENT_ID = env.str("FEDERATEDCODE_CLIENT_ID")
6262
FEDERATEDCODE_CLIENT_SECRET = env.str("FEDERATEDCODE_CLIENT_SECRET")
6363

64+
RECAPTCHA_PUBLIC_KEY = env.str("RECAPTCHA_PUBLIC_KEY", "")
65+
RECAPTCHA_PRIVATE_KEY = env.str("RECAPTCHA_PRIVATE_KEY", "")
66+
SILENCED_SYSTEM_CHECKS = ["captcha.recaptcha_test_key_error"]
67+
RECAPTCHA_DOMAIN = env.str("RECAPTCHA_DOMAIN", "www.recaptcha.net")
6468

6569
# Application definition
6670

@@ -78,6 +82,7 @@
7882
"django.contrib.humanize",
7983
# Third-party apps
8084
"oauth2_provider",
85+
"django_recaptcha",
8186
]
8287

8388
MIDDLEWARE = [

Diff for: federatedcode/urls.py

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from django.urls import path
1414

1515
from fedcode import views
16+
from fedcode.views import AdminLoginView
1617
from fedcode.views import CreateReview
1718
from fedcode.views import CreateSync
1819
from fedcode.views import CreatGitView
@@ -45,6 +46,7 @@
4546
from fedcode.views import redirect_vulnerability
4647

4748
urlpatterns = [
49+
path("admin/login/", AdminLoginView.as_view(), name="admin-login"),
4850
path("admin/", admin.site.urls),
4951
path(".well-known/webfinger", WebfingerView.as_view(), name="web-finger"),
5052
path("", HomeView.as_view(), name="home-page"),

Diff for: requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Django==5.1.2
1616
django-environ==0.11.2
1717
django-ninja==1.3.0
1818
django-oauth-toolkit==3.0.1
19+
django-recaptcha==4.0.0
1920
djangorestframework==3.15.2
2021
doc8==1.1.2
2122
docutils==0.21.2

Diff for: setup.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ install_requires =
7676
python-dotenv==1.0.1
7777
click==8.1.7
7878

79+
# Captcha
80+
django-recaptcha==4.0.0
81+
7982
[options.extras_require]
8083
dev =
8184
# Validation

0 commit comments

Comments
 (0)