Skip to content

Commit 98cdea0

Browse files
committed
LYNX-361: Implemented GraphQL email confirmation
1 parent 6225e64 commit 98cdea0

File tree

3 files changed

+242
-0
lines changed

3 files changed

+242
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\CustomerGraphQl\Model\Resolver;
18+
19+
use Magento\Customer\Api\AccountManagementInterface;
20+
use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData;
21+
use Magento\Framework\Exception\State\InputMismatchException;
22+
use Magento\Framework\Exception\State\InvalidTransitionException;
23+
use Magento\Framework\GraphQl\Config\Element\Field;
24+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
25+
use Magento\Framework\GraphQl\Query\Resolver\Value;
26+
use Magento\Framework\GraphQl\Query\ResolverInterface;
27+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
28+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
29+
30+
/**
31+
* Customer email confirmation, used for GraphQL request processing
32+
*/
33+
class ConfirmEmail implements ResolverInterface
34+
{
35+
/**
36+
* @param AccountManagementInterface $accountManagement
37+
* @param EmailValidator $emailValidator
38+
* @param ExtractCustomerData $extractCustomerData
39+
*/
40+
public function __construct(
41+
private readonly AccountManagementInterface $accountManagement,
42+
private readonly EmailValidator $emailValidator,
43+
private readonly ExtractCustomerData $extractCustomerData
44+
) {
45+
}
46+
47+
/**
48+
* Confirm customer email mutation
49+
*
50+
* @param \Magento\Framework\GraphQl\Config\Element\Field $field
51+
* @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $context
52+
* @param ResolveInfo $info
53+
* @param array|null $value
54+
* @param array|null $args
55+
* @return array|Value
56+
* @throws \Exception
57+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
58+
*/
59+
public function resolve(
60+
Field $field,
61+
$context,
62+
ResolveInfo $info,
63+
array $value = null,
64+
array $args = null
65+
) {
66+
if (!$this->emailValidator->isValid($args['input']['email'])) {
67+
throw new GraphQlInputException(__('Email is invalid'));
68+
}
69+
70+
try {
71+
$customer = $this->accountManagement->activate($args['input']['email'], $args['input']['confirmation_key']);
72+
} catch (InvalidTransitionException | InputMismatchException $e) {
73+
throw new GraphQlInputException(__($e->getMessage()));
74+
}
75+
76+
return ['customer' => $this->extractCustomerData->execute($customer)];
77+
}
78+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ type Mutation {
2929
requestPasswordResetEmail(email: String! @doc(description: "The customer's email address.")): 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.")
3030
resetPassword(email: String! @doc(description: "The customer's email address."), resetPasswordToken: String! @doc(description: "A runtime token generated by the `requestPasswordResetEmail` mutation."), newPassword: String! @doc(description: "The customer's new password.")): 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`.")
3131
updateCustomerEmail(email: String! @doc(description: "The customer's email address."), password: String! @doc(description: "The customer's password.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerEmail") @doc(description: "Change the email address for the logged-in customer.")
32+
confirmEmail(input: ConfirmEmailInput! @doc(description: "An input object to identify the customer to confirm the email.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ConfirmEmail") @doc(description: "Confirms the email address for a customer.")
33+
}
34+
35+
input ConfirmEmailInput @doc(description: "Contains details about a customer email address to confirm.") {
36+
email: String! @doc(description: "The email address to be confirmed.")
37+
confirmation_key: String! @doc(description: "The key to confirm the email address.")
3238
}
3339

3440
input CustomerAddressInput @doc(description: "Contains details about a billing or shipping address."){
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\GraphQl\Customer;
18+
19+
use Magento\Customer\Test\Fixture\Customer as CustomerFixture;
20+
use Magento\Framework\Exception\AuthenticationException;
21+
use Magento\Integration\Api\CustomerTokenServiceInterface;
22+
use Magento\TestFramework\Fixture\DataFixture;
23+
use Magento\TestFramework\Helper\Bootstrap;
24+
use Magento\TestFramework\TestCase\GraphQlAbstract;
25+
26+
/**
27+
* Tests for confirm customer email
28+
*/
29+
#[
30+
DataFixture(
31+
CustomerFixture::class,
32+
[
33+
'email' => 'customer@example.com',
34+
'confirmation' => 'abcde',
35+
],
36+
'customer'
37+
)
38+
]
39+
class ConfirmEmailTest extends GraphQlAbstract
40+
{
41+
private const QUERY = <<<QUERY
42+
mutation {
43+
confirmEmail(input: {
44+
email: "%s"
45+
confirmation_key: "%s"
46+
}) {
47+
customer {
48+
email
49+
}
50+
}
51+
}
52+
QUERY;
53+
54+
/**
55+
* @var string
56+
*/
57+
private const PASSWORD = 'password';
58+
59+
/**
60+
* @return void
61+
* @throws AuthenticationException
62+
*/
63+
public function testConfirmEmail()
64+
{
65+
$response = $this->graphQlMutation(
66+
sprintf(
67+
self::QUERY,
68+
'customer@example.com',
69+
'abcde',
70+
),
71+
[],
72+
'',
73+
$this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD)
74+
);
75+
76+
$this->assertEquals(
77+
[
78+
'confirmEmail' => [
79+
'customer' => [
80+
'email' => 'customer@example.com'
81+
]
82+
]
83+
],
84+
$response
85+
);
86+
87+
$this->expectException(\Exception::class);
88+
$this->expectExceptionMessage('The account is already active.');
89+
90+
$this->graphQlMutation(
91+
sprintf(
92+
self::QUERY,
93+
'customer@example.com',
94+
'abcde',
95+
),
96+
[],
97+
'',
98+
$this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD)
99+
);
100+
}
101+
102+
/**
103+
* @return void
104+
* @throws AuthenticationException
105+
*/
106+
public function testConfirmEmailWrongEmail()
107+
{
108+
$this->expectException(\Exception::class);
109+
110+
$this->graphQlMutation(
111+
sprintf(
112+
self::QUERY,
113+
'bad-email',
114+
'abcde',
115+
),
116+
[],
117+
'',
118+
$this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD)
119+
);
120+
}
121+
122+
/**
123+
* @return void
124+
* @throws AuthenticationException
125+
*/
126+
public function testConfirmEmailWrongConfirmation()
127+
{
128+
$this->expectException(\Exception::class);
129+
130+
$this->graphQlMutation(
131+
sprintf(
132+
self::QUERY,
133+
'customer@example.com',
134+
'wrong-confirmation',
135+
),
136+
[],
137+
'',
138+
$this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD)
139+
);
140+
}
141+
142+
/**
143+
* @param string $email
144+
* @param string $password
145+
* @return array
146+
* @throws AuthenticationException
147+
*/
148+
private function getCustomerAuthHeaders(string $email, string $password): array
149+
{
150+
$customerToken = Bootstrap::getObjectManager()->get(
151+
CustomerTokenServiceInterface::class
152+
)->createCustomerAccessToken(
153+
$email,
154+
$password
155+
);
156+
return ['Authorization' => 'Bearer ' . $customerToken];
157+
}
158+
}

0 commit comments

Comments
 (0)