Skip to content

Commit

Permalink
Add reCAPTCHA (#157)
Browse files Browse the repository at this point in the history
* Add reCAPTCHA
* Update translations
* Add forms for login, register, etc.
  • Loading branch information
tudoramariei authored Feb 1, 2024
1 parent fb944b4 commit 0e4b581
Show file tree
Hide file tree
Showing 19 changed files with 412 additions and 282 deletions.
Empty file.
78 changes: 78 additions & 0 deletions backend/donations/forms/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from django import forms
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _

from donations.forms.common import ReCaptchaMixin, TwoPasswordMixin


class LoginForm(forms.Form, ReCaptchaMixin):
email = forms.EmailField(required=True)
password = forms.CharField(widget=forms.PasswordInput(), required=True, max_length=150)

class Meta:
fields = ["email", "password"]

def clean_email(self):
email = self.cleaned_data["email"]
if not email:
raise forms.ValidationError(_("Email is required"))
return email

def clean_password(self):
password = self.cleaned_data["password"]
if not password:
raise forms.ValidationError(_("Password is required"))
return password


class RegisterForm(forms.ModelForm, ReCaptchaMixin, TwoPasswordMixin):
password_confirm = forms.CharField(widget=forms.PasswordInput(), required=True, max_length=150)

class Meta:
model = get_user_model()
fields = [
"first_name",
"last_name",
"email",
"password",
"password_confirm",
]

def clean_first_name(self):
first_name = self.cleaned_data["first_name"]
if not first_name:
raise forms.ValidationError(_("First name is required"))
return first_name

def clean_last_name(self):
last_name = self.cleaned_data["last_name"]
if not last_name:
raise forms.ValidationError(_("Last name is required"))
return last_name

def clean_email(self):
email = self.cleaned_data["email"]
if not email:
raise forms.ValidationError(_("Email is required"))
return email


class ForgotPasswordForm(forms.Form, ReCaptchaMixin):
email = forms.EmailField(required=True)

class Meta:
fields = ["email"]

def clean_email(self):
email = self.cleaned_data["email"]
if not email:
raise forms.ValidationError(_("Email is required"))
return email


class ResetPasswordForm(forms.Form, ReCaptchaMixin, TwoPasswordMixin):
password = forms.CharField(widget=forms.PasswordInput(), required=True, max_length=150)
password_confirm = forms.CharField(widget=forms.PasswordInput(), required=True, max_length=150)

class Meta:
fields = ["password", "password_confirm"]
41 changes: 41 additions & 0 deletions backend/donations/forms/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from secrets import compare_digest
from typing import Dict

from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django_recaptcha.fields import ReCaptchaField
from django_recaptcha.widgets import ReCaptchaV2Invisible


class ReCaptchaMixin:
fields: Dict

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.init_captcha()

def init_captcha(self):
if settings.RECAPTCHA_ENABLED:
self.fields["g-recaptcha-response"] = ReCaptchaField(widget=ReCaptchaV2Invisible)


class TwoPasswordMixin:
cleaned_data: Dict
password: str
password_confirm: str

def clean_password(self):
password = self.cleaned_data["password"]
if not password:
raise forms.ValidationError(_("Password is required"))
return password

def clean_password_confirm(self):
password = self.cleaned_data.get("password")
password_confirm = self.cleaned_data.get("password_confirm")

if not compare_digest(password, password_confirm):
raise forms.ValidationError(_("Passwords do not match"))

return password_confirm
File renamed without changes.
123 changes: 34 additions & 89 deletions backend/donations/views/account_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.db import IntegrityError
from django.http import Http404
from django.http import Http404, HttpRequest
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from redirectioneaza.common.messaging import send_email
from .base import AccountHandler
from ..forms.account import ForgotPasswordForm, LoginForm, RegisterForm, ResetPasswordForm

logger = logging.getLogger(__name__)

Expand All @@ -25,26 +26,14 @@ def get(self, request, *args, **kwargs):

def post(self, request, *args, **kwargs):
context = {}
post = self.request.POST

email = post.get("email")

if not email:
context["errors"] = _("Câmpul email nu poate fi gol.")
form = ForgotPasswordForm(request.POST)
if not form.is_valid():
context["errors"] = form.errors
return render(request, self.template_name, context)

## TODO: Fix captcha
# captcha_response = submit(post.get(CAPTCHA_POST_PARAM), CAPTCHA_PRIVATE_KEY, post.remote_addr)
#
# # if the captcha is not valid return
# if not captcha_response.is_valid:
# context["errors"] = _(
# "Se pare că a fost o problemă cu verificarea reCAPTCHA. Te rugăm să încerci din nou."
# )
# return render(request, self.template_name, context)

try:
user = self.user_model.objects.get(email=email)
user = self.user_model.objects.get(email=form.cleaned_data["email"].lower().strip())
except self.user_model.DoesNotExist:
user = None

Expand Down Expand Up @@ -95,39 +84,27 @@ def get(self, request, *args, **kwargs):

return render(request, self.template_name, context)

def post(self, request):
email = request.POST.get("email", "").lower().strip()
password = request.POST.get("parola", "")

def post(self, request: HttpRequest):
context = {}

if not email:
context["errors"] = "Câmpul email nu poate fi gol."
form = LoginForm(request.POST)
if not form.is_valid():
context["errors"] = form.errors
return render(request, self.template_name, context)

if not password:
context["errors"] = "Câmpul parola nu poate fi gol."
return render(request, self.template_name, context)

## TODO: Fix captcha
# captcha_response = submit(post.get(CAPTCHA_POST_PARAM), CAPTCHA_PRIVATE_KEY, post.remote_addr)

# # if the captcha is not valid return
# if not captcha_response.is_valid:
# context["errors"] = (
# "Se pare că a fost o problemă cu verificarea reCAPTCHA." + "Te rugăm să încerci din nou."
# )
# return render(request, self.template_name, context)
email = form.cleaned_data["email"].lower().strip()
password = form.cleaned_data["password"]

user = authenticate(email=email, password=password)
if user is not None:
login(request, user)
return redirect(reverse("contul-meu"))
else:
logger.warning("Invalid email or password: {0}".format(email))
context["email"] = email
context["errors"] = "Se pare că această combinație de email și parolă este incorectă."
return render(request, self.template_name, context)

logger.warning("Invalid email or password: {0}".format(email))
context["email"] = email
context["errors"] = "Se pare că această combinație de email și parolă este incorectă."

return render(request, self.template_name, context)


class LogoutHandler(AccountHandler):
Expand All @@ -141,21 +118,10 @@ class SetPasswordHandler(AccountHandler):

def post(self, request, *args, **kwargs):
context = {}
post = self.request.POST

password = post.get("parola")
password_confirm = post.get("confirma-parola")

if not password:
context["errors"] = "Câmpul parola nu poate fi gol."
return render(request, self.template_name, context)

if not password_confirm:
context["errors"] = "Câmpul parola confirmare nu poate fi gol."
return render(request, self.template_name, context)

if password != password_confirm:
context["errors"] = "Parolele nu se potrivesc."
form = ResetPasswordForm(request.POST)
if not form.is_valid():
context["errors"] = form.errors
return render(request, self.template_name, context)

user = request.user
Expand All @@ -164,7 +130,7 @@ def post(self, request, *args, **kwargs):
logger.warning("Invalid user")
return redirect(reverse("login"))

user.set_password(password)
user.set_password(form.cleaned_data["password"])
user.clear_token(commit=False)
user.save()

Expand All @@ -182,49 +148,27 @@ def get(self, request, *args, **kwargs):

def post(self, request, *args, **kwargs):
context = {}
post = self.request.POST

# TODO: Create a sign up form with validations
first_name = post.get("nume")
last_name = post.get("prenume")

email = post.get("email", "").lower().strip()
password = post.get("parola")

if not first_name:
context["errors"] = "Câmpul nume nu poate fi gol."
return render(request, self.template_name, context)

if not last_name:
context["errors"] = "Câmpul prenume nu poate fi gol."
return render(request, self.template_name, context)

if not email:
context["errors"] = "Câmpul email nu poate fi gol."
return render(request, self.template_name, context)

if not password:
context["errors"] = "Câmpul parola nu poate fi gol."
form = RegisterForm(request.POST)
if not form.is_valid():
context["errors"] = form.errors
return render(request, self.template_name, context)

# TODO: fix captcha
# captcha_response = submit(post.get(CAPTCHA_POST_PARAM), CAPTCHA_PRIVATE_KEY, post.remote_addr)

# # if the captcha is not valid return
# if not captcha_response.is_valid:
# context["errors"] = "Se pare că a fost o problemă cu verificarea reCAPTCHA. Te rugăm să încerci din nou."
# return render(request, self.template_name, context)
first_name = form.cleaned_data["first_name"]
last_name = form.cleaned_data["last_name"]
email = form.cleaned_data["email"].lower().strip()

user = self.user_model.objects.create_user(
email=email,
user = self.user_model(
first_name=first_name,
last_name=last_name,
password=password,
email=email,
is_verified=False,
validation_token=uuid.uuid4(),
token_timestamp=timezone.now(),
)

user.set_password(form.cleaned_data["password"])

try:
user.save()
except IntegrityError:
Expand Down Expand Up @@ -295,7 +239,8 @@ def get(self, request, *args, **kwargs):
logger.info('Could not find any user with id "%s" signup token "%s"', user_id, signup_token)
raise Http404
else:
user.clear_token()
# user.clear_token()
pass

login(request, user)

Expand Down
4 changes: 1 addition & 3 deletions backend/donations/views/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ def get(self, request, *args, **kwargs):
class AdminNgoHandler(BaseHandler):
template_name = "admin2/ngo.html"

def get(self, request, *args, **kwargs):
ngo_url = kwargs.get("ngo_url")

def get(self, request, ngo_url, *args, **kwargs):
if not request.user.is_staff:
return redirect(User.create_admin_login_url(reverse("admin-ong")))

Expand Down
26 changes: 26 additions & 0 deletions backend/donations/views/captcha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from urllib.error import HTTPError

from django.conf import settings
from django_recaptcha import client as captcha


def validate_captcha(request):
"""
Validates the captcha
"""
if request.method != "POST":
return False

captcha_response = request.POST.get("g-recaptcha-response")
if not captcha_response:
return False

try:
check_captcha = captcha.submit(captcha_response, settings.RECAPTCHA_PRIVATE_KEY, {})
except HTTPError:
return False

if not check_captcha.is_valid:
return False

return True
Loading

0 comments on commit 0e4b581

Please sign in to comment.