From 0466b9fff6b6649b70906f069c8f6b48f20b18c9 Mon Sep 17 00:00:00 2001 From: Valentin Rigal <rigal@teklia.com> Date: Wed, 12 Jun 2024 13:13:05 +0000 Subject: [PATCH] RQ task to send a verification email --- arkindex/project/config.py | 1 + .../tests/config_samples/defaults.yaml | 1 + .../project/tests/config_samples/errors.yaml | 1 + .../tests/config_samples/expected_errors.yaml | 1 + .../tests/config_samples/override.yaml | 5 +-- arkindex/project/triggers.py | 8 +++++ arkindex/users/api.py | 29 ++--------------- arkindex/users/tasks.py | 32 +++++++++++++++++++ arkindex/users/tests/test_registration.py | 9 ++++-- 9 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 arkindex/users/tasks.py diff --git a/arkindex/project/config.py b/arkindex/project/config.py index 81315b5b4c..338b50998e 100644 --- a/arkindex/project/config.py +++ b/arkindex/project/config.py @@ -150,6 +150,7 @@ def get_settings_parser(base_dir): job_timeouts_parser.add_option("notify_process_completion", type=int, default=120) # Task execution in RQ timeouts after 10 hours by default job_timeouts_parser.add_option("task", type=int, default=36000) + job_timeouts_parser.add_option("send_verification_email", type=int, default=120) csrf_parser = parser.add_subparser("csrf", default={}) csrf_parser.add_option("cookie_name", type=str, default="arkindex.csrf") diff --git a/arkindex/project/tests/config_samples/defaults.yaml b/arkindex/project/tests/config_samples/defaults.yaml index 59651795f2..96f45ad2c0 100644 --- a/arkindex/project/tests/config_samples/defaults.yaml +++ b/arkindex/project/tests/config_samples/defaults.yaml @@ -61,6 +61,7 @@ job_timeouts: notify_process_completion: 120 process_delete: 3600 reindex_corpus: 7200 + send_verification_email: 120 task: 36000 worker_results_delete: 3600 jwt_signing_key: null diff --git a/arkindex/project/tests/config_samples/errors.yaml b/arkindex/project/tests/config_samples/errors.yaml index ba921e1eb7..c4cbfe7333 100644 --- a/arkindex/project/tests/config_samples/errors.yaml +++ b/arkindex/project/tests/config_samples/errors.yaml @@ -44,6 +44,7 @@ job_timeouts: reindex_corpus: {} task: '' worker_results_delete: null + send_verification_email: lol jwt_signing_key: null local_imageserver_id: 1 metrics_port: 12 diff --git a/arkindex/project/tests/config_samples/expected_errors.yaml b/arkindex/project/tests/config_samples/expected_errors.yaml index d2a208c675..c070fed5df 100644 --- a/arkindex/project/tests/config_samples/expected_errors.yaml +++ b/arkindex/project/tests/config_samples/expected_errors.yaml @@ -29,6 +29,7 @@ job_timeouts: reindex_corpus: "int() argument must be a string, a bytes-like object or a real number, not 'dict'" task: "invalid literal for int() with base 10: ''" worker_results_delete: "int() argument must be a string, a bytes-like object or a real number, not 'NoneType'" + send_verification_email: "invalid literal for int() with base 10: 'lol'" ponos: artifact_max_size: cannot convert float NaN to integer task_expiry: "invalid literal for int() with base 10: 'zero'" diff --git a/arkindex/project/tests/config_samples/override.yaml b/arkindex/project/tests/config_samples/override.yaml index f0fb66517e..1e4700a47e 100644 --- a/arkindex/project/tests/config_samples/override.yaml +++ b/arkindex/project/tests/config_samples/override.yaml @@ -75,8 +75,9 @@ job_timeouts: notify_process_completion: 6 process_delete: 7 reindex_corpus: 8 - task: 9 - worker_results_delete: 10 + send_verification_email: 9 + task: 10 + worker_results_delete: 11 jwt_signing_key: deadbeef local_imageserver_id: 45 metrics_port: 4242 diff --git a/arkindex/project/triggers.py b/arkindex/project/triggers.py index 2bd186ef97..9db62d1b7a 100644 --- a/arkindex/project/triggers.py +++ b/arkindex/project/triggers.py @@ -16,6 +16,8 @@ from arkindex.ponos.models import State, Task from arkindex.process import tasks as process_tasks from arkindex.process.models import Process, WorkerActivityState, WorkerConfiguration, WorkerRun, WorkerVersion from arkindex.training.models import ModelVersion +from arkindex.users import tasks as user_tasks +from arkindex.users.models import User def corpus_delete(corpus: Union[Corpus, UUID, str], user_id: Optional[int] = None) -> None: @@ -259,3 +261,9 @@ def schedule_tasks(process: Process, run: int): if parent_job: kwargs["depends_on"] = Dependency(jobs=[parent_job], allow_failure=True) parent_job = ponos_tasks.run_task_rq.delay(task, **kwargs) + + +def send_verification_email(user: User): + """Send validation email to an user""" + assert user.verified_email is False, "Only non verified users can receive a verification email" + user_tasks.send_verification_email.delay(user) diff --git a/arkindex/users/api.py b/arkindex/users/api.py index 0d00c159c2..8731b4ec5d 100644 --- a/arkindex/users/api.py +++ b/arkindex/users/api.py @@ -1,13 +1,9 @@ import logging -import urllib.parse from django.conf import settings from django.contrib.auth import login, logout from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.tokens import default_token_generator -from django.core.mail import send_mail -from django.template.loader import render_to_string -from django.urls import reverse from django_rq.queues import get_queue from django_rq.settings import QUEUES from drf_spectacular.types import OpenApiTypes @@ -19,6 +15,7 @@ from rest_framework.response import Response from rq.job import JobStatus from arkindex.project.permissions import IsAuthenticatedOrReadOnly, IsVerified +from arkindex.project.triggers import send_verification_email from arkindex.users.models import User from arkindex.users.serializers import ( EmailLoginSerializer, @@ -97,29 +94,7 @@ class UserCreate(CreateAPIView): user = serializer.save() login(self.request, user) - activation_url = "{}?{}".format( - self.request.build_absolute_uri(reverse("frontend-verify-email")), - urllib.parse.urlencode({ - "email": user.email, - "token": default_token_generator.make_token(user), - }), - ) - sent = send_mail( - # Subject cannot have any newlines - subject="".join(render_to_string("registration/verification_email_subject.txt").splitlines()), - message=render_to_string( - "registration/verification_email.html", - context={ - "url": activation_url, - }, - request=self.request, - ), - from_email=None, - recipient_list=[user.email], - fail_silently=True, - ) - if sent == 0: - logger.error(f"Failed to send registration email to {user.email}") + send_verification_email(user) return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED) diff --git a/arkindex/users/tasks.py b/arkindex/users/tasks.py new file mode 100644 index 0000000000..4ca7e257d3 --- /dev/null +++ b/arkindex/users/tasks.py @@ -0,0 +1,32 @@ +import urllib + +from django.conf import settings +from django.contrib.auth.tokens import default_token_generator +from django.core.mail import send_mail +from django.template.loader import render_to_string +from django.urls import reverse +from django_rq import job + +from arkindex.users.models import User + + +@job("default", timeout=settings.RQ_TIMEOUTS["send_verification_email"]) +def send_verification_email(user: User): + activation_url = "{}?{}".format( + urllib.parse.urljoin(settings.PUBLIC_HOSTNAME, reverse("frontend-verify-email")), + urllib.parse.urlencode({ + "email": user.email, + "token": default_token_generator.make_token(user), + }), + ) + send_mail( + # Subject cannot have any newlines + subject="".join(render_to_string("registration/verification_email_subject.txt").splitlines()), + message=render_to_string( + "registration/verification_email.html", + context={"url": activation_url}, + ), + from_email=None, + recipient_list=[user.email], + fail_silently=False, + ) diff --git a/arkindex/users/tests/test_registration.py b/arkindex/users/tests/test_registration.py index c6278a177b..07ec0201b8 100644 --- a/arkindex/users/tests/test_registration.py +++ b/arkindex/users/tests/test_registration.py @@ -11,6 +11,7 @@ from arkindex.documents.models import Corpus from arkindex.project.default_corpus import DEFAULT_CORPUS_TYPES from arkindex.project.tests import FixtureAPITestCase from arkindex.users.models import Group, Right, Role, Scope, User +from arkindex.users.tasks import send_verification_email class TestRegistration(FixtureAPITestCase): @@ -319,7 +320,9 @@ class TestRegistration(FixtureAPITestCase): "__all__": ["There is no user with this email or the token is invalid."] }) - def test_create_no_password(self): + @patch("arkindex.users.tasks.send_verification_email.delay") + def test_create_no_password(self, async_verification_email): + async_verification_email.side_effect = send_verification_email response = self.client.post( reverse("api:user-new"), data={"display_name": "New user", "email": "newuser@example.com"}, @@ -334,7 +337,9 @@ class TestRegistration(FixtureAPITestCase): self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to, ["newuser@example.com"]) - def test_create(self): + @patch("arkindex.users.tasks.send_verification_email.delay") + def test_create(self, async_verification_email): + async_verification_email.side_effect = send_verification_email response = self.client.post( reverse("api:user-new"), data={"display_name": "New user", "email": "newuser@example.com", "password": "myVerySecretPassword"}, -- GitLab