Skip to content

Commit

Permalink
Merge pull request #781 from ita-social-projects/768-be-incorporate-c…
Browse files Browse the repository at this point in the history
…aptcha

768 be incorporate captcha
  • Loading branch information
superolegatron authored Sep 20, 2024
2 parents ee23c79 + 80ef38e commit 0350888
Show file tree
Hide file tree
Showing 32 changed files with 513 additions and 138 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/tests_be.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ env:
CORS_ORIGIN_WHITELIST: ''
ALLOWED_ENV_HOST: ''
REDIS_URL: ${{ vars.REDIS_URL }}
RECAPTCHA_V2_PRIVATE_KEY: ''
RECAPTCHA_URL: ${{ vars.RECAPTCHA_URL }}


jobs:
Expand Down
2 changes: 2 additions & 0 deletions BackEnd/administration/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.urls import path

from administration.views import (
ContactsView,
ProfilesListView,
ProfileDetailView,
UsersListView,
Expand All @@ -22,4 +23,5 @@
name="automoderation_hours",
),
path("email/", ModerationEmailView.as_view(), name="moderation-email"),
path("contacts/", ContactsView.as_view(), name="contacts"),
]
12 changes: 12 additions & 0 deletions BackEnd/administration/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.http import JsonResponse
from django.views import View
from drf_spectacular.utils import (
extend_schema,
OpenApiExample,
Expand All @@ -14,6 +16,7 @@
RetrieveUpdateAPIView,
)

from forum.settings import CONTACTS_INFO
from administration.serializers import (
AdminCompanyListSerializer,
AdminCompanyDetailSerializer,
Expand Down Expand Up @@ -136,3 +139,12 @@ class ModerationEmailView(RetrieveUpdateAPIView):

def get_object(self):
return ModerationEmail.objects.first()


class ContactsView(View):
"""
View for retrieving contact information.
"""

def get(self, request):
return JsonResponse(CONTACTS_INFO)
25 changes: 24 additions & 1 deletion BackEnd/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
validate_password_include_symbols,
)
from validation.validate_profile import validate_profile
from validation.validate_recaptcha import verify_recaptcha

User = get_user_model()

Expand All @@ -42,13 +43,23 @@ class UserRegistrationSerializer(UserCreatePasswordRetypeSerializer):
password = serializers.CharField(
style={"input_type": "password"}, write_only=True
)
captcha = serializers.CharField(
write_only=True, allow_blank=True, allow_null=True
)

class Meta(UserCreatePasswordRetypeSerializer.Meta):
model = User
fields = ("email", "password", "name", "surname", "company")
fields = ("email", "password", "name", "surname", "company", "captcha")

def validate(self, value):
custom_errors = defaultdict(list)
captcha_token = value.get("captcha")

if captcha_token and not verify_recaptcha(captcha_token):
custom_errors["captcha"].append(
"Invalid reCAPTCHA. Please try again."
)

self.fields.pop("re_password", None)
re_password = value.pop("re_password")
email = value.get("email").lower()
Expand Down Expand Up @@ -79,6 +90,7 @@ def validate(self, value):
return value

def create(self, validated_data):
validated_data.pop("captcha", None)
company_data = validated_data.pop("company")
user = User.objects.create(**validated_data)
user.set_password(validated_data["password"])
Expand All @@ -105,7 +117,18 @@ class Meta(UserSerializer.Meta):


class CustomTokenCreateSerializer(TokenCreateSerializer):
captcha = serializers.CharField(
write_only=True, allow_blank=True, allow_null=True
)

def validate(self, attrs):
captcha_token = attrs.get("captcha")

if captcha_token and not verify_recaptcha(captcha_token):
raise serializers.ValidationError(
"Invalid reCAPTCHA. Please try again."
)

try:
validate_profile(attrs.get("email"))
except ValidationError as error:
Expand Down
9 changes: 9 additions & 0 deletions BackEnd/authentication/tests/test_user_autologout.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import timedelta
from unittest.mock import patch

from rest_framework import status
from rest_framework.authtoken.models import Token
Expand All @@ -12,6 +13,12 @@

class UserLogoutAPITests(APITestCase):
def setUp(self):
patcher = patch(
"authentication.serializers.verify_recaptcha", return_value=True
)
self.mock_verify_recaptcha = patcher.start()
self.addCleanup(patcher.stop)

self.user = UserFactory(
email="test@test.com", name="Test", surname="Test"
)
Expand All @@ -29,6 +36,7 @@ def test_user_autologout_after_14_days(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
).data["auth_token"]
token = Token.objects.get(key=self.test_user_token)
Expand All @@ -53,6 +61,7 @@ def test_user_autologout_after_10_days(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
).data["auth_token"]
token = Token.objects.get(key=self.test_user_token)
Expand Down
18 changes: 18 additions & 0 deletions BackEnd/authentication/tests/test_user_login.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import patch

from rest_framework import status
from rest_framework.test import APITestCase
from time import sleep
Expand All @@ -9,6 +11,12 @@

class UserLoginAPITests(APITestCase):
def setUp(self):
patcher = patch(
"authentication.serializers.verify_recaptcha", return_value=True
)
self.mock_verify_recaptcha = patcher.start()
self.addCleanup(patcher.stop)

self.user = UserFactory(email="test@test.com")

def test_login_successful(self):
Expand All @@ -20,6 +28,7 @@ def test_login_successful(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
Expand All @@ -35,6 +44,7 @@ def test_login_email_incorrect(self):
data={
"email": "tost@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
Expand All @@ -56,6 +66,7 @@ def test_login_password_incorrect(self):
data={
"email": "test@test.com",
"password": "Test5678",
"captcha": "dummy_captcha",
},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
Expand All @@ -77,13 +88,15 @@ def test_login_after_allowed_number_attempts(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)
self.client.post(
path="/api/auth/token/login/",
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)

Expand All @@ -92,6 +105,7 @@ def test_login_after_allowed_number_attempts(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)
sleep(6)
Expand All @@ -110,6 +124,7 @@ def test_login_after_allowed_delay_time(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)

Expand All @@ -118,13 +133,15 @@ def test_login_after_allowed_delay_time(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)
self.client.post(
path="/api/auth/token/login/",
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)
sleep(6)
Expand All @@ -133,6 +150,7 @@ def test_login_after_allowed_delay_time(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
)

Expand Down
9 changes: 9 additions & 0 deletions BackEnd/authentication/tests/test_user_logout.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import patch

from rest_framework import status
from rest_framework.test import APITestCase

Expand All @@ -8,6 +10,12 @@

class UserLogoutAPITests(APITestCase):
def setUp(self):
patcher = patch(
"authentication.serializers.verify_recaptcha", return_value=True
)
self.mock_verify_recaptcha = patcher.start()
self.addCleanup(patcher.stop)

self.user = UserFactory(email="test@test.com")

def test_logout_successful(self):
Expand All @@ -19,6 +27,7 @@ def test_logout_successful(self):
data={
"email": "test@test.com",
"password": "Test1234",
"captcha": "dummy_captcha",
},
).data["auth_token"]
self.client.credentials(
Expand Down
15 changes: 15 additions & 0 deletions BackEnd/authentication/tests/test_user_registration.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import patch

from rest_framework import status
from rest_framework.test import APITestCase

Expand All @@ -8,6 +10,12 @@

class UserRegistrationAPITests(APITestCase):
def setUp(self):
patcher = patch(
"authentication.serializers.verify_recaptcha", return_value=True
)
self.mock_verify_recaptcha = patcher.start()
self.addCleanup(patcher.stop)

self.user = UserFactory(email="test@test.com")

def test_register_user_yurosoba_successful(self):
Expand All @@ -19,6 +27,7 @@ def test_register_user_yurosoba_successful(self):
"re_password": "Test1234",
"name": "Jane",
"surname": "Smith",
"captcha": "dummy_captcha",
"company": {
"name": "My Company",
"is_registered": True,
Expand Down Expand Up @@ -49,6 +58,7 @@ def test_register_user_fop_successful(self):
"re_password": "Test1234",
"name": "Jane",
"surname": "Smith",
"captcha": "dummy_captcha",
"company": {
"name": "My Company",
"is_registered": True,
Expand Down Expand Up @@ -79,6 +89,7 @@ def test_register_user_email_incorrect(self):
"re_password": "Test1234",
"name": "Jane",
"surname": "Smith",
"captcha": "dummy_captcha",
"company": {
"name": "My Company",
"is_registered": True,
Expand All @@ -103,6 +114,7 @@ def test_register_user_email_exists(self):
"re_password": "Test1234",
"name": "Test",
"surname": "Test",
"captcha": "dummy_captcha",
"company": {
"name": "Test Company",
"is_registered": True,
Expand All @@ -127,6 +139,7 @@ def test_register_user_password_incorrect(self):
"re_password": "tess",
"name": "Jane",
"surname": "Smith",
"captcha": "dummy_captcha",
"company": {
"name": "My Company",
"is_registered": True,
Expand Down Expand Up @@ -157,6 +170,7 @@ def test_register_user_who_represent_empty_fields(self):
"re_password": "Test1234",
"name": "Jane",
"surname": "Smith",
"captcha": "dummy_captcha",
"company": {
"name": "My Company",
"is_registered": False,
Expand All @@ -181,6 +195,7 @@ def test_register_user_who_represent_both_chosen(self):
"re_password": "Test1234",
"name": "Jane",
"surname": "Smith",
"captcha": "dummy_captcha",
"company": {
"name": "My Company",
"is_registered": True,
Expand Down
8 changes: 8 additions & 0 deletions BackEnd/forum/celery.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import os
from celery import Celery
from celery.schedules import crontab

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "forum.settings")
app = Celery("forum")

app.config_from_object("django.conf:settings", namespace="CELERY")

app.autodiscover_tasks()

app.conf.beat_schedule = {
"every": {
"task": "images.tasks.celery_send_email_images",
"schedule": crontab(day_of_month="1"),
}
}
12 changes: 11 additions & 1 deletion BackEnd/forum/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ def show_toolbar(request):
"SHOW_TOOLBAR_CALLBACK": show_toolbar,
}


LOGGING = {
"version": 1,
"disable_existing_loggers": False,
Expand Down Expand Up @@ -267,3 +266,14 @@ def show_toolbar(request):
},
},
}

# ReCaptcha V2 Invisible
RECAPTCHA_V2_PRIVATE_KEY = config("RECAPTCHA_V2_PRIVATE_KEY")
RECAPTCHA_URL = config("RECAPTCHA_URL")

CONTACTS_INFO = {
"email": "craft.forum0@gmail.com",
"phone": "+38 050 234 23 23",
"university": "Львівська Політехніка",
"address": "вул. Степана Бандери 12, Львів",
}
Loading

0 comments on commit 0350888

Please sign in to comment.