diff --git a/appveyor.yml b/appveyor.yml index 61c9bfcb6..581f36bdb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ environment: TEST_DATABASE_DSN: mysql://root:Password12!@127.0.0.1:3306/test_maker matrix: - dependencies: highest - php_ver_target: 7.2.3 + php_ver_target: 7.2.5 install: - ps: Set-Service wuauserv -StartupType Manual diff --git a/src/Maker/MakeRegistrationForm.php b/src/Maker/MakeRegistrationForm.php index 2dce4aa33..17d1a7cb0 100644 --- a/src/Maker/MakeRegistrationForm.php +++ b/src/Maker/MakeRegistrationForm.php @@ -27,6 +27,7 @@ use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Console\Command\Command; @@ -34,11 +35,14 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Validator\Validation; +use SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle; /** - * @author Ryan Weaver + * @author Ryan Weaver + * @author Jesse Rushlow * * @internal */ @@ -77,6 +81,11 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma ->addArgument('user-class') ->addArgument('username-field') ->addArgument('password-field') + ->addArgument('will-verify-email') + ->addArgument('id-getter') + ->addArgument('email-getter') + ->addArgument('from-email-address') + ->addArgument('from-email-name') ->addOption('auto-login-authenticator') ->addOption('firewall-name') ->addOption('redirect-route-name') @@ -124,6 +133,29 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $addAnnotation ); + $willVerify = $io->confirm('Do you want to send an email to verify the user\'s email address after registration?', true); + + $input->setArgument('will-verify-email', $willVerify); + + if ($willVerify) { + $this->checkComponentsExist($io); + + $input->setArgument('id-getter', $interactiveSecurityHelper->guessIdGetter($io, $userClass)); + $input->setArgument('email-getter', $interactiveSecurityHelper->guessEmailGetter($io, $userClass, 'email')); + + $input->setArgument('from-email-address', $io->ask( + 'What email address will be used to send registration confirmations? e.g. mailer@your-domain.com', + null, + [Validator::class, 'validateEmailAddress'] + )); + + $input->setArgument('from-email-name', $io->ask( + 'What "name" should be associated with that email address? e.g. "Acme Mail Bot"', + null, + [Validator::class, 'notBlank'] + )); + } + if ($io->confirm('Do you want to automatically authenticate the user after registration?')) { $this->interactAuthenticatorQuestions( $input, @@ -132,7 +164,9 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $securityData, $command ); - } else { + } + + if (!$input->getOption('auto-login-authenticator')) { $routeNames = array_keys($this->router->getRouteCollection()->all()); $input->setOption( 'redirect-route-name', @@ -184,6 +218,27 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen 'Entity\\' ); + $verifyEmailServiceClassNameDetails = $generator->createClassNameDetails( + 'EmailVerifier', + 'Security\\' + ); + + if ($input->getArgument('will-verify-email')) { + $generator->generateClass( + $verifyEmailServiceClassNameDetails->getFullName(), + 'verifyEmail/EmailVerifier.tpl.php', + [ + 'id_getter' => $input->getArgument('id-getter'), + 'email_getter' => $input->getArgument('email-getter'), + ] + ); + + $generator->generateTemplate( + 'registration/confirmation_email.html.twig', + 'registration/twig_email.tpl.php' + ); + } + // 1) Generate the form class $usernameField = $input->getArgument('username-field'); $formClassDetails = $this->generateFormClass( @@ -210,6 +265,11 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen 'user_class_name' => $userClassNameDetails->getShortName(), 'user_full_class_name' => $userClassNameDetails->getFullName(), 'password_field' => $input->getArgument('password-field'), + 'will_verify_email' => $input->getArgument('will-verify-email'), + 'verify_email_security_service' => $verifyEmailServiceClassNameDetails->getFullName(), + 'from_email' => $input->getArgument('from-email-address'), + 'from_email_name' => $input->getArgument('from-email-name'), + 'email_getter' => $input->getArgument('email-getter'), 'authenticator_class_name' => $authenticatorClassName ? Str::getShortClassName($authenticatorClassName) : null, 'authenticator_full_class_name' => $authenticatorClassName, 'firewall_name' => $input->getOption('firewall-name'), @@ -244,11 +304,85 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $this->fileManager->dumpFile($classDetails->getPath(), $userManipulator->getSourceCode()); } + if ($input->getArgument('will-verify-email')) { + $classDetails = new ClassDetails($userClass); + $userManipulator = new ClassSourceManipulator( + file_get_contents($classDetails->getPath()) + ); + $userManipulator->setIo($io); + + $userManipulator->addProperty('isVerified', ['@ORM\Column(type="boolean")'], false); + $userManipulator->addAccessorMethod('isVerified', 'isVerified', 'bool', false); + $userManipulator->addSetter('isVerified', 'bool', false); + + $this->fileManager->dumpFile($classDetails->getPath(), $userManipulator->getSourceCode()); + } + $generator->writeChanges(); $this->writeSuccessMessage($io); - $io->text('Next: Go to /register to check out your new form!'); - $io->text('Make any changes you need to the form, controller & template.'); + $this->successMessage($io, $input->getArgument('will-verify-email'), $userClassNameDetails->getShortName()); + } + + private function successMessage(ConsoleStyle $io, bool $emailVerification, string $userClass): void + { + $closing[] = 'Next:'; + + if (!$emailVerification) { + $closing[] = 'Make any changes you need to the form, controller & template.'; + } else { + $index = 1; + if ($missingPackagesMessage = $this->getMissingComponentsComposerMessage()) { + $closing[] = '1) Install some missing packages:'; + $closing[] = sprintf(' %s', $missingPackagesMessage); + ++$index; + } + + $closing[] = sprintf('%d) In RegistrationController::verifyUserEmail():', $index++); + $closing[] = ' * Customize the last redirectToRoute() after a successful email verification.'; + $closing[] = ' * Make sure you\'re rendering success flash messages or change the $this->addFlash() line.'; + $closing[] = sprintf('%d) Review and customize the form, controller, and templates as needed.', $index++); + $closing[] = sprintf('%d) Run "php bin/console make:migration" to generate a migration for the newly added %s::isVerified property.', $index++, $userClass); + } + + $io->text($closing); + $io->newLine(); + $io->text('Then open your browser, go to "/register" and enjoy your new form!'); + $io->newLine(); + } + + private function checkComponentsExist(ConsoleStyle $io): void + { + $message = $this->getMissingComponentsComposerMessage(); + + if ($message) { + $io->warning([ + 'We\'re missing some important components. Don\'t forget to install these after you\'re finished.', + $message, + ]); + } + } + + private function getMissingComponentsComposerMessage(): ?string + { + $missing = false; + $composerMessage = 'composer require'; + + if (!class_exists(SymfonyCastsVerifyEmailBundle::class)) { + $missing = true; + $composerMessage = sprintf('%s symfonycasts/verify-email-bundle', $composerMessage); + } + + if (!interface_exists(MailerInterface::class)) { + $missing = true; + $composerMessage = sprintf('%s symfony/mailer', $composerMessage); + } + + if (!$missing) { + return null; + } + + return $composerMessage; } public function configureDependencies(DependencyBuilder $dependencies) diff --git a/src/Resources/skeleton/registration/RegistrationController.tpl.php b/src/Resources/skeleton/registration/RegistrationController.tpl.php index 2d48ea81d..23f6bb8ce 100644 --- a/src/Resources/skeleton/registration/RegistrationController.tpl.php +++ b/src/Resources/skeleton/registration/RegistrationController.tpl.php @@ -4,20 +4,41 @@ use ; use ; + +use ; + use ; + +use Symfony\Bridge\Twig\Mime\TemplatedEmail; + use Symfony\Bundle\FrameworkBundle\Controller\; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; + +use Symfony\Component\Mime\Address; + use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; + +use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface; + class extends { + + private $emailVerifier; + + public function __construct(EmailVerifier $emailVerifier) + { + $this->emailVerifier = $emailVerifier; + } + + /** * @Route("", name="") */ @@ -39,7 +60,17 @@ public function register(Request $request, UserPasswordEncoderInterface $passwor $entityManager = $this->getDoctrine()->getManager(); $entityManager->persist($user); $entityManager->flush(); + + // generate a signed url and email it to the user + $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, + (new TemplatedEmail()) + ->from(new Address('', '')) + ->to($user->()) + ->subject('Please Confirm your Email') + ->htmlTemplate('registration/confirmation_email.html.twig') + ); + // do anything else you need here, like send an email @@ -58,4 +89,28 @@ public function register(Request $request, UserPasswordEncoderInterface $passwor 'registrationForm' => $form->createView(), ]); } + + + /** + * @Route("/verify/email", name="app_verify_email") + */ + public function verifyUserEmail(Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); + + // validate email confirmation link, sets User::isVerified=true and persists + try { + $this->emailVerifier->handleEmailConfirmation($request, $this->getUser()); + } catch (VerifyEmailExceptionInterface $exception) { + $this->addFlash('verify_email_error', $exception->getReason()); + + return $this->redirectToRoute(''); + } + + // @TODO Change the redirect on success and handle or remove the flash message in your templates + $this->addFlash('success', 'Your email address has been verified.'); + + return $this->redirectToRoute('app_register'); + } + } diff --git a/src/Resources/skeleton/registration/twig_email.tpl.php b/src/Resources/skeleton/registration/twig_email.tpl.php new file mode 100644 index 000000000..2a6aede56 --- /dev/null +++ b/src/Resources/skeleton/registration/twig_email.tpl.php @@ -0,0 +1,11 @@ +

Hi! Please confirm your email!

+ +

+ Please confirm your email address by clicking the following link:

+ Confirm my Email. + This link will expire in {{ expiresAt|date('g') }} hour(s). +

+ +

+ Cheers! +

diff --git a/src/Resources/skeleton/registration/twig_template.tpl.php b/src/Resources/skeleton/registration/twig_template.tpl.php index 6a7649980..44f6a5d8c 100644 --- a/src/Resources/skeleton/registration/twig_template.tpl.php +++ b/src/Resources/skeleton/registration/twig_template.tpl.php @@ -1,11 +1,17 @@ getHeadPrintCode('Register'); ?> {% block body %} + {% for flashError in app.flashes('verify_email_error') %} + + {% endfor %} +

Register

{{ form_start(registrationForm) }} {{ form_row(registrationForm.) }} - {{ form_row(registrationForm.plainPassword) }} + {{ form_row(registrationForm.plainPassword, { + label: 'Password' + }) }} {{ form_row(registrationForm.agreeTerms) }} diff --git a/src/Resources/skeleton/verifyEmail/EmailVerifier.tpl.php b/src/Resources/skeleton/verifyEmail/EmailVerifier.tpl.php new file mode 100644 index 000000000..c9fc72f49 --- /dev/null +++ b/src/Resources/skeleton/verifyEmail/EmailVerifier.tpl.php @@ -0,0 +1,55 @@ + + +namespace ; + +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface; +use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface; + +class +{ + private $verifyEmailHelper; + private $mailer; + private $entityManager; + + public function __construct(VerifyEmailHelperInterface $helper, MailerInterface $mailer, EntityManagerInterface $manager) + { + $this->verifyEmailHelper = $helper; + $this->mailer = $mailer; + $this->entityManager = $manager; + } + + public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void + { + $signatureComponents = $this->verifyEmailHelper->generateSignature( + $verifyEmailRouteName, + $user->(), + $user->() + ); + + $context = $email->getContext(); + $context['signedUrl'] = $signatureComponents->getSignedUrl(); + $context['expiresAt'] = $signatureComponents->getExpiresAt(); + + $email->context($context); + + $this->mailer->send($email); + } + + /** + * @throws VerifyEmailExceptionInterface + */ + public function handleEmailConfirmation(Request $request, UserInterface $user): void + { + $this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->(), $user->()); + + $user->setIsVerified(true); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + } +} diff --git a/src/Security/InteractiveSecurityHelper.php b/src/Security/InteractiveSecurityHelper.php index 749f07705..0839cb4d5 100644 --- a/src/Security/InteractiveSecurityHelper.php +++ b/src/Security/InteractiveSecurityHelper.php @@ -206,41 +206,56 @@ public function getAuthenticatorClasses(array $firewallData): array public function guessPasswordSetter(SymfonyStyle $io, string $userClass): string { - $reflectionClass = new \ReflectionClass($userClass); - - if ($reflectionClass->hasMethod('setPassword')) { + if (null === ($methodChoices = $this->methodNameGuesser($userClass, 'setPassword'))) { return 'setPassword'; } - $classMethods = []; - foreach ($reflectionClass->getMethods() as $method) { - $classMethods[] = $method->name; - } - return $io->choice( sprintf('Which method on your %s class can be used to set the encoded password (e.g. setPassword())?', $userClass), - $classMethods + $methodChoices ); } public function guessEmailGetter(SymfonyStyle $io, string $userClass, string $emailPropertyName): string { - $reflectionClass = new \ReflectionClass($userClass); + $supposedEmailMethodName = sprintf('get%s', Str::asCamelCase($emailPropertyName)); - $supposedEmailMethodName = 'get'.Str::asCamelCase($emailPropertyName); - - if ($reflectionClass->hasMethod($supposedEmailMethodName)) { + if (null === ($methodChoices = $this->methodNameGuesser($userClass, $supposedEmailMethodName))) { return $supposedEmailMethodName; } + return $io->choice( + sprintf('Which method on your %s class can be used to get the email address (e.g. getEmail())?', $userClass), + $methodChoices + ); + } + + public function guessIdGetter(SymfonyStyle $io, string $userClass): string + { + if (null === ($methodChoices = $this->methodNameGuesser($userClass, 'getId'))) { + return 'getId'; + } + + return $io->choice( + sprintf('Which method on your %s class can be used to get the unique user identifier (e.g. getId())?', $userClass), + $methodChoices + ); + } + + private function methodNameGuesser(string $className, string $suspectedMethodName): ?array + { + $reflectionClass = new \ReflectionClass($className); + + if ($reflectionClass->hasMethod($suspectedMethodName)) { + return null; + } + $classMethods = []; + foreach ($reflectionClass->getMethods() as $method) { $classMethods[] = $method->name; } - return $io->choice( - sprintf('Which method on your %s class can be used to get the email address (e.g. getEmail())?', $userClass), - $classMethods - ); + return $classMethods; } } diff --git a/tests/Maker/MakeRegistrationFormTest.php b/tests/Maker/MakeRegistrationFormTest.php index 34b29988b..31ff1acae 100644 --- a/tests/Maker/MakeRegistrationFormTest.php +++ b/tests/Maker/MakeRegistrationFormTest.php @@ -14,6 +14,7 @@ use Symfony\Bundle\MakerBundle\Maker\MakeRegistrationForm; use Symfony\Bundle\MakerBundle\Test\MakerTestCase; use Symfony\Bundle\MakerBundle\Test\MakerTestDetails; +use Symfony\Component\Filesystem\Filesystem; class MakeRegistrationFormTest extends MakerTestCase { @@ -25,8 +26,9 @@ public function getTestDetails() // user class guessed, // username field guessed // password guessed - // firewall name guessed '', // yes to add UniqueEntity + 'n', // verify user + // firewall name guessed '', // yes authenticate after // 1 authenticator will be guessed ]) @@ -48,6 +50,7 @@ public function getTestDetails() 'emailAlt', // username field 'passwordAlt', // password field 'n', // no UniqueEntity + 'n', // no verify user '', // yes authenticate after 'main', // firewall '1', // authenticator @@ -60,6 +63,7 @@ public function getTestDetails() [ // all basic data guessed 'y', // add UniqueEntity + 'n', // no verify user 'n', // no authenticate after 'app_anonymous', // route name to redirect to ]) @@ -70,5 +74,54 @@ public function getTestDetails() // registration_form_entity_guard_authenticate for details ->addPostMakeCommand('php bin/console cache:clear --env=test'), ]; + + yield 'registration_form_with_email_verification' => [MakerTestDetails::createTest( + $this->getMakerInstance(MakeRegistrationForm::class), + [ + 'n', // add UniqueEntity + 'y', // no verify user + 'jr@rushlow.dev', // from email address + 'SymfonyCasts', // From Name + 'n', // no authenticate after + 0, // route number to redirect to + ]) + ->setRequiredPhpVersion(70200) + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeRegistrationFormVerifyEmail') + ->addExtraDependencies('symfonycasts/verify-email-bundle') + ->assert( + function (string $output, string $directory) { + $this->assertStringContainsString('Success', $output); + + $fs = new Filesystem(); + + $generatedFiles = [ + 'src/Security/EmailVerifier.php', + 'templates/registration/confirmation_email.html.twig', + ]; + + foreach ($generatedFiles as $file) { + $this->assertTrue($fs->exists(sprintf('%s/%s', $directory, $file))); + } + } + ), + ]; + + yield 'verify_email_functional_test' => [MakerTestDetails::createTest( + $this->getMakerInstance(MakeRegistrationForm::class), + [ + 'n', // add UniqueEntity + 'y', // no verify user + 'jr@rushlow.dev', // from email address + 'SymfonyCasts', // From Name + '', // yes authenticate after + 'app_register', + ]) + ->setRequiredPhpVersion(70200) + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeRegistrationFormVerifyEmailFunctionalTest') + ->addExtraDependencies('symfonycasts/verify-email-bundle') + ->configureDatabase() + ->updateSchemaAfterCommand() + ->addExtraDependencies('mailer'), + ]; } } diff --git a/tests/fixtures/MakeRegistrationFormEntity/tests/VerifyEmailDoesntModifyUserEntityTest.php b/tests/fixtures/MakeRegistrationFormEntity/tests/VerifyEmailDoesntModifyUserEntityTest.php new file mode 100644 index 000000000..6cf26d357 --- /dev/null +++ b/tests/fixtures/MakeRegistrationFormEntity/tests/VerifyEmailDoesntModifyUserEntityTest.php @@ -0,0 +1,15 @@ +id; + } + + public function getEmail() + { + return $this->email; + } + + public function setEmail(string $email): self + { + $this->email = $email; + + return $this; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUsername(): string + { + return (string) $this->email; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + public function setRoles(array $roles): self + { + $this->roles = $roles; + + return $this; + } + + /** + * @see UserInterface + */ + public function getPassword(): string + { + return (string) $this->password; + } + + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } + + public function getSalt() + { + } + + public function eraseCredentials() + { + } +} diff --git a/tests/fixtures/MakeRegistrationFormVerifyEmail/tests/UserEntityTest.php b/tests/fixtures/MakeRegistrationFormVerifyEmail/tests/UserEntityTest.php new file mode 100644 index 000000000..5409965d4 --- /dev/null +++ b/tests/fixtures/MakeRegistrationFormVerifyEmail/tests/UserEntityTest.php @@ -0,0 +1,15 @@ +id; + } + + public function getEmail() + { + return $this->email; + } + + public function setEmail(string $email): self + { + $this->email = $email; + + return $this; + } + + public function getUsername(): string + { + return (string) $this->email; + } + + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + public function setRoles(array $roles): self + { + $this->roles = $roles; + + return $this; + } + + public function getPassword(): string + { + return (string) $this->password; + } + + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } + + public function getSalt() + { + } + + public function eraseCredentials() + { + } +} diff --git a/tests/fixtures/MakeRegistrationFormVerifyEmailFunctionalTest/src/Repository/UserRepository.php b/tests/fixtures/MakeRegistrationFormVerifyEmailFunctionalTest/src/Repository/UserRepository.php new file mode 100644 index 000000000..8f695cbe0 --- /dev/null +++ b/tests/fixtures/MakeRegistrationFormVerifyEmailFunctionalTest/src/Repository/UserRepository.php @@ -0,0 +1,21 @@ +entityManager = $entityManager; + $this->urlGenerator = $urlGenerator; + $this->csrfTokenManager = $csrfTokenManager; + $this->passwordEncoder = $passwordEncoder; + } + + public function supports(Request $request) + { + return self::LOGIN_ROUTE === $request->attributes->get('_route') + && $request->isMethod('POST'); + } + + public function getCredentials(Request $request) + { + $credentials = [ + 'email' => $request->request->get('email'), + 'password' => $request->request->get('password'), + 'csrf_token' => $request->request->get('_csrf_token'), + ]; + $request->getSession()->set( + Security::LAST_USERNAME, + $credentials['email'] + ); + + return $credentials; + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + $token = new CsrfToken('authenticate', $credentials['csrf_token']); + if (!$this->csrfTokenManager->isTokenValid($token)) { + throw new InvalidCsrfTokenException(); + } + + $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]); + + if (!$user) { + throw new CustomUserMessageAuthenticationException('Email could not be found.'); + } + + return $user; + } + + public function checkCredentials($credentials, UserInterface $user) + { + return $this->passwordEncoder->isPasswordValid($user, $credentials['password']); + } + + public function getPassword($credentials): ?string + { + return $credentials['password']; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { + return new RedirectResponse($targetPath); + } + + return new RedirectResponse($this->urlGenerator->generate('app_login')); + } + + protected function getLoginUrl() + { + return $this->urlGenerator->generate(self::LOGIN_ROUTE); + } +} diff --git a/tests/fixtures/MakeRegistrationFormVerifyEmailFunctionalTest/tests/VerifyEmailTest.php b/tests/fixtures/MakeRegistrationFormVerifyEmailFunctionalTest/tests/VerifyEmailTest.php new file mode 100644 index 000000000..8fc8fbf50 --- /dev/null +++ b/tests/fixtures/MakeRegistrationFormVerifyEmailFunctionalTest/tests/VerifyEmailTest.php @@ -0,0 +1,41 @@ +request('GET', '/register'); + + $form = $crawler->selectButton('Register')->form(); + $form['registration_form[email]'] = 'jr@rushlow.dev'; + $form['registration_form[plainPassword]'] = 'makeDockerComingSoon!'; + $form['registration_form[agreeTerms]'] = true; + + $client->submit($form); + + $messages = $this->getMailerMessages(); + self::assertCount(1, $messages); + + /** @var EntityManager $em */ + $em = self::$kernel->getContainer() + ->get('doctrine') + ->getManager() + ; + + $query = $em->createQuery('SELECT u FROM App\\Entity\\User u WHERE u.email = \'jr@rushlow.dev\''); + + self::assertFalse(($query->getSingleResult())->isVerified()); + + $context = $messages[0]->getContext(); + + $client->request('GET', $context['signedUrl']); + + self::assertTrue(($query->getSingleResult())->isVerified()); + } +}