Skip to content

Add CAPTCHA challenge to user login, signup, and admin login #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 23, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions fedcode/forms.py
Original file line number Diff line number Diff line change
@@ -8,8 +8,12 @@
#

from django import forms
from django.contrib.admin.forms import AdminAuthenticationForm
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django_recaptcha.fields import ReCaptchaField
from django_recaptcha.widgets import ReCaptchaV2Checkbox

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


class PersonSignUpForm(UserCreationForm):
captcha = ReCaptchaField(
error_messages={"required": ("Captcha is required")}, widget=ReCaptchaV2Checkbox
)
email = forms.EmailField(max_length=254)

class Meta:
@@ -141,3 +148,21 @@ class SearchRepositoryForm(forms.Form):
},
),
)


class UserLoginForm(AuthenticationForm):
captcha = ReCaptchaField(
error_messages={
"required": ("Captcha is required"),
},
widget=ReCaptchaV2Checkbox,
)


class AdminLoginForm(AdminAuthenticationForm):
captcha = ReCaptchaField(
error_messages={
"required": ("Captcha is required"),
},
widget=ReCaptchaV2Checkbox(),
)
66 changes: 66 additions & 0 deletions fedcode/templates/admin_login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}

{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/login.css" %}">
{{ form.media }}
{% endblock %}

{% block bodyclass %}{{ block.super }} login{% endblock %}

{% block usertools %}{% endblock %}

{% block nav-global %}{% endblock %}

{% block nav-sidebar %}{% endblock %}

{% block content_title %}{% endblock %}

{% block nav-breadcrumbs %}{% endblock %}

{% block content %}

{% if form.errors %}
{% for error in form.errors.values %}
<p>{{ error }}</p>
{% endfor %}
{% endif %}


<div id="content-main">

{% if user.is_authenticated %}
<p class="errornote">
{% blocktranslate trimmed %}
You are authenticated as {{ username }}, but are not authorized to
access this page. Would you like to login to a different account?
{% endblocktranslate %}
</p>
{% endif %}

<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
<div class="form-row">
{{ form.username.errors }}
{{ form.username.label_tag }} {{ form.username }}
</div>
<div class="form-row">
{{ form.password.errors }}
{{ form.password.label_tag }} {{ form.password }}
<input type="hidden" name="next" value="{{ next }}">
</div>
{% url 'admin_password_reset' as password_reset_url %}
{% if password_reset_url %}
<div class="password-reset-link">
<a href="{{ password_reset_url }}">{% translate 'Forgotten your password or username?' %}</a>
</div>
{% endif %}
<div class="field">
<div class="control">
{{ form.captcha }}
</div>
</div>
<div class="submit-row">
<input type="submit" value="{% translate 'Log in' %}">
</div>
</form>
</div>
{% endblock %}
11 changes: 9 additions & 2 deletions fedcode/templates/login.html
Original file line number Diff line number Diff line change
@@ -10,7 +10,10 @@
{% if form.errors %}
<div class="notification is-danger">
<button class="delete"></button>
<strong>Error!</strong> Your username and password didn't match. Please try again.
<strong>Error!</strong>
{% for error in form.errors.values %}
{{ error }}
{% endfor %}
</div>
{% endif %}

@@ -32,7 +35,11 @@ <h2 class="title">User Login</h2>
autocomplete="current-password" required id="id_password">
</div>
</div>

<div class="field">
<div class="control">
{{ form.captcha }}
</div>
</div>
<div class="field">
<div class="control">
<input class="button is-fullwidth is-info" type="submit" value="Log in">
9 changes: 8 additions & 1 deletion fedcode/templates/user_sign_up.html
Original file line number Diff line number Diff line change
@@ -17,7 +17,9 @@
<p>Error</p>
</div>
<div class="message-body">
{{ form.errors }}
{% for error in form.errors.values %}
{{ error }}
{% endfor %}
</div>
</article>
</div>
@@ -61,6 +63,11 @@ <h2 class="title">User Signup</h2>
autocomplete="new-password" required id="id_password2">
</div>
</div>
<div class="field">
<div class="control">
{{ form.captcha }}
</div>
</div>
<input class="button is-fullwidth is-info" type="submit" value="Sign Up">
</form>
</div>
9 changes: 8 additions & 1 deletion fedcode/views.py
Original file line number Diff line number Diff line change
@@ -10,7 +10,6 @@
import json
import logging
import os.path
from urllib.parse import urlparse

import requests
from django.contrib import messages
@@ -47,6 +46,7 @@
from fedcode.activitypub import AP_CONTEXT
from fedcode.activitypub import create_activity_obj
from fedcode.activitypub import has_valid_header
from fedcode.forms import AdminLoginForm
from fedcode.forms import CreateGitRepoForm
from fedcode.forms import CreateNoteForm
from fedcode.forms import CreateReviewForm
@@ -57,6 +57,7 @@
from fedcode.forms import SearchRepositoryForm
from fedcode.forms import SearchReviewForm
from fedcode.forms import SubscribePackageForm
from fedcode.forms import UserLoginForm
from fedcode.models import Follow
from fedcode.models import Note
from fedcode.models import Package
@@ -277,6 +278,7 @@ def post(self, request, repository_id):
class UserLogin(LoginView):
template_name = "login.html"
next_page = "/"
form_class = UserLoginForm

def dispatch(self, request, *args, **kwargs):
# If user is already logged in, redirect to the next_page.
@@ -887,3 +889,8 @@ def revoke_token(request):
},
)
return JsonResponse(json.loads(r.content), status=r.status_code, content_type=AP_CONTENT_TYPE)


class AdminLoginView(LoginView):
template_name = "admin_login.html"
authentication_form = AdminLoginForm
5 changes: 5 additions & 0 deletions federatedcode/settings.py
Original file line number Diff line number Diff line change
@@ -61,6 +61,10 @@
FEDERATEDCODE_CLIENT_ID = env.str("FEDERATEDCODE_CLIENT_ID")
FEDERATEDCODE_CLIENT_SECRET = env.str("FEDERATEDCODE_CLIENT_SECRET")

RECAPTCHA_PUBLIC_KEY = env.str("RECAPTCHA_PUBLIC_KEY", "")
RECAPTCHA_PRIVATE_KEY = env.str("RECAPTCHA_PRIVATE_KEY", "")
SILENCED_SYSTEM_CHECKS = ["captcha.recaptcha_test_key_error"]
RECAPTCHA_DOMAIN = env.str("RECAPTCHA_DOMAIN", "www.recaptcha.net")

# Application definition

@@ -78,6 +82,7 @@
"django.contrib.humanize",
# Third-party apps
"oauth2_provider",
"django_recaptcha",
]

MIDDLEWARE = [
2 changes: 2 additions & 0 deletions federatedcode/urls.py
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
from django.urls import path

from fedcode import views
from fedcode.views import AdminLoginView
from fedcode.views import CreateReview
from fedcode.views import CreateSync
from fedcode.views import CreatGitView
@@ -45,6 +46,7 @@
from fedcode.views import redirect_vulnerability

urlpatterns = [
path("admin/login/", AdminLoginView.as_view(), name="admin-login"),
path("admin/", admin.site.urls),
path(".well-known/webfinger", WebfingerView.as_view(), name="web-finger"),
path("", HomeView.as_view(), name="home-page"),
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ Django==5.1.2
django-environ==0.11.2
django-ninja==1.3.0
django-oauth-toolkit==3.0.1
django-recaptcha==4.0.0
djangorestframework==3.15.2
doc8==1.1.2
docutils==0.21.2
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -76,6 +76,9 @@ install_requires =
python-dotenv==1.0.1
click==8.1.7

# Captcha
django-recaptcha==4.0.0

[options.extras_require]
dev =
# Validation