From 654db6b2874da435f5222d4511f44ed357835672 Mon Sep 17 00:00:00 2001 From: ddahan Date: Wed, 27 Mar 2024 09:14:27 +0000 Subject: [PATCH 1/4] Re-Send verification mail with normal send mail --- api/urls.py | 5 +++ api/views/__init__.py | 8 +++- api/views/user.py | 30 +++++++++---- .../SendNewSignupVerificationEmail.vue | 42 +++++++++++++++++++ frontend/src/views/SignupPage.vue | 8 ++-- 5 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 frontend/src/components/SendNewSignupVerificationEmail.vue diff --git a/api/urls.py b/api/urls.py index 1ba85081..481b7d1c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -26,6 +26,11 @@ path("signup/", views.SignupView.as_view(), name="signup"), path("generate-username/", views.GenerateUsernameView.as_view(), name="generate_username"), path("verify-email/", views.VerifyEmailView.as_view(), name="verify_email"), + path( + "send-new-signup-verification-email/", + views.SendNewSignupVerificationEmailView.as_view(), + name="send_new_signup_verification_email", + ), } urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/api/views/__init__.py b/api/views/__init__.py index a2bbeffe..bf47d0f1 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -1,7 +1,13 @@ from .blog import BlogPostsView, BlogPostView # noqa: F401 from .newsletter import SubscribeNewsletter # noqa: F401 from .report_issue import ReportIssue # noqa: F401 -from .user import LoggedUserView, SignupView, GenerateUsernameView, VerifyEmailView # noqa: F401 +from .user import ( # noqa: F401 + LoggedUserView, + SignupView, + GenerateUsernameView, + VerifyEmailView, + SendNewSignupVerificationEmailView, +) from .webinar import WebinarView # noqa from .search import SearchView # noqa: F401 from .plant import PlantRetrieveView, PlantPartListView # noqa: F401 diff --git a/api/views/user.py b/api/views/user.py index 98f61cfb..3af52545 100644 --- a/api/views/user.py +++ b/api/views/user.py @@ -3,6 +3,7 @@ from django.contrib.auth import login from django.middleware.csrf import get_token from django.core.mail import send_mail +from django.shortcuts import get_object_or_404 from rest_framework.views import APIView from rest_framework.generics import RetrieveAPIView from rest_framework.response import Response @@ -31,20 +32,31 @@ def get_object(self): return self.request.user +def _send_verification_mail(request, user): + new_token = MagicLinkToken.objects.create(user=user, usage=MagicLinkUsage.VERIFY_EMAIL_ADDRESS) + verification_url = urljoin(get_base_url(request), new_token.as_url(key=new_token.key)) + send_mail( + subject="Vérifiez votre adresse e-mail", + message=f"Cliquez sur le lien suivant pour vérifier votre adresse e-mail : {verification_url}", + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=[user.email], + ) + + class SignupView(APIView): def post(self, request, *args, **kwargs): serializer = UserInputSerializer(data=request.data) if serializer.is_valid(raise_exception=True): new_user = serializer.save() - new_token = MagicLinkToken.objects.create(user=new_user, usage=MagicLinkUsage.VERIFY_EMAIL_ADDRESS) - verification_url = urljoin(get_base_url(request), new_token.as_url(key=new_token.key)) - send_mail( - subject="Vérifiez votre adresse e-mail", - message=f"Cliquez sur le lien suivant pour vérifier votre adresse e-mail : {verification_url}", - from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=[new_user.email], - ) - return Response({}, status=status.HTTP_201_CREATED) + _send_verification_mail(request, new_user) + return Response({"user_id": new_user.id}, status=status.HTTP_201_CREATED) + + +class SendNewSignupVerificationEmailView(APIView): + def get(self, request, user_id, *args, **kwargs): + user = get_object_or_404(User, id=user_id) + _send_verification_mail(request, user) + return Response(None, status=status.HTTP_204_NO_CONTENT) class GenerateUsernameView(APIView): diff --git a/frontend/src/components/SendNewSignupVerificationEmail.vue b/frontend/src/components/SendNewSignupVerificationEmail.vue new file mode 100644 index 00000000..99816bd6 --- /dev/null +++ b/frontend/src/components/SendNewSignupVerificationEmail.vue @@ -0,0 +1,42 @@ + + + diff --git a/frontend/src/views/SignupPage.vue b/frontend/src/views/SignupPage.vue index 59e34620..b9d6584a 100644 --- a/frontend/src/views/SignupPage.vue +++ b/frontend/src/views/SignupPage.vue @@ -91,10 +91,7 @@ Veuillez cliquez dans le lien à l'intérieur pour vérifier votre adresse e-email et pouvoir utiliser votre compte.

-

- Si vous n'avez pas reçu l'email au bout de quelques minutes, veuillez vérifier l'adresse e-mail entrée, ainsi - que vos courriers indésirables. -

+ @@ -103,6 +100,7 @@ import { computed, ref } from "vue" import { useVuelidate } from "@vuelidate/core" import SingleItemWrapper from "@/components/SingleItemWrapper" +import SendNewSignupVerificationEmail from "@/components/SendNewSignupVerificationEmail" import FormWrapper from "@/components/FormWrapper" import { errorRequiredField, errorRequiredEmail, firstErrorMsg } from "@/utils/forms" import { useFetch } from "@vueuse/core" @@ -136,7 +134,7 @@ const $externalResults = ref({}) const v$ = useVuelidate(rules, state, { $externalResults }) // Main request definition -const { response, execute, isFetching } = useFetch( +const { data, response, execute, isFetching } = useFetch( "/api/v1/signup/", { headers: headers(), From bcdaa169b2a2aa549b2a257c27df9ad40f4122e9 Mon Sep 17 00:00:00 2001 From: ddahan Date: Wed, 27 Mar 2024 15:16:46 +0000 Subject: [PATCH 2/4] Add modal, extra data to error handling, and a dirty hack --- api/exception_handling.py | 5 +++ api/views/authentication.py | 3 +- .../SendNewSignupVerificationEmail.vue | 37 +++++++++++++------ frontend/src/icons.js | 9 +++++ frontend/src/utils/error-handling.js | 4 +- frontend/src/views/LoginPage.vue | 12 ++++++ 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/api/exception_handling.py b/api/exception_handling.py index bead912f..7949449e 100644 --- a/api/exception_handling.py +++ b/api/exception_handling.py @@ -38,6 +38,7 @@ def __init__( non_field_errors: list[str] | None = None, field_errors: dict[str, list[str]] | None = None, log_level: int | None = logging.INFO, + extra: dict = None, **kwargs ): super().__init__(**kwargs) @@ -60,6 +61,10 @@ def __init__( if not any([self.global_error, self.non_field_errors, self.field_errors]): raise ValueError("An exception must contain at least one error type.") + # Inject addtional data of your choice into the response + if extra: + self.extra = extra + # Create an optional log using provided log level if log_level: logger.log(level=log_level, msg=self.__class__.__name__, extra=self.__dict__) diff --git a/api/views/authentication.py b/api/views/authentication.py index 6fb7dfa1..85d46d5d 100644 --- a/api/views/authentication.py +++ b/api/views/authentication.py @@ -15,7 +15,8 @@ def post(self, request, *args, **kwargs): raise ProjectAPIException( non_field_errors=[ "Votre compte n'est pas encore vérifié. Veuillez vérifier vos e-mails reçus, et vos courriers indésirables." - ] + ], + extra={"user_id": user.id}, ) login(request, user) # will create the user session return Response({"csrf_token": get_token(request)}) diff --git a/frontend/src/components/SendNewSignupVerificationEmail.vue b/frontend/src/components/SendNewSignupVerificationEmail.vue index 99816bd6..5453ead7 100644 --- a/frontend/src/components/SendNewSignupVerificationEmail.vue +++ b/frontend/src/components/SendNewSignupVerificationEmail.vue @@ -1,18 +1,19 @@ @@ -23,12 +24,13 @@ import { handleError } from "@/utils/error-handling" import useToaster from "@/composables/use-toaster" const props = defineProps({ userId: Number }) +const opened = ref(false) -const emailNotReceived = ref(false) +const close = () => (opened.value = false) // Main request definition const url = computed(() => `/api/v1/send-new-signup-verification-email/${props.userId}`) -const { response, execute, isFetching, isFinished } = useFetch(url, { +const { response, execute } = useFetch(url, { immediate: false, }).json() @@ -38,5 +40,18 @@ const resendEmail = async () => { if (response.value.ok) { useToaster().addSuccessMessage("L'email de vérification a été renvoyé.") } + close() } + +const actions = [ + { + label: "Renvoyer un nouvel e-mail", + onClick: resendEmail, + }, + { + label: "Annuler", + onClick: close, + secondary: true, + }, +] diff --git a/frontend/src/icons.js b/frontend/src/icons.js index a21670fd..90b61907 100644 --- a/frontend/src/icons.js +++ b/frontend/src/icons.js @@ -191,3 +191,12 @@ export const RiEyeOffLine = { height: 24, raw: '', } + +export const RiMailForbidLine = { + name: "ri-mail-forbid-line", + minX: 0, + minY: 0, + width: 24, + height: 24, + raw: '', +} diff --git a/frontend/src/utils/error-handling.js b/frontend/src/utils/error-handling.js index 97d10c29..379e030a 100644 --- a/frontend/src/utils/error-handling.js +++ b/frontend/src/utils/error-handling.js @@ -22,9 +22,9 @@ export const handleError = async (response) => { // show an error toast addErrorMessage(backErrorData.globalError) } - // Return other errors to be handled by Vuelidate directly + // Return other errors to be handled by Vuelidate directly (and "extra" parameters to get additional data) // If you don't have a form and expect global errors only, just ignore the result of this function when called. - return { nonFieldErrors: backErrorData.nonFieldErrors, ...backErrorData.fieldErrors } + return { nonFieldErrors: backErrorData.nonFieldErrors, ...backErrorData.fieldErrors, extra: backErrorData.extra } // TODO LATER: auto logout (in case of 401) could be handled here // TODO LATER: timeout could be handled here too diff --git a/frontend/src/views/LoginPage.vue b/frontend/src/views/LoginPage.vue index 0697201d..d85fd729 100644 --- a/frontend/src/views/LoginPage.vue +++ b/frontend/src/views/LoginPage.vue @@ -2,6 +2,7 @@

Se connecter

+ @@ -45,6 +46,7 @@ import { useRouter } from "vue-router" import { useRootStore } from "@/stores/root" import FormWrapper from "@/components/FormWrapper" import SingleItemWrapper from "@/components/SingleItemWrapper" +import SendNewSignupVerificationEmail from "@/components/SendNewSignupVerificationEmail" const router = useRouter() const rootStore = useRootStore() @@ -76,6 +78,9 @@ const { data, response, execute, isFetching } = useFetch( .post(state) .json() +const showSendNewConfirmationMail = ref(false) +const userIdForNewConfirmationMail = ref() + // Form validation const submit = async () => { v$.value.$validate() @@ -85,6 +90,13 @@ const submit = async () => { await execute() $externalResults.value = await handleError(response) + // Give the ability to ask for a new e-email, only if the user is not verified yet. + // ⛔️ TODO: change this dirty hack: we use error message until having appropriate error codes in responses + if ($externalResults.value.nonFieldErrors[0].includes("vérifié")) { + showSendNewConfirmationMail.value = true + userIdForNewConfirmationMail.value = $externalResults.value.extra.userId + } + if (response.value.ok) { { await rootStore.fetchInitialData() From 91da5848cecbe8a4cb9b2bf2cc191377e94f40a2 Mon Sep 17 00:00:00 2001 From: ddahan Date: Wed, 27 Mar 2024 18:36:41 +0000 Subject: [PATCH 3/4] typo --- frontend/src/components/SendNewSignupVerificationEmail.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/SendNewSignupVerificationEmail.vue b/frontend/src/components/SendNewSignupVerificationEmail.vue index 5453ead7..e6d0e10e 100644 --- a/frontend/src/components/SendNewSignupVerificationEmail.vue +++ b/frontend/src/components/SendNewSignupVerificationEmail.vue @@ -11,7 +11,7 @@ >

Si vous n'avez pas reçu d'email au bout de quelques minutes, veuillez vérifier l'adresse e-mail entrée, ainsi - que vos courriers indésirables. Sinon, cliquez sur le bouton ci-dessous pour recevoir un nouvel e-mail : + que vos courriers indésirables. Sinon, cliquez sur le bouton ci-dessous pour recevoir un nouvel e-mail.

From 8c5c702e8069697385940fff0fd93a1a1728af66 Mon Sep 17 00:00:00 2001 From: Alejandro MG Date: Thu, 28 Mar 2024 11:17:23 +0100 Subject: [PATCH 4/4] Secondaty button for email resending --- .../src/components/SendNewSignupVerificationEmail.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/SendNewSignupVerificationEmail.vue b/frontend/src/components/SendNewSignupVerificationEmail.vue index e6d0e10e..c551dc5b 100644 --- a/frontend/src/components/SendNewSignupVerificationEmail.vue +++ b/frontend/src/components/SendNewSignupVerificationEmail.vue @@ -1,6 +1,13 @@