Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API] Password reset #12360

Merged
merged 15 commits into from
Feb 25, 2021
6 changes: 3 additions & 3 deletions features/account/resetting_password.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion features/account/resetting_password_validation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions src/Sylius/Behat/Context/Api/EmailContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Sylius\Behat\Context\Api;

use Behat\Behat\Context\Context;
use Sylius\Component\Core\Test\Services\EmailCheckerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Webmozart\Assert\Assert;

class EmailContext implements Context
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
lchrusciel marked this conversation as resolved.
Show resolved Hide resolved
{
/** @var EmailCheckerInterface */
private $emailChecker;

/** @var TranslatorInterface */
private $translator;

public function __construct(EmailCheckerInterface $emailChecker, TranslatorInterface $translator)
{
$this->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));
}
}
72 changes: 70 additions & 2 deletions src/Sylius/Behat/Context/Api/Shop/LoginContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

public function __construct(ApiSecurityClientInterface $client)
{
/** @var ApiClientInterface */
private $apiClient;

/** @var ResponseCheckerInterface */
private $responseChecker;

/** @var Request */
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
private $request;

public function __construct(
ApiSecurityClientInterface $client,
ApiClientInterface $apiClient,
ResponseCheckerInterface $responseChecker
) {
$this->client = $client;
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
$this->apiClient = $apiClient;
$this->responseChecker = $responseChecker;
}

/**
Expand All @@ -43,6 +60,34 @@ public function iWantToLogIn(): void
$this->client->prepareLoginRequest();
}

/**
* @When I want to reset password
*/
public function iWantToResetPassword(): void
{
$this->request = Request::create('shop', 'password-reset-request', '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);
}

/**
* @When I specify the username as :username
*/
Expand All @@ -51,6 +96,15 @@ public function iSpecifyTheUsername(string $username): void
$this->client->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]);
}

/**
* @When I specify the password as :password
*/
Expand Down Expand Up @@ -111,4 +165,18 @@ public function iShouldBeNotifiedAboutBadCredentials(): void
{
Assert::same($this->client->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]);
}
}
2 changes: 1 addition & 1 deletion src/Sylius/Behat/Context/Ui/Shop/LoginContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
<imports>
<import resource="api/admin.xml" />
<import resource="api/shop.xml" />
<import resource="api/email.xml" />
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
</imports>
</container>
22 changes: 22 additions & 0 deletions src/Sylius/Behat/Resources/config/services/contexts/api/email.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--

This file is part of the Sylius package.

(c) Paweł Jędrzejewski

For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.

-->

<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults public="true" />
<service id="sylius.behat.context.api.email" class="Sylius\Behat\Context\Api\EmailContext">
<argument type="service" id="sylius.behat.email_checker" />
<argument type="service" id="translator" />
</service>
</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@

<service id="sylius.behat.context.api.shop.login" class="Sylius\Behat\Context\Api\Shop\LoginContext">
<argument type="service" id="sylius.behat.client.shop_api_platform_security_client" />
<argument type="service" id="sylius.behat.api_platform_client.shop.account" />
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
</service>

<service id="sylius.behat.context.api.shop.product" class="Sylius\Behat\Context\Api\Shop\ProductContext">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 12 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Command/LocaleCodeAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);
arti0090 marked this conversation as resolved.
Show resolved Hide resolved

namespace Sylius\Bundle\ApiBundle\Command;
arti0090 marked this conversation as resolved.
Show resolved Hide resolved

interface LocaleCodeAwareInterface extends CommandAwareDataTransformerInterface
{
public function getLocaleCode(): ?string;

public function setLocaleCode(?string $localeCode): void;
}
47 changes: 47 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Command/ResetPassword.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Sylius\Bundle\ApiBundle\Command;

class ResetPassword implements ChannelCodeAwareInterface, LocaleCodeAwareInterface
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
{
/** @var string */
public $email;

/** @var string|null */
private $channelCode;

/** @var string|null */
private $localeCode;

public function __construct(string $email)
{
$this->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;
}
}
42 changes: 42 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Command/SendResetPasswordEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Sylius\Bundle\ApiBundle\Command;

class SendResetPasswordEmail
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
{
/** @var string */
public $email;

/** @var string */
private $channelCode;
arti0090 marked this conversation as resolved.
Show resolved Hide resolved

/** @var string */
private $localeCode;
arti0090 marked this conversation as resolved.
Show resolved Hide resolved

public function __construct(
string $email,
string $channelCode,
string $localeCode
) {
$this->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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Sylius\Bundle\ApiBundle\CommandHandler;

use Sylius\Bundle\ApiBundle\Command\ResetPassword;
use Sylius\Bundle\ApiBundle\Event\ResetPasswordRequested;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Sylius\Component\User\Security\Generator\GeneratorInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
use Webmozart\Assert\Assert;

class ResetPasswordHandler
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
{
/** @var UserRepositoryInterface */
private $userRepository;

/** @var MessageBusInterface */
private $eventBus;

/** @var GeneratorInterface */
private $generator;

public function __construct(
UserRepositoryInterface $userRepository,
MessageBusInterface $eventBus,
GeneratorInterface $generator
) {
$this->userRepository = $userRepository;
$this->eventBus = $eventBus;
$this->generator = $generator;
}

public function __invoke(ResetPassword $command): void
{
$user = $this->userRepository->findOneByEmail($command->getEmail());
Assert::notNull($user);

$user->setPasswordResetToken($this->generator->generate());
$user->setPasswordRequestedAt(new \DateTime());

$this->eventBus->dispatch(
new ResetPasswordRequested($command->getEmail(), $command->getChannelCode(), $command->getLocaleCode()),
[new DispatchAfterCurrentBusStamp()
]);
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
}
}