diff --git a/features/account/resetting_password.feature b/features/account/resetting_password.feature index e381cd3c31f..4c4cd497311 100644 --- a/features/account/resetting_password.feature +++ b/features/account/resetting_password.feature @@ -9,15 +9,15 @@ Feature: Resetting a password And that channel allows to shop using "English (United States)" and "Polish (Poland)" locales And there is a user "goodman@example.com" identified by "heisenberg" - @ui @email + @ui @email @api Scenario: Resetting an account password When I want to reset password - And I specify the email as "goodman@example.com" + And I specify customer email as "goodman@example.com" And I reset it Then I should be notified that email with reset instruction has been sent And an email with reset token should be sent to "goodman@example.com" - @ui @email + @ui @email @api Scenario: Resetting an account password in different locale than the default one When I reset password for email "goodman@example.com" in "Polish (Poland)" locale Then an email with reset token should be sent to "goodman@example.com" in "Polish (Poland)" locale diff --git a/features/account/resetting_password_validation.feature b/features/account/resetting_password_validation.feature index 2ffba025c65..3f0969e78ca 100644 --- a/features/account/resetting_password_validation.feature +++ b/features/account/resetting_password_validation.feature @@ -8,7 +8,7 @@ Feature: Resetting a password validation Given the store operates on a single channel in "United States" And there is a user "goodman@example.com" identified by "heisenberg" - @ui + @ui @api Scenario: Trying to reset password without specifying email When I want to reset password And I do not specify the email diff --git a/src/Sylius/Behat/Context/Api/EmailContext.php b/src/Sylius/Behat/Context/Api/EmailContext.php new file mode 100644 index 00000000000..dd86e5aef8f --- /dev/null +++ b/src/Sylius/Behat/Context/Api/EmailContext.php @@ -0,0 +1,51 @@ +emailChecker = $emailChecker; + $this->translator = $translator; + } + + /** + * @Then an email with reset token should be sent to :recipient + * @Then an email with reset token should be sent to :recipient in :localeCode locale + */ + public function anEmailWithResetTokenShouldBeSentTo(string $recipient, string $localeCode = 'en_US'): void + { + $this->assertEmailContainsMessageTo( + $this->translator->trans('sylius.email.password_reset.reset_your_password', [], null, $localeCode), + $recipient + ); + } + + private function assertEmailContainsMessageTo(string $message, string $recipient): void + { + Assert::true($this->emailChecker->hasMessageTo($message, $recipient)); + } +} diff --git a/src/Sylius/Behat/Context/Api/Shop/LoginContext.php b/src/Sylius/Behat/Context/Api/Shop/LoginContext.php index a8779db0e90..31f638b783d 100644 --- a/src/Sylius/Behat/Context/Api/Shop/LoginContext.php +++ b/src/Sylius/Behat/Context/Api/Shop/LoginContext.php @@ -14,17 +14,34 @@ namespace Sylius\Behat\Context\Api\Shop; use Behat\Behat\Context\Context; +use Sylius\Behat\Client\ApiClientInterface; use Sylius\Behat\Client\ApiSecurityClientInterface; +use Sylius\Behat\Client\Request; +use Sylius\Behat\Client\ResponseCheckerInterface; use Webmozart\Assert\Assert; final class LoginContext implements Context { /** @var ApiSecurityClientInterface */ - private $client; + private $apiSecurityClient; - public function __construct(ApiSecurityClientInterface $client) - { - $this->client = $client; + /** @var ApiClientInterface */ + private $apiClient; + + /** @var ResponseCheckerInterface */ + private $responseChecker; + + /** @var Request|null */ + private $request; + + public function __construct( + ApiSecurityClientInterface $apiSecurityClient, + ApiClientInterface $apiClient, + ResponseCheckerInterface $responseChecker + ) { + $this->apiSecurityClient = $apiSecurityClient; + $this->apiClient = $apiClient; + $this->responseChecker = $responseChecker; } /** @@ -40,7 +57,35 @@ public function iAmAVisitor(): void */ public function iWantToLogIn(): void { - $this->client->prepareLoginRequest(); + $this->apiSecurityClient->prepareLoginRequest(); + } + + /** + * @When I want to reset password + */ + public function iWantToResetPassword(): void + { + $this->request = Request::create('shop', 'request-reset-password', 'Bearer'); + } + + /** + * @When I reset password for email :email in :localeCode locale + */ + public function iResetPasswordForEmailInLocale(string $email, string $localeCode): void + { + $this->iWantToResetPassword(); + $this->iSpecifyTheEmail($email); + $this->addLocaleCode($localeCode); + $this->iResetIt(); + } + + /** + * @When I reset it + * @When I try to reset it + */ + public function iResetIt(): void + { + $this->apiClient->executeCustomRequest($this->request); } /** @@ -48,7 +93,16 @@ public function iWantToLogIn(): void */ public function iSpecifyTheUsername(string $username): void { - $this->client->setEmail($username); + $this->apiSecurityClient->setEmail($username); + } + + /** + * @When I specify customer email as :email + * @When I do not specify the email + */ + public function iSpecifyTheEmail(string $email = ''): void + { + $this->request->setContent(['email' => $email]); } /** @@ -56,7 +110,7 @@ public function iSpecifyTheUsername(string $username): void */ public function iSpecifyThePasswordAs(string $password): void { - $this->client->setPassword($password); + $this->apiSecurityClient->setPassword($password); } /** @@ -65,7 +119,7 @@ public function iSpecifyThePasswordAs(string $password): void */ public function iLogIn(): void { - $this->client->call(); + $this->apiSecurityClient->call(); } /** @@ -73,10 +127,10 @@ public function iLogIn(): void */ public function iLogInAsWithPassword(string $email, string $password): void { - $this->client->prepareLoginRequest(); - $this->client->setEmail($email); - $this->client->setPassword($password); - $this->client->call(); + $this->apiSecurityClient->prepareLoginRequest(); + $this->apiSecurityClient->setEmail($email); + $this->apiSecurityClient->setPassword($password); + $this->apiSecurityClient->call(); } /** @@ -85,7 +139,7 @@ public function iLogInAsWithPassword(string $email, string $password): void */ public function iLogOut() { - $this->client->logOut(); + $this->apiSecurityClient->logOut(); } /** @@ -93,7 +147,7 @@ public function iLogOut() */ public function iShouldBeLoggedIn(): void { - Assert::true($this->client->isLoggedIn(), 'Shop user should be logged in, but they are not.'); + Assert::true($this->apiSecurityClient->isLoggedIn(), 'Shop user should be logged in, but they are not.'); } /** @@ -101,7 +155,7 @@ public function iShouldBeLoggedIn(): void */ public function iShouldNotBeLoggedIn(): void { - Assert::false($this->client->isLoggedIn(), 'Shop user should not be logged in, but they are.'); + Assert::false($this->apiSecurityClient->isLoggedIn(), 'Shop user should not be logged in, but they are.'); } /** @@ -109,6 +163,20 @@ public function iShouldNotBeLoggedIn(): void */ public function iShouldBeNotifiedAboutBadCredentials(): void { - Assert::same($this->client->getErrorMessage(), 'Invalid credentials.'); + Assert::same($this->apiSecurityClient->getErrorMessage(), 'Invalid credentials.'); + } + + /** + * @Then I should be notified that email with reset instruction has been sent + */ + public function iShouldBeNotifiedThatEmailWithResetInstructionWasSent(): void + { + $response = $this->apiClient->getLastResponse(); + Assert::same($response->getStatusCode(), 202); + } + + private function addLocaleCode(string $localeCode): void + { + $this->request->updateContent(['localeCode' => $localeCode]); } } diff --git a/src/Sylius/Behat/Context/Ui/Shop/LoginContext.php b/src/Sylius/Behat/Context/Ui/Shop/LoginContext.php index 935c281d2b2..e6f07471032 100644 --- a/src/Sylius/Behat/Context/Ui/Shop/LoginContext.php +++ b/src/Sylius/Behat/Context/Ui/Shop/LoginContext.php @@ -119,7 +119,7 @@ public function iSpecifyTheUsername(?string $username = null): void } /** - * @When I specify the email as :email + * @When I specify customer email as :email * @When I do not specify the email */ public function iSpecifyTheEmail(?string $email = null): void diff --git a/src/Sylius/Behat/Resources/config/services/contexts/api.xml b/src/Sylius/Behat/Resources/config/services/contexts/api.xml index 2f013ad0ffe..22594485204 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/api.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/api.xml @@ -16,7 +16,6 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd" > - - + diff --git a/src/Sylius/Behat/Resources/config/services/contexts/api/email.xml b/src/Sylius/Behat/Resources/config/services/contexts/api/email.xml new file mode 100644 index 00000000000..c4a1f219ef1 --- /dev/null +++ b/src/Sylius/Behat/Resources/config/services/contexts/api/email.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml index 90d3a893771..4b63b51dbaa 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml @@ -74,6 +74,8 @@ + + diff --git a/src/Sylius/Behat/Resources/config/suites/api/account/login.yml b/src/Sylius/Behat/Resources/config/suites/api/account/login.yml index d0eb5d0ed77..b77b7b5b554 100644 --- a/src/Sylius/Behat/Resources/config/suites/api/account/login.yml +++ b/src/Sylius/Behat/Resources/config/suites/api/account/login.yml @@ -6,12 +6,18 @@ default: api_customer_login: contexts: - sylius.behat.context.hook.doctrine_orm + - sylius.behat.context.hook.email_spool + - sylius.behat.context.transform.locale + - sylius.behat.context.transform.shared_storage - sylius.behat.context.transform.user - sylius.behat.context.setup.channel + - sylius.behat.context.setup.locale - sylius.behat.context.setup.user + - sylius.behat.context.api.email + - sylius.behat.context.api.shop.customer - sylius.behat.context.api.shop.login filters: diff --git a/src/Sylius/Bundle/ApiBundle/Command/LocaleCodeAwareInterface.php b/src/Sylius/Bundle/ApiBundle/Command/LocaleCodeAwareInterface.php new file mode 100644 index 00000000000..d0573a1da81 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Command/LocaleCodeAwareInterface.php @@ -0,0 +1,22 @@ +email = $email; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getChannelCode(): ?string + { + return $this->channelCode; + } + + public function setChannelCode(?string $channelCode): void + { + $this->channelCode = $channelCode; + } + + public function getLocaleCode(): ?string + { + return $this->localeCode; + } + + public function setLocaleCode(?string $localeCode): void + { + $this->localeCode = $localeCode; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Command/SendResetPasswordEmail.php b/src/Sylius/Bundle/ApiBundle/Command/SendResetPasswordEmail.php new file mode 100644 index 00000000000..827a93d44f4 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Command/SendResetPasswordEmail.php @@ -0,0 +1,52 @@ +email = $email; + $this->channelCode = $channelCode; + $this->localeCode = $localeCode; + } + + public function email(): string + { + return $this->email; + } + + public function channelCode(): string + { + return $this->channelCode; + } + + public function localeCode(): string + { + return $this->localeCode; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/RequestResetPasswordTokenHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/RequestResetPasswordTokenHandler.php new file mode 100644 index 00000000000..27c1b44cbfc --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/RequestResetPasswordTokenHandler.php @@ -0,0 +1,63 @@ +userRepository = $userRepository; + $this->commandBus = $eventBus; + $this->generator = $generator; + } + + public function __invoke(RequestResetPasswordToken $command): void + { + $user = $this->userRepository->findOneByEmail($command->getEmail()); + Assert::notNull($user); + + $user->setPasswordResetToken($this->generator->generate()); + $user->setPasswordRequestedAt(new \DateTime()); + + $this->commandBus->dispatch( + new SendResetPasswordEmail( + $command->getEmail(), + $command->getChannelCode(), + $command->getLocaleCode() + ), + [new DispatchAfterCurrentBusStamp()] + ); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/SendResetPasswordEmailHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/SendResetPasswordEmailHandler.php new file mode 100644 index 00000000000..0b4eba47224 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/SendResetPasswordEmailHandler.php @@ -0,0 +1,60 @@ +emailSender = $emailSender; + $this->channelRepository = $channelRepository; + $this->userRepository = $userRepository; + } + + public function __invoke(SendResetPasswordEmail $command) + { + $user = $this->userRepository->findOneByEmail($command->email); + $channel = $this->channelRepository->findOneByCode($command->channelCode()); + + $this->emailSender->send( + Emails::PASSWORD_RESET, + [$command->email], + [ + 'user' => $user, + 'localeCode' => $command->localeCode(), + 'channel' => $channel, + ] + ); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/DataTransformer/LocaleCodeAwareInputCommandDataTransformer.php b/src/Sylius/Bundle/ApiBundle/DataTransformer/LocaleCodeAwareInputCommandDataTransformer.php new file mode 100644 index 00000000000..4049d0dbaa3 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/DataTransformer/LocaleCodeAwareInputCommandDataTransformer.php @@ -0,0 +1,47 @@ +localeContext = $localeContext; + } + + public function transform($object, string $to, array $context = []) + { + if ($object->getLocaleCode() !== null) { + return $object; + } + + $localeCode = $this->localeContext->getLocaleCode(); + + $object->setLocaleCode($localeCode); + + return $object; + } + + public function supportsTransformation($object): bool + { + return $object instanceof LocaleCodeAwareInterface; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/api_resources/PasswordReset.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/api_resources/PasswordReset.xml new file mode 100644 index 00000000000..d1b5f792244 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/api_resources/PasswordReset.xml @@ -0,0 +1,46 @@ + + + + + + + + sylius + + + + POST + shop/request-reset-password + input + Sylius\Bundle\ApiBundle\Command\RequestResetPasswordToken + false + 202 + + sylius + sylius_shop_password_reset + + + shop:reset_password:create + + + Request password reset + + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Account/ResetPassword.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Account/ResetPassword.xml new file mode 100644 index 00000000000..26c1d0c2685 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Commands/Account/ResetPassword.xml @@ -0,0 +1,26 @@ + + + + + + + + shop:reset_password:create + + + shop:reset_password:create + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml index 3651a1dfde9..92bf8566e51 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml @@ -109,6 +109,11 @@ + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml index 94d47258b02..9cb4f30b297 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml @@ -102,6 +102,13 @@ + + + + + + + @@ -114,5 +121,12 @@ + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/validation/ResetPassword.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/ResetPassword.xml new file mode 100644 index 00000000000..933f0360cff --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/ResetPassword.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendResetPasswordEmailHandlerTest.php b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendResetPasswordEmailHandlerTest.php new file mode 100644 index 00000000000..37a236e0d4d --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendResetPasswordEmailHandlerTest.php @@ -0,0 +1,137 @@ +getContainer(); + + /** @var Filesystem $filesystem */ + $filesystem = $container->get('filesystem'); + + /** @var TranslatorInterface $translator */ + $translator = $container->get('translator'); + + /** @var EmailChecker $emailChecker */ + $emailChecker = $container->get('sylius.behat.email_checker'); + + $filesystem->remove($emailChecker->getSpoolDirectory()); + + $emailSender = $container->get('sylius.email_sender'); + + /** @var ChannelRepositoryInterface|ObjectProphecy $channelRepository */ + $channelRepository = $this->prophesize(ChannelRepositoryInterface::class); + /** @var UserRepositoryInterface|ObjectProphecy $userRepository */ + $userRepository = $this->prophesize(UserRepositoryInterface::class); + /** @var ChannelInterface|ObjectProphecy $channel */ + $channel = $this->prophesize(ChannelInterface::class); + /** @var UserInterface|ObjectProphecy $user */ + $user = $this->prophesize(UserInterface::class); + + $user->getUsername()->willReturn('username'); + $user->getPasswordResetToken()->willReturn('token'); + + $channelRepository->findOneByCode('CHANNEL_CODE')->willReturn($channel->reveal()); + $userRepository->findOneByEmail('user@example.com')->willReturn($user->reveal()); + + $resetPasswordEmailHandler = new SendResetPasswordEmailHandler( + $emailSender, + $channelRepository->reveal(), + $userRepository->reveal() + ); + + $resetPasswordEmailHandler(new SendResetPasswordEmail( + 'user@example.com', + 'CHANNEL_CODE', + 'en_US' + )); + + self::assertSame(1, $emailChecker->countMessagesTo('user@example.com')); + self::assertTrue($emailChecker->hasMessageTo( + $translator->trans('sylius.email.password_reset.to_reset_your_password_token', [], null, 'en_US'), + 'user@example.com' + )); + } + + /** + * @test + */ + public function it_sends_password_reset_token_email_with_hostname(): void + { + $container = self::bootKernel()->getContainer(); + + /** @var Filesystem $filesystem */ + $filesystem = $container->get('filesystem'); + + /** @var TranslatorInterface $translator */ + $translator = $container->get('translator'); + + /** @var EmailChecker $emailChecker */ + $emailChecker = $container->get('sylius.behat.email_checker'); + + $filesystem->remove($emailChecker->getSpoolDirectory()); + + $emailSender = $container->get('sylius.email_sender'); + + /** @var ChannelRepositoryInterface|ObjectProphecy $channelRepository */ + $channelRepository = $this->prophesize(ChannelRepositoryInterface::class); + /** @var UserRepositoryInterface|ObjectProphecy $userRepository */ + $userRepository = $this->prophesize(UserRepositoryInterface::class); + /** @var ChannelInterface|ObjectProphecy $channel */ + $channel = $this->prophesize(ChannelInterface::class); + /** @var UserInterface|ObjectProphecy $user */ + $user = $this->prophesize(UserInterface::class); + + $user->getUsername()->willReturn('username'); + $user->getPasswordResetToken()->willReturn('token'); + + $channelRepository->findOneByCode('CHANNEL_CODE')->willReturn($channel->reveal()); + $userRepository->findOneByEmail('user@example.com')->willReturn($user->reveal()); + + $resetPasswordEmailHandler = new SendResetPasswordEmailHandler( + $emailSender, + $channelRepository->reveal(), + $userRepository->reveal() + ); + + $resetPasswordEmailHandler(new SendResetPasswordEmail( + 'user@example.com', + 'CHANNEL_CODE', + 'en_US' + )); + + self::assertSame(1, $emailChecker->countMessagesTo('user@example.com')); + self::assertTrue($emailChecker->hasMessageTo( + $translator->trans('sylius.email.password_reset.to_reset_your_password_token', [], null, 'en_US'), + 'user@example.com' + )); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Tests/TestKernel.php b/src/Sylius/Bundle/ApiBundle/Tests/TestKernel.php new file mode 100644 index 00000000000..6616a08ac96 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Tests/TestKernel.php @@ -0,0 +1,199 @@ +setParameter('locale', 'en_US'); + + $containerBuilder->loadFromExtension('framework', [ + 'test' => null, + 'secret' => 'S0ME_SECRET', + 'session' => [ + 'handler_id' => null, + ], + 'default_locale' => '%locale%', + 'translator' => [ + 'fallbacks' => [ + '%locale%', + 'en', + ], + ], + ]); + + $containerBuilder->loadFromExtension('security', [ + 'firewalls' => [ + 'main' => [ + 'anonymous' => true, + ], + ], + ]); + + $containerBuilder->loadFromExtension('doctrine', [ + 'dbal' => [ + 'driver' => 'pdo_mysql', + 'server_version' => '5.7', + 'charset' => 'UTF8', + 'url' => 'sqlite:///%kernel.project_dir%/var/data.db', + ], + ]); + + $containerBuilder->loadFromExtension('swiftmailer', [ + 'disable_delivery' => true, + 'logging' => true, + 'spool' => [ + 'type' => 'file', + 'path' => '%kernel.cache_dir%/spool', + ], + ]); + + $containerBuilder->loadFromExtension('stof_doctrine_extensions', [ + 'default_locale' => '%locale%', + ]); + + $containerBuilder->loadFromExtension('twig', [ + 'debug' => '%kernel.debug%', + 'strict_variables' => '%kernel.debug%', + ]); + + $containerBuilder->loadFromExtension('lexik_jwt_authentication', [ + 'secret_key' => 'var', + 'public_key' => 'var', + 'pass_phrase' => 'var', + ]); + + $loader->load('@SyliusCoreBundle/Resources/config/app/config.yml'); + $loader->load('@SyliusApiBundle/Resources/config/app/config.yaml'); + } + + protected function configureRoutes(RoutingConfigurator $routes): void + { + } + + public function getCacheDir(): string + { + return sys_get_temp_dir() . '/SyliusApiBundle/cache/' . $this->getEnvironment(); + } + + public function getLogDir(): string + { + return sys_get_temp_dir() . '/SyliusApiBundle/logs'; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/composer.json b/src/Sylius/Bundle/ApiBundle/composer.json index d58b096da0c..dce65a573a5 100644 --- a/src/Sylius/Bundle/ApiBundle/composer.json +++ b/src/Sylius/Bundle/ApiBundle/composer.json @@ -26,7 +26,8 @@ "php": "^7.4", "api-platform/core": "^2.5", "sylius/core-bundle": "^1.7", - "symfony/messenger": "^4.4 || ^5.2" + "symfony/messenger": "^4.4 || ^5.2", + "lexik/jwt-authentication-bundle": "^2.6" }, "require-dev": { "matthiasnoback/symfony-config-test": "^4.2", diff --git a/src/Sylius/Bundle/ApiBundle/phpunit.xml.dist b/src/Sylius/Bundle/ApiBundle/phpunit.xml.dist index 31c6db728e2..5d986d3f7f9 100644 --- a/src/Sylius/Bundle/ApiBundle/phpunit.xml.dist +++ b/src/Sylius/Bundle/ApiBundle/phpunit.xml.dist @@ -9,4 +9,15 @@ ./Tests/ + + + + + + + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/SendResetPasswordEmailHandlerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/SendResetPasswordEmailHandlerSpec.php new file mode 100644 index 00000000000..10ff2de8882 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/SendResetPasswordEmailHandlerSpec.php @@ -0,0 +1,71 @@ +beConstructedWith($emailSender, $channelRepository, $userRepository); + } + + function it_is_a_message_handler(): void + { + $this->shouldImplement(MessageHandlerInterface::class); + } + + function it_sends_message_with_reset_password_token( + SenderInterface $sender, + UserRepositoryInterface $userRepository, + SendResetPasswordEmail $sendResetPasswordEmail, + UserInterface $user, + ChannelRepositoryInterface $channelRepository, + ChannelInterface $channel + ): void { + $sendResetPasswordEmail->email()->willReturn('iAmAnEmail@spaghettiCode.php'); + + $userRepository->findOneByEmail('iAmAnEmail@spaghettiCode.php')->willReturn($user); + + $sendResetPasswordEmail->channelCode()->willReturn('WEB'); + + $channelRepository->findOneByCode('WEB')->willReturn($channel); + + $sendResetPasswordEmail->localeCode()->willReturn('en_US'); + + $sender->send( + Emails::PASSWORD_RESET, + ['iAmAnEmail@spaghettiCode.php'], + [ + 'user' => $user->getWrappedObject(), + 'localeCode' => 'en_US', + 'channel' => $channel->getWrappedObject(), + ] + ); + + $this(new SendResetPasswordEmail('iAmAnEmail@spaghettiCode.php', 'WEB', 'en_US')); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/Mailer/Emails.php b/src/Sylius/Bundle/CoreBundle/Mailer/Emails.php index b65e5e7a175..78135590602 100644 --- a/src/Sylius/Bundle/CoreBundle/Mailer/Emails.php +++ b/src/Sylius/Bundle/CoreBundle/Mailer/Emails.php @@ -24,4 +24,6 @@ interface Emails public const SHIPMENT_CONFIRMATION = 'shipment_confirmation'; public const USER_REGISTRATION = 'user_registration'; + + public const PASSWORD_RESET = 'password_reset'; } diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_mailer.yml b/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_mailer.yml index b2cfe7b61c3..370c8f14ef2 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_mailer.yml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_mailer.yml @@ -9,3 +9,6 @@ sylius_mailer: order_confirmation: subject: sylius.emails.order_confirmation.subject template: "@SyliusCore/Email/orderConfirmation.html.twig" + password_reset: + subject: sylius.emails.user.password_reset.subject + template: "@SyliusCore/Email/passwordReset.html.twig" diff --git a/src/Sylius/Bundle/CoreBundle/Resources/views/Email/passwordReset.html.twig b/src/Sylius/Bundle/CoreBundle/Resources/views/Email/passwordReset.html.twig new file mode 100644 index 00000000000..559ec8c2d02 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/views/Email/passwordReset.html.twig @@ -0,0 +1,31 @@ +{% extends '@SyliusCore/Email/layout.html.twig' %} + +{% block subject %} + {{ 'sylius.email.password_reset.subject'|trans({}, null, localeCode) }} +{% endblock %} + +{% block content %} + {% if sylius_bundle_loaded_checker('SyliusShopBundle') %} + {% set url = channel.hostname is not null ? 'http://' ~ channel.hostname ~ path('sylius_shop_password_reset', { 'token': user.passwordResetToken}) : url('sylius_shop_password_reset', { 'token': user.passwordResetToken, '_locale': localeCode}) %} + {% endif %} +
+
+ {{ 'sylius.email.password_reset.hello'|trans({}, null, localeCode) }} {{ user.username }}
+
+ {% if sylius_bundle_loaded_checker('SyliusShopBundle') %} + {{ 'sylius.email.password_reset.to_reset_your_password'|trans({}, null, localeCode) }}: + {% else %} + {{ 'sylius.email.password_reset.to_reset_your_password_token'|trans({}, null, localeCode) }}: + {% endif %} +
+ +
+ {% if sylius_bundle_loaded_checker('SyliusShopBundle') %} + + {{ 'sylius.email.password_reset.reset_your_password'|trans({}, null, localeCode) }} + + {% else %} + {{ 'sylius.email.password_reset.token'|trans({}, null, localeCode) }}: {{ user.passwordResetToken }} + {% endif %} +
+{% endblock %} diff --git a/src/Sylius/Bundle/ShopBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/ShopBundle/Resources/translations/messages.en.yml index 005ac51051e..59ff57ec43d 100644 --- a/src/Sylius/Bundle/ShopBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/ShopBundle/Resources/translations/messages.en.yml @@ -18,6 +18,8 @@ sylius: reset_your_password: 'Reset your password' subject: 'Password reset' to_reset_your_password: 'To reset your password - click the link below' + to_reset_your_password_token: 'To reset your password - use the token below' + token: 'Your password reset token is' user_registration: start_shopping: 'Start shopping' subject: 'User registration' diff --git a/src/Sylius/Bundle/ShopBundle/Resources/views/Email/passwordReset.html.twig b/src/Sylius/Bundle/ShopBundle/Resources/views/Email/passwordReset.html.twig index 57f99bcec7b..4905c466ffd 100644 --- a/src/Sylius/Bundle/ShopBundle/Resources/views/Email/passwordReset.html.twig +++ b/src/Sylius/Bundle/ShopBundle/Resources/views/Email/passwordReset.html.twig @@ -1,22 +1 @@ -{% extends '@SyliusShop/Email/layout.html.twig' %} - -{% block subject %} - {{ 'sylius.email.password_reset.subject'|trans({}, null, localeCode) }} -{% endblock %} - -{% block content %} - {% set url = channel.hostname is not null ? 'http://' ~ channel.hostname ~ path('sylius_shop_password_reset', { 'token': user.passwordResetToken}) : url('sylius_shop_password_reset', { 'token': user.passwordResetToken}) %} - -
-
- {{ 'sylius.email.password_reset.hello'|trans({}, null, localeCode) }} {{ user.username }}
-
- {{ 'sylius.email.password_reset.to_reset_your_password'|trans({}, null, localeCode) }}: -
- -
- - {{ 'sylius.email.password_reset.reset_your_password'|trans({}, null, localeCode) }} - -
-{% endblock %} +{% extends '@SyliusCore/Email/passwordReset.html.twig' %}