Skip to content

Commit aa01211

Browse files
authored
ENGCOM-7473: Feature 259 Add GraphQL mutations for Reset password for MyAccount #27876
2 parents f2fd3e5 + 14db001 commit aa01211

File tree

7 files changed

+672
-0
lines changed

7 files changed

+672
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Resolver;
9+
10+
use Magento\Customer\Api\AccountManagementInterface;
11+
use Magento\Customer\Api\CustomerRepositoryInterface;
12+
use Magento\Customer\Model\AccountManagement;
13+
use Magento\Customer\Model\AuthenticationInterface;
14+
use Magento\Framework\Exception\LocalizedException;
15+
use Magento\Framework\GraphQl\Config\Element\Field;
16+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
17+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
18+
use Magento\Framework\GraphQl\Query\Resolver\Value;
19+
use Magento\Framework\GraphQl\Query\ResolverInterface;
20+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
21+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
22+
23+
/**
24+
* Class Resolver for RequestPasswordResetEmail
25+
*/
26+
class RequestPasswordResetEmail implements ResolverInterface
27+
{
28+
/**
29+
* @var AuthenticationInterface
30+
*/
31+
private $authentication;
32+
33+
/**
34+
* @var CustomerRepositoryInterface
35+
*/
36+
private $customerRepository;
37+
38+
/**
39+
* @var AccountManagementInterface
40+
*/
41+
private $customerAccountManagement;
42+
43+
/**
44+
* @var EmailValidator
45+
*/
46+
private $emailValidator;
47+
48+
/**
49+
* RequestPasswordResetEmail constructor.
50+
*
51+
* @param AuthenticationInterface $authentication
52+
* @param CustomerRepositoryInterface $customerRepository
53+
* @param AccountManagementInterface $customerAccountManagement
54+
* @param EmailValidator $emailValidator
55+
*/
56+
public function __construct(
57+
AuthenticationInterface $authentication,
58+
CustomerRepositoryInterface $customerRepository,
59+
AccountManagementInterface $customerAccountManagement,
60+
EmailValidator $emailValidator
61+
) {
62+
$this->authentication = $authentication;
63+
$this->customerRepository = $customerRepository;
64+
$this->customerAccountManagement = $customerAccountManagement;
65+
$this->emailValidator = $emailValidator;
66+
}
67+
68+
/**
69+
* Send password email request
70+
*
71+
* @param Field $field
72+
* @param ContextInterface $context
73+
* @param ResolveInfo $info
74+
* @param array|null $value
75+
* @param array|null $args
76+
*
77+
* @return bool|Value|mixed
78+
*
79+
* @throws GraphQlInputException
80+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
81+
*/
82+
public function resolve(
83+
Field $field,
84+
$context,
85+
ResolveInfo $info,
86+
array $value = null,
87+
array $args = null
88+
) {
89+
if (empty($args['email'])) {
90+
throw new GraphQlInputException(__('You must specify an email address.'));
91+
}
92+
93+
if (!$this->emailValidator->isValid($args['email'])) {
94+
throw new GraphQlInputException(__('The email address has an invalid format.'));
95+
}
96+
97+
try {
98+
$customer = $this->customerRepository->get($args['email']);
99+
} catch (LocalizedException $e) {
100+
throw new GraphQlInputException(__('Cannot reset the customer\'s password'), $e);
101+
}
102+
103+
if (true === $this->authentication->isLocked($customer->getId())) {
104+
throw new GraphQlInputException(__('The account is locked'));
105+
}
106+
107+
try {
108+
return $this->customerAccountManagement->initiatePasswordReset(
109+
$args['email'],
110+
AccountManagement::EMAIL_RESET
111+
);
112+
} catch (LocalizedException $e) {
113+
throw new GraphQlInputException(__('Cannot reset the customer\'s password'), $e);
114+
}
115+
}
116+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Resolver;
9+
10+
use Magento\Customer\Api\AccountManagementInterface;
11+
use Magento\Customer\Api\CustomerRepositoryInterface;
12+
use Magento\Customer\Model\AuthenticationInterface;
13+
use Magento\Framework\Exception\LocalizedException;
14+
use Magento\Framework\GraphQl\Config\Element\Field;
15+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
16+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
17+
use Magento\Framework\GraphQl\Query\Resolver\Value;
18+
use Magento\Framework\GraphQl\Query\ResolverInterface;
19+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
20+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
21+
22+
/**
23+
* Class Resolver for ResetPassword
24+
*/
25+
class ResetPassword implements ResolverInterface
26+
{
27+
/**
28+
* @var AccountManagementInterface
29+
*/
30+
private $customerAccountManagement;
31+
32+
/**
33+
* @var EmailValidator
34+
*/
35+
private $emailValidator;
36+
37+
/**
38+
* @var AuthenticationInterface
39+
*/
40+
private $authentication;
41+
42+
/**
43+
* @var CustomerRepositoryInterface
44+
*/
45+
private $customerRepository;
46+
47+
/**
48+
* ResetPassword constructor.
49+
*
50+
* @param AuthenticationInterface $authentication
51+
* @param CustomerRepositoryInterface $customerRepository
52+
* @param AccountManagementInterface $customerAccountManagement
53+
* @param EmailValidator $emailValidator
54+
*/
55+
public function __construct(
56+
AuthenticationInterface $authentication,
57+
CustomerRepositoryInterface $customerRepository,
58+
AccountManagementInterface $customerAccountManagement,
59+
EmailValidator $emailValidator
60+
) {
61+
$this->authentication = $authentication;
62+
$this->customerRepository = $customerRepository;
63+
$this->customerAccountManagement = $customerAccountManagement;
64+
$this->emailValidator = $emailValidator;
65+
}
66+
67+
/**
68+
* Reset old password and set new
69+
*
70+
* @param Field $field
71+
* @param ContextInterface $context
72+
* @param ResolveInfo $info
73+
* @param array|null $value
74+
* @param array|null $args
75+
*
76+
* @return bool|Value|mixed
77+
*
78+
* @throws GraphQlInputException
79+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
80+
*/
81+
public function resolve(
82+
Field $field,
83+
$context,
84+
ResolveInfo $info,
85+
array $value = null,
86+
array $args = null
87+
) {
88+
if (empty($args['email'])) {
89+
throw new GraphQlInputException(__('You must specify an email address.'));
90+
}
91+
92+
if (!$this->emailValidator->isValid($args['email'])) {
93+
throw new GraphQlInputException(__('The email address has an invalid format.'));
94+
}
95+
96+
if (empty($args['resetPasswordToken'])) {
97+
throw new GraphQlInputException(__('resetPasswordToken must be specified'));
98+
}
99+
100+
if (empty($args['newPassword'])) {
101+
throw new GraphQlInputException(__('newPassword must be specified'));
102+
}
103+
104+
try {
105+
$customer = $this->customerRepository->get($args['email']);
106+
} catch (LocalizedException $e) {
107+
throw new GraphQlInputException(__('Cannot set the customer\'s password'), $e);
108+
}
109+
110+
if (true === $this->authentication->isLocked($customer->getId())) {
111+
throw new GraphQlInputException(__('The account is locked'));
112+
}
113+
114+
try {
115+
return $this->customerAccountManagement->resetPassword(
116+
$args['email'],
117+
$args['resetPasswordToken'],
118+
$args['newPassword']
119+
);
120+
} catch (LocalizedException $e) {
121+
throw new GraphQlInputException(__('Cannot set the customer\'s password'), $e);
122+
}
123+
}
124+
}

app/code/Magento/CustomerGraphQl/etc/graphql/di.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,13 @@
2020
</argument>
2121
</arguments>
2222
</type>
23+
<type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider">
24+
<arguments>
25+
<argument name="extendedConfigData" xsi:type="array">
26+
<item name="required_character_classes_number" xsi:type="string">customer/password/required_character_classes_number</item>
27+
<item name="minimum_password_length" xsi:type="string">customer/password/minimum_password_length</item>
28+
<item name="autocomplete_on_storefront" xsi:type="string">customer/password/autocomplete_on_storefront</item>
29+
</argument>
30+
</arguments>
31+
</type>
2332
</config>

app/code/Magento/CustomerGraphQl/etc/schema.graphqls

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Copyright © Magento, Inc. All rights reserved.
22
# See COPYING.txt for license details.
33

4+
type StoreConfig {
5+
required_character_classes_number : String @doc(description: "The number of different character classes required in a password (lowercase, uppercase, digits, special characters).")
6+
minimum_password_length : String @doc(description: "The minimum number of characters required for a valid password.")
7+
autocomplete_on_storefront : Boolean @doc(description: "Enable autocomplete on login and forgot password forms")
8+
}
9+
410
type Query {
511
customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account") @cache(cacheable: false)
612
isEmailAvailable (
@@ -17,6 +23,8 @@ type Mutation {
1723
createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create customer address")
1824
updateCustomerAddress(id: Int!, input: CustomerAddressInput): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update customer address")
1925
deleteCustomerAddress(id: Int!): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddress") @doc(description: "Delete customer address")
26+
requestPasswordResetEmail(email: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RequestPasswordResetEmail") @doc(description: "Request an email with a reset password token for the registered customer identified by the specified email.")
27+
resetPassword(email: String!, resetPasswordToken: String!, newPassword: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResetPassword") @doc(description: "Reset a customer's password using the reset password token that the customer received in an email after requesting it using requestPasswordResetEmail.")
2028
}
2129

2230
input CustomerAddressInput {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Customer;
9+
10+
use Magento\TestFramework\Helper\Bootstrap;
11+
use Magento\TestFramework\TestCase\GraphQlAbstract;
12+
13+
class RequestPasswordResetEmailTest extends GraphQlAbstract
14+
{
15+
/**
16+
* @var LockCustomer
17+
*/
18+
private $lockCustomer;
19+
20+
protected function setUp(): void
21+
{
22+
parent::setUp();
23+
24+
$this->lockCustomer = Bootstrap::getObjectManager()->get(LockCustomer::class);
25+
}
26+
/**
27+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
28+
*/
29+
public function testCustomerAccountWithEmailAvailable()
30+
{
31+
$query =
32+
<<<QUERY
33+
mutation {
34+
requestPasswordResetEmail(email: "[email protected]")
35+
}
36+
QUERY;
37+
$response = $this->graphQlMutation($query);
38+
39+
self::assertArrayHasKey('requestPasswordResetEmail', $response);
40+
self::assertTrue($response['requestPasswordResetEmail']);
41+
}
42+
43+
/**
44+
* Check if customer account is not available
45+
*
46+
* @expectedException \Exception
47+
* @expectedExceptionMessage Cannot reset the customer's password
48+
*/
49+
public function testCustomerAccountWithEmailNotAvailable()
50+
{
51+
$query =
52+
<<<QUERY
53+
mutation {
54+
requestPasswordResetEmail(email: "[email protected]")
55+
}
56+
QUERY;
57+
$this->graphQlMutation($query);
58+
}
59+
60+
/**
61+
* Check if email value empty
62+
*
63+
* @expectedException \Exception
64+
* @expectedExceptionMessage You must specify an email address.
65+
*/
66+
public function testEmailAvailableEmptyValue()
67+
{
68+
$query = <<<QUERY
69+
mutation {
70+
requestPasswordResetEmail(email: "")
71+
}
72+
QUERY;
73+
$this->graphQlMutation($query);
74+
}
75+
76+
/**
77+
* Check if email is invalid
78+
*
79+
* @expectedException \Exception
80+
* @expectedExceptionMessage The email address has an invalid format.
81+
*/
82+
public function testEmailAvailableInvalidValue()
83+
{
84+
$query = <<<QUERY
85+
mutation {
86+
requestPasswordResetEmail(email: "invalid-email")
87+
}
88+
QUERY;
89+
$this->graphQlMutation($query);
90+
}
91+
92+
/**
93+
* Check if email was sent for lock customer
94+
*
95+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
96+
*
97+
* @expectedException \Exception
98+
* @expectedExceptionMessage The account is locked
99+
*/
100+
public function testRequestPasswordResetEmailForLockCustomer()
101+
{
102+
$this->lockCustomer->execute(1);
103+
$query =
104+
<<<QUERY
105+
mutation {
106+
requestPasswordResetEmail(email: "[email protected]")
107+
}
108+
QUERY;
109+
110+
$this->graphQlMutation($query);
111+
}
112+
}

0 commit comments

Comments
 (0)