SymfonyCasts / verify-email-bundle

Simple, stylish Email Verification for Symfony
https://symfonycasts.com
MIT License
401 stars 32 forks source link

Can resend another confirmation email when user trying to login by not being enabled #50

Open bastien70 opened 3 years ago

bastien70 commented 3 years ago

Hey! When my user tries to log in when he is not activated, I display a notification telling him to activate his account, and I add a "Resend activation email" link.

If I want to be able to manage this, I will be forced, each time a user tries to connect, to generate a new unique link allowing them to access a route that will use the bundle to send a confirmation email.

In my UserChecker.php class :

    public function checkPreAuth(UserInterface $user)
    {

        if (!$user instanceof User) {
            return;
        }
        if (!$user->getEnabled() && !$user->isVerified()) {
            // $uniqueResendEmailurl = ....
            throw new CustomUserMessageAccountStatusException("Votre compte n'est pas activé. Veuillez confirmer 
            votre inscription en cliquant sur le lien qui vous a été envoyé par email. Pensez à vérifier dans vos spams. <a href=. "$uniqueResendEmailurl" . >Renvoyer l'email de confirmation</a>");
        }
    }

The bundle should really be able to integrate this functionality directly.

To counter this, I thought about using another of your bundles. The symfonycasts/reset-password-bundle

By creating a "ResendConfirmationEmailRequest" entity which is a full clone of the "ResetPasswordRequest" entity from your other bundle. And use the same methods to generate a unique signature to allow a user to receive a confirmation email again.

What do you think ?

jrushlow commented 3 years ago

Related to #35

Howdy @bastien70 - adding the ability to generate a new verification link is something that is currently planned.. I believe the functionality will be added to the templates in MakerBundle but I'm not 100% positive on that yet. Stay tuned for more information on this...

aniskasmi commented 3 years ago

HI do you have information for this new feature ? Date or delay because on my application i want them

latrach commented 2 years ago

yo can add a route to request new link :

    /**
     * requestVerifyUserEmail
     */
    #[Route('/request-verify-email', name: 'app_request_verify_email')]
    public function requestVerifyUserEmail(
        Request $request,
        UserRepository $userRepository
    ): Response {

        if ($this->getUser()) {
            return $this->redirectToRoute('app_home');
        }

        $form = $this->createForm(RequestVerifyUserEmailFormType::class);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            // generate a signed url and email it to the user
            $user =  $userRepository->findOneByEmail($form->get('email')->getData());
            if ($user) {
                $this->emailVerifier->sendEmailConfirmation(
                    'app_verify_email',
                    $user,
                    (new TemplatedEmail())
                        ->from(new Address('email@example.com', 'Sender'))
                        ->to($user->getEmail())
                        ->subject('Validation Link')
                        ->htmlTemplate('security/registration/confirmation_email.html.twig')
                );
                // do anything else you need here, like flash message
                $this->addFlash('success', 'blabla.');
                return $this->redirectToRoute('app_home');
            } else {
                $this->addFlash('error',  'Email inconnu.');
            }
        }
        return $this->render('security/registration/request.html.twig', [
            'requestForm' => $form->createView(),
        ]);
    }

and a form RequestVerifyUserEmailFormType

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class RequestVerifyUserEmailFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('email', EmailType::class, [
                'attr' => ['autocomplete' => 'email'],
                'constraints' => [
                    new NotBlank([
                        'message' => 'Merci de saisir votre email',
                    ]),
                ],
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([]);
    }
}
arirangz commented 2 years ago

@latrach thanks for sharing this solution

BAD-WOLF commented 1 year ago

Idea: Currently, in the verifyUserEmail() method of the RegistrationController class, we handle exceptions using VerifyEmailExceptionInterface after attempting to verify the user's email. The proposal is to handle the more specific exception, ExpiredSignatureException, before the more general exception, VerifyEmailExceptionInterface.

Details:

Currently, we have the following code:

try {
    $this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
} catch (VerifyEmailExceptionInterface $exception) {
    $this->addFlash('verify_email_error', $exception->getReason());
    return $this->redirectToRoute('app_register');
}

Suggestion:

My suggestion is to adjust the structure of the try and catch block as follows:

try {
    $this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
} catch (ExpiredSignatureException $expiredException) {
    // Remove the user from the database, as the token has expired
    // This allows the user to re-register
    // and subsequently resend the email with the confirmation link
    // Additionally, redirect the user to the registration process again
    // The absence of the user in the database means the user can redo their registration
    return $this->redirectToRoute('app_register');
} catch (VerifyEmailExceptionInterface $genericException) {
    $this->addFlash('verify_email_error', $genericException->getReason());
    return $this->redirectToRoute('app_register');
}

Benefits:

This approach not only enhances how we deal with expired verification tokens but also contributes to a cleaner and clearer registration flow for users.

sovetski commented 11 months ago

@latrach thank you! I think this solution should be integrated by default to the bundle