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 %}
+
+
+
+{% 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) }}:
-
-
-
-{% endblock %}
+{% extends '@SyliusCore/Email/passwordReset.html.twig' %}