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

Add email validation to RequestResetPassword #621

Merged
merged 27 commits into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b8f300d
Add Validation on RequestResetPassword
Amr3zzat Jan 10, 2020
58fb774
Add Tests
Amr3zzat Jan 10, 2020
6bcc97e
Fix Email Validation
Amr3zzat Jan 10, 2020
b761ad9
Handle email not exists RequestResetPassword
Amr3zzat Jan 10, 2020
019cb00
Add Test email doesn't exist RequestResetPassword
Amr3zzat Jan 10, 2020
a5711dd
Add docs email doesn't exist RequestResetPassword
Amr3zzat Jan 10, 2020
3d320f8
Remove user not found from specs
Amr3zzat Jan 10, 2020
caaeeca
Codestyle
mamazu Jan 11, 2020
133f48c
Documenting changes in passwort reset endpoint
mamazu Jan 11, 2020
113a980
Fix Typo
Amr3zzat Jan 11, 2020
fefbc30
Make Logic clearer ResetPassword
Amr3zzat Jan 11, 2020
e19b3a3
Add Specs ResetPassword
Amr3zzat Jan 11, 2020
7667198
Fix SendResetPassword Specs
Amr3zzat Jan 16, 2020
3117dc8
Fix Composer
Amr3zzat Jan 17, 2020
2a79e43
Add ValidationViewFactory last argument
Amr3zzat Jan 18, 2020
7e1c7e6
Make Handlers throw exceptions
Amr3zzat Jan 18, 2020
d73537b
Add Specs
Amr3zzat Jan 18, 2020
5036738
Remove Unused classes
Amr3zzat Jan 18, 2020
7fca470
Create UserNotFound Exception
Amr3zzat Jan 21, 2020
f4abe07
Update src/Controller/Customer/RequestPasswordResettingAction.php
Amr3zzat Jan 22, 2020
a35aec7
Update src/Controller/Customer/RequestPasswordResettingAction.php
Amr3zzat Jan 22, 2020
97e8ed2
Update src/Exception/UserNotFoundException.php
Amr3zzat Jan 22, 2020
bd3ba00
Update src/Exception/UserNotFoundException.php
Amr3zzat Jan 22, 2020
0ed1096
Update src/Exception/UserNotFoundException.php
Amr3zzat Jan 22, 2020
cd17826
Update Specs
Amr3zzat Jan 22, 2020
1c09aa6
Code Style
Amr3zzat Jan 22, 2020
5ac484e
Fix tests
Amr3zzat Jan 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ access_control:
- { path: "%sylius_shop_api.security.regex%/me", role: ROLE_USER}

```
* The forgot passwort function now doesn't return an error if the user with this email is not found. It might give a potential attacker information about which users are registered in the shop.

# UPGRADE FROM 1.0.0-rc.2 or 1.0.0-rc.3 to 1.0.0

Expand Down
6 changes: 3 additions & 3 deletions doc/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -709,9 +709,9 @@ paths:
$ref: "#/definitions/RequestPasswordResetting"
responses:
204:
description: "Reset password request has been sent."
500:
description: "User with provided email has not been found."
description: "Reset password request has been sent If the email exists in our system,."
400:
description: "Email not valid"

/password-reset/{token}:
parameters:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,4 @@ function it_handles_generating_user_verification_token(

$this(new GenerateResetPasswordToken('example@customer.com'));
}

function it_throws_an_exception_if_user_has_not_been_found(
UserRepositoryInterface $userRepository
): void {
$userRepository->findOneByEmail('example@customer.com')->willReturn(null);

$this->shouldThrow(\InvalidArgumentException::class)->during('__invoke', [new GenerateResetPasswordToken('example@customer.com')]);
}
}
8 changes: 0 additions & 8 deletions spec/Handler/Customer/SendResetPasswordTokenHandlerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,6 @@ function it_handles_emailing_user_with_verification_email(
$this(new SendResetPasswordToken('example@customer.com', 'WEB_GB'));
}

function it_throws_an_exception_if_user_has_not_been_found(
mamazu marked this conversation as resolved.
Show resolved Hide resolved
UserRepositoryInterface $userRepository
): void {
$userRepository->findOneByEmail('example@customer.com')->willReturn(null);

$this->shouldThrow(\InvalidArgumentException::class)->during('__invoke', [new SendResetPasswordToken('example@customer.com', 'WEB_GB')]);
}

function it_throws_an_exception_if_user_has_not_verification_token(
UserRepositoryInterface $userRepository,
ShopUserInterface $user
Expand Down
14 changes: 13 additions & 1 deletion src/Controller/Customer/RequestPasswordResettingAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\ShopApiPlugin\CommandProvider\ChannelBasedCommandProviderInterface;
use Sylius\ShopApiPlugin\CommandProvider\CommandProviderInterface;
use Sylius\ShopApiPlugin\Factory\ValidationErrorViewFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
Expand All @@ -22,6 +23,9 @@ final class RequestPasswordResettingAction
/** @var MessageBusInterface */
private $bus;

/** @var ValidationErrorViewFactoryInterface */
private $validationErrorViewFactory;

/** @var ChannelContextInterface */
private $channelContext;

Expand All @@ -34,12 +38,14 @@ final class RequestPasswordResettingAction
public function __construct(
ViewHandlerInterface $viewHandler,
MessageBusInterface $bus,
ValidationErrorViewFactoryInterface $validationErrorViewFactory,
Amr3zzat marked this conversation as resolved.
Show resolved Hide resolved
ChannelContextInterface $channelContext,
CommandProviderInterface $generateResetPasswordTokenCommandProvider,
ChannelBasedCommandProviderInterface $sendResetPasswordTokenCommandProvider
) {
$this->viewHandler = $viewHandler;
$this->bus = $bus;
$this->validationErrorViewFactory = $validationErrorViewFactory;
$this->channelContext = $channelContext;
$this->generateResetPasswordTokenCommandProvider = $generateResetPasswordTokenCommandProvider;
$this->sendResetPasswordTokenCommandProvider = $sendResetPasswordTokenCommandProvider;
Expand All @@ -49,7 +55,13 @@ public function __invoke(Request $request): Response
{
/** @var ChannelInterface $channel */
$channel = $this->channelContext->getChannel();

$validationResults = $this->generateResetPasswordTokenCommandProvider->validate($request);
if (0 !== count($validationResults)) {
return $this->viewHandler->handle(View::create(
$this->validationErrorViewFactory->create($validationResults),
Response::HTTP_BAD_REQUEST
));
}
$this->bus->dispatch($this->generateResetPasswordTokenCommandProvider->getCommand($request));
$this->bus->dispatch($this->sendResetPasswordTokenCommandProvider->getCommand($request, $channel));

Expand Down
10 changes: 4 additions & 6 deletions src/Handler/Customer/GenerateResetPasswordTokenHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Sylius\Component\User\Security\Generator\GeneratorInterface;
use Sylius\ShopApiPlugin\Command\Customer\GenerateResetPasswordToken;
use Webmozart\Assert\Assert;

final class GenerateResetPasswordTokenHandler
{
Expand All @@ -30,10 +29,9 @@ public function __invoke(GenerateResetPasswordToken $generateResetPasswordToken)

/** @var ShopUserInterface $user */
Amr3zzat marked this conversation as resolved.
Show resolved Hide resolved
$user = $this->userRepository->findOneByEmail($email);

Assert::notNull($user, sprintf('User with %s email has not been found.', $email));
Amr3zzat marked this conversation as resolved.
Show resolved Hide resolved

$user->setPasswordResetToken($this->tokenGenerator->generate());
$user->setPasswordRequestedAt(new \DateTime());
if (null !== $user) {
$user->setPasswordResetToken($this->tokenGenerator->generate());
$user->setPasswordRequestedAt(new \DateTime());
}
}
}
17 changes: 8 additions & 9 deletions src/Handler/Customer/SendResetPasswordTokenHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ public function __invoke(SendResetPasswordToken $resendResetPasswordToken): void

/** @var ShopUserInterface $user */
$user = $this->userRepository->findOneByEmail($email);

Assert::notNull($user, sprintf('User with %s email has not been found.', $email));
Amr3zzat marked this conversation as resolved.
Show resolved Hide resolved
Assert::notNull($user->getPasswordResetToken(), sprintf('User with %s email has not verification token defined.', $email));

$this->sender->send(
Emails::EMAIL_RESET_PASSWORD_TOKEN,
[$email],
['user' => $user, 'channelCode' => $resendResetPasswordToken->channelCode()]
);
if (null !== $user) {
mamazu marked this conversation as resolved.
Show resolved Hide resolved
Assert::notNull($user->getPasswordResetToken(), sprintf('User with %s email has not verification token defined.', $email));
$this->sender->send(
Emails::EMAIL_RESET_PASSWORD_TOKEN,
[$email],
['user' => $user, 'channelCode' => $resendResetPasswordToken->channelCode()]
);
}
}
}
1 change: 1 addition & 0 deletions src/Resources/config/services/actions/customer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
>
<argument type="service" id="fos_rest.view_handler" />
<argument type="service" id="sylius_shop_api_plugin.command_bus" />
<argument type="service" id="sylius.shop_api_plugin.factory.validation_error_view_factory" />
<argument type="service" id="sylius.context.channel" />
<argument type="service" id="sylius.shop_api_plugin.command_provider.generate_reset_password_token" />
<argument type="service" id="sylius.shop_api_plugin.command_provider.send_reset_password_token" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\ShopApiPlugin\Request\Customer\GenerateResetPasswordTokenRequest">
<property name="email">
<constraint name="NotBlank">
<option name="message">sylius.shop_api.email.not_blank</option>
</constraint>
<constraint name="Email">
<option name="message">sylius.shop_api.email.invalid</option>
</constraint>
</property>
</class>
</constraint-mapping>
45 changes: 45 additions & 0 deletions tests/Controller/Customer/RequestPasswordResettingApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,51 @@ public function it_allows_to_reset_user_password(): void
$this->assertTrue($emailChecker->hasRecipient('oliver@queen.com'));
}

/**
* @test
*/
public function it_does_not_allow_to_reset_user_password_without_entering_valid_email(): void
{
$this->loadFixturesFromFiles(['channel.yml', 'customer.yml']);

$data = '{"email": "oliver"}';

$this->client->request('PUT', '/shop-api/request-password-reset', [], [], self::CONTENT_TYPE_HEADER, $data);

$response = $this->client->getResponse();
$this->assertResponse($response, 'customer/request_reset_password_failed_email', Response::HTTP_BAD_REQUEST);
}

/**
* @test
*/
public function it_does_not_allow_to_reset_user_password_without_entering_email(): void
{
$this->loadFixturesFromFiles(['channel.yml', 'customer.yml']);

$data = '{}';

$this->client->request('PUT', '/shop-api/request-password-reset', [], [], self::CONTENT_TYPE_HEADER, $data);

$response = $this->client->getResponse();
$this->assertResponse($response, 'customer/request_reset_password_empty_email', Response::HTTP_BAD_REQUEST);
}

/**
* @test
*/
public function it_allow_to_reset_user_password_without_sending_mail_user_not_exist(): void
{
$this->loadFixturesFromFiles(['channel.yml', 'customer.yml']);

$data = '{"email": "Amr@amr.com"}';

$this->client->request('PUT', '/shop-api/request-password-reset', [], [], self::CONTENT_TYPE_HEADER, $data);

$response = $this->client->getResponse();
$this->assertResponseCode($response, Response::HTTP_NO_CONTENT);
}

protected function getContainer(): ContainerInterface
{
return static::$sharedKernel->getContainer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"code": 400,
"message": "Validation failed",
"errors": {
"email": [
"Please enter your email."
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"code": 400,
"message": "Validation failed",
"errors": {
"email": [
"This email is invalid."
]
}
}