Skip to content

Commit b941d4a

Browse files
Merge pull request #6 from dmitrijs-voronovs/account-confrmation-feature
Email confirmation
2 parents d21e2b7 + aa348a2 commit b941d4a

9 files changed

+351
-20
lines changed

README.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,18 @@ mutation ResetPassword(
5454

5555
### Mutation `confirmCustomerEmail`
5656

57-
> ⚠️ This is not tested but implemented !!! ⚠️
58-
5957
Here is an example use of it:
6058

6159
```graphql
6260
mutation ConfirmCustomerEmail(
6361
$password: String!
6462
$key: String!
65-
$id: String!
63+
$email: String!
6664
) {
6765
confirmCustomerEmail(
6866
password: $password
6967
key: $key
70-
id: $id
68+
email: $email
7169
) {
7270
status
7371
token
@@ -99,7 +97,7 @@ The variables for input above might look like:
9997
```json
10098
{
10199
"key": "0129309912",
102-
"id": "0000000000",
100+
"email": "[email protected]",
103101
"password": "Testing123_"
104102
}
105103
```
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
/**
3+
* ScandiPWA - Progressive Web App for Magento
4+
*
5+
* Copyright © Scandiweb, Inc. All rights reserved.
6+
* See LICENSE for license details.
7+
*
8+
* @license OSL-3.0 (Open Software License ("OSL") v. 3.0)
9+
* @package scandipwa/module-customer-graph-ql
10+
* @link https://github.com/scandipwa/module-customer-graph-ql
11+
*/
12+
declare(strict_types=1);
13+
14+
namespace ScandiPWA\CustomerGraphQl\Model\Customer;
15+
16+
use Magento\Customer\Api\Data\CustomerInterface;
17+
use Magento\Customer\Model\AccountConfirmation;
18+
use Magento\Framework\App\Config\ScopeConfigInterface;
19+
use Magento\Framework\Exception\LocalizedException;
20+
use Magento\Framework\Serialize\SerializerInterface;
21+
use Magento\Framework\Webapi\ServiceOutputProcessor;
22+
use Magento\Store\Model\ScopeInterface;
23+
24+
/**
25+
* Transform single customer data from object to in array format
26+
*/
27+
class ExtractCustomerData extends \Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData
28+
{
29+
/**
30+
* @var ScopeConfigInterface
31+
*/
32+
private $scopeConfig;
33+
34+
/**
35+
* @var ServiceOutputProcessor
36+
*/
37+
private $serviceOutputProcessor;
38+
39+
/**
40+
* @var SerializerInterface
41+
*/
42+
private $serializer;
43+
44+
/**
45+
* @param ServiceOutputProcessor $serviceOutputProcessor
46+
* @param ScopeConfigInterface $scopeConfig
47+
* @param SerializerInterface $serializer
48+
*/
49+
public function __construct(
50+
ServiceOutputProcessor $serviceOutputProcessor,
51+
ScopeConfigInterface $scopeConfig,
52+
SerializerInterface $serializer
53+
) {
54+
parent::__construct(
55+
$serviceOutputProcessor,
56+
$serializer
57+
);
58+
$this->scopeConfig = $scopeConfig;
59+
}
60+
61+
/**
62+
* Transform single customer data from object to in array format
63+
*
64+
* @param CustomerInterface $customer
65+
* @return array
66+
* @throws LocalizedException
67+
*/
68+
public function execute(CustomerInterface $customer): array
69+
{
70+
$customerData = parent::execute($customer);
71+
72+
$isConfirmationRequired = $this->scopeConfig->isSetFlag(
73+
AccountConfirmation::XML_PATH_IS_CONFIRM,
74+
ScopeInterface::SCOPE_WEBSITES,
75+
$customer->getStoreId()
76+
);
77+
78+
$customerData['confirmation_required'] = $isConfirmationRequired;
79+
80+
return $customerData;
81+
}
82+
}

src/Model/Customer/GetCustomer.php

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
/**
3+
* ScandiPWA - Progressive Web App for Magento
4+
*
5+
* Copyright © Scandiweb, Inc. All rights reserved.
6+
* See LICENSE for license details.
7+
*
8+
* @license OSL-3.0 (Open Software License ("OSL") v. 3.0)
9+
* @package scandipwa/module-customer-graph-ql
10+
* @link https://github.com/scandipwa/module-customer-graph-ql
11+
*/
12+
declare(strict_types=1);
13+
14+
namespace ScandiPWA\CustomerGraphQl\Model\Customer;
15+
16+
use Magento\Authorization\Model\UserContextInterface;
17+
use Magento\Customer\Api\AccountManagementInterface;
18+
use Magento\Customer\Api\CustomerRepositoryInterface;
19+
use Magento\Customer\Api\Data\CustomerInterface;
20+
use Magento\Customer\Model\AuthenticationInterface;
21+
use Magento\Framework\Exception\LocalizedException;
22+
use Magento\Framework\Exception\NoSuchEntityException;
23+
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
24+
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
25+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
26+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
27+
use Magento\GraphQl\Model\Query\ContextInterface;
28+
29+
/**
30+
* Get customer
31+
*/
32+
class GetCustomer extends \Magento\CustomerGraphQl\Model\Customer\GetCustomer
33+
{
34+
/**
35+
* @var AuthenticationInterface
36+
*/
37+
private $authentication;
38+
39+
/**
40+
* @var CustomerRepositoryInterface
41+
*/
42+
private $customerRepository;
43+
44+
/**
45+
* @var AccountManagementInterface
46+
*/
47+
private $accountManagement;
48+
49+
/**
50+
* @param AuthenticationInterface $authentication
51+
* @param CustomerRepositoryInterface $customerRepository
52+
* @param AccountManagementInterface $accountManagement
53+
*/
54+
public function __construct(
55+
AuthenticationInterface $authentication,
56+
CustomerRepositoryInterface $customerRepository,
57+
AccountManagementInterface $accountManagement
58+
) {
59+
$this->authentication = $authentication;
60+
$this->customerRepository = $customerRepository;
61+
$this->accountManagement = $accountManagement;
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function execute(ContextInterface $context): CustomerInterface
68+
{
69+
$currentUserId = $context->getUserId();
70+
$currentUserType = $context->getUserType();
71+
72+
if ($this->isUserGuest($currentUserId, $currentUserType) === true) {
73+
throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.'));
74+
}
75+
76+
try {
77+
$customer = $this->customerRepository->getById($currentUserId);
78+
} catch (NoSuchEntityException $e) {
79+
throw new GraphQlNoSuchEntityException(
80+
__('Customer with id "%customer_id" does not exist.', ['customer_id' => $currentUserId]),
81+
$e
82+
);
83+
} catch (LocalizedException $e) {
84+
throw new GraphQlInputException(__($e->getMessage()));
85+
}
86+
87+
if (true === $this->authentication->isLocked($currentUserId)) {
88+
throw new GraphQlAuthenticationException(__('The account is locked.'));
89+
}
90+
91+
return $customer;
92+
}
93+
94+
/**
95+
* Checking if current customer is guest
96+
*
97+
* @param int|null $customerId
98+
* @param int|null $customerType
99+
* @return bool
100+
*/
101+
protected function isUserGuest(?int $customerId, ?int $customerType): bool
102+
{
103+
if (null === $customerId || null === $customerType) {
104+
return true;
105+
}
106+
return 0 === (int)$customerId || (int)$customerType === UserContextInterface::USER_TYPE_GUEST;
107+
}
108+
}

src/Model/Resolver/ConfirmEmail.php

+45-14
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,18 @@
2222
use Magento\Customer\Api\AccountManagementInterface;
2323
use Magento\Customer\Api\CustomerRepositoryInterface;
2424
use Magento\Integration\Api\CustomerTokenServiceInterface;
25+
use Magento\Framework\Encryption\EncryptorInterface as Encryptor;
26+
use Magento\Customer\Model\AuthenticationInterface;
27+
use Magento\Customer\Model\CustomerRegistry;
2528

2629
class ConfirmEmail implements ResolverInterface {
2730
const STATUS_TOKEN_EXPIRED = 'token_expired';
2831

32+
/**
33+
* @var AuthenticationInterface
34+
*/
35+
private $authentication;
36+
2937
/**
3038
* @var Session
3139
*/
@@ -46,24 +54,42 @@ class ConfirmEmail implements ResolverInterface {
4654
*/
4755
protected $customerTokenService;
4856

57+
/**
58+
* @var Encryptor
59+
*/
60+
protected $encryptor;
61+
62+
/**
63+
* @var CustomerRegistry
64+
*/
65+
protected $customerRegistry;
66+
4967
/**
5068
* ConfirmEmail constructor.
69+
* @param AuthenticationInterface $authentication
5170
* @param Session $customerSession
5271
* @param AccountManagementInterface $customerAccountManagement
5372
* @param CustomerRepositoryInterface $customerRepository
5473
* @param CustomerTokenServiceInterface $customerTokenService
55-
* @param CustomerDataProvider $customerDataProvider
74+
* @param Encryptor $encryptor
75+
* @param CustomerRegistry $customerRegistry
5676
*/
5777
public function __construct(
78+
AuthenticationInterface $authentication,
5879
Session $customerSession,
5980
AccountManagementInterface $customerAccountManagement,
6081
CustomerRepositoryInterface $customerRepository,
61-
CustomerTokenServiceInterface $customerTokenService
82+
CustomerTokenServiceInterface $customerTokenService,
83+
Encryptor $encryptor,
84+
CustomerRegistry $customerRegistry
6285
) {
86+
$this->authentication = $authentication;
6387
$this->customerTokenService = $customerTokenService;
6488
$this->session = $customerSession;
6589
$this->customerAccountManagement = $customerAccountManagement;
6690
$this->customerRepository = $customerRepository;
91+
$this->encryptor = $encryptor;
92+
$this->customerRegistry = $customerRegistry;
6793
}
6894

6995
/**
@@ -78,26 +104,31 @@ public function resolve(
78104
)
79105
{
80106
if ($this->session->isLoggedIn()) {
81-
return [ 'status' => AccountManagementInterface::ACCOUNT_CONFIRMATION_NOT_REQUIRED ];
107+
$this->session->logOut();
82108
}
83109

84110
try {
85-
$customerId = $args['id'];
111+
$customerEmail = $args['email'];
86112
$key = $args['key'];
87113
$password = $args['password'];
88114

89-
$customerEmail = $this->customerRepository->getById($customerId)->getEmail();
90-
$customer = $this->customerAccountManagement->activate($customerEmail, $key);
91-
$this->session->setCustomerDataAsLoggedIn($customer);
92-
$token = $this->customerTokenService->createCustomerAccessToken($customer->getEmail(), $password);
115+
$id = $this->customerRepository->get($customerEmail)->getId();
116+
$currentPasswordHash = $this->customerRegistry->retrieveSecureData($id)->getPasswordHash();
117+
118+
if ($this->encryptor->validateHash($password, $currentPasswordHash)) {
119+
$customer = $this->customerAccountManagement->activate($customerEmail, $key);
93120

94-
return [
95-
'customer' => $this->customerRepository->getById((int)$customer->getId()),
96-
'status' => AccountManagementInterface::ACCOUNT_CONFIRMED,
97-
'token' => $token
98-
];
121+
$this->session->setCustomerDataAsLoggedIn($customer);
122+
$token = $this->customerTokenService->createCustomerAccessToken($customerEmail, $password);
123+
return [
124+
'status' => AccountManagementInterface::ACCOUNT_CONFIRMED,
125+
'token' => $token
126+
];
127+
} else {
128+
throw new GraphQlInputException(__('Password is incorrect'));
129+
}
99130
} catch (StateException $e) {
100-
return [ 'status' => self::STATUS_TOKEN_EXPIRED ];
131+
return ['status' => self::STATUS_TOKEN_EXPIRED];
101132
} catch (\Exception $e) {
102133
throw new GraphQlInputException(__('There was an error confirming the account'), $e);
103134
}

src/etc/di.xml

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
*/
1111
-->
1212
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
13+
<preference for="Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData"
14+
type="ScandiPWA\CustomerGraphQl\Model\Customer\ExtractCustomerData"/>
15+
<preference for="Magento\CustomerGraphQl\Model\Customer\GetCustomer"
16+
type="ScandiPWA\CustomerGraphQl\Model\Customer\GetCustomer"/>
1317
<preference for="Magento\CustomerGraphQl\Model\Resolver\GenerateCustomerToken"
1418
type="ScandiPWA\CustomerGraphQl\Model\Resolver\GenerateCustomerToken"/>
1519
</config>

src/etc/email_templates.xml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* ScandiPWA - Progressive Web App for Magento
5+
*
6+
* Copyright © Scandiweb, Inc. All rights reserved.
7+
* See LICENSE for license details.
8+
*
9+
* @license OSL-3.0 (Open Software License ("OSL") v. 3.0)
10+
* @package scandipwa/module-customer-graph-ql
11+
* @link https://github.com/scandipwa/module-customer-graph-ql
12+
*/
13+
-->
14+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Email:etc/email_templates.xsd">
15+
<template id="customer_create_account_email_confirmation_template" label="New Account Confirmation Key" file="account_new_confirmation.html" type="html" module="ScandiPWA_CustomerGraphQl" area="frontend"/>
16+
<template id="customer_create_account_email_confirmed_template" label="New Account Confirmed" file="account_new_confirmed.html" type="html" module="ScandiPWA_CustomerGraphQl" area="frontend"/>
17+
</config>

src/etc/schema.graphqls

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
# See COPYING.txt for license details.
1313

1414
type Mutation {
15+
confirmCustomerEmail(key: String!, email: String!, password: String!): CreateCustomerType @resolver(class: "\\ScandiPWA\\CustomerGraphQl\\Model\\Resolver\\ConfirmEmail") @doc(description:"Confirm customer account")
1516
generateCustomerToken(email: String!, password: String!, guest_quote_id: String): CustomerToken @resolver(class: "\\ScandiPWA\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Retrieve the customer token")
16-
confirmCustomerEmail(key: String!, id: String!, password: String!): CreateCustomerType @resolver(class: "\\ScandiPWA\\CustomerGraphQl\\Model\\Resolver\\ConfirmEmail") @doc(description:"Confirm customer account")
1717
resendConfirmationEmail(email: String!): CustomerActionConfirmationType @resolver(class: "\\ScandiPWA\\CustomerGraphQl\\Model\\Resolver\\ResendConfirmationEmail") @doc(description:"Resend customer confirmation")
1818
forgotPassword(email: String!): CustomerActionConfirmationType @resolver(class: "\\ScandiPWA\\CustomerGraphQl\\Model\\Resolver\\ForgotPassword") @doc(description:"Resend customer confirmation")
1919
resetPassword(password: String!, token: String!, password_confirmation: String!): ResetPasswordType @resolver(class: "\\ScandiPWA\\CustomerGraphQl\\Model\\Resolver\\ResetPassword") @doc(description:"Resend customer confirmation")
@@ -37,3 +37,7 @@ type CreateCustomerType {
3737
type AvailabilityResponseType {
3838
isAvailable: Boolean
3939
}
40+
41+
type Customer @doc(description: "Customer defines the customer name and address and other details") {
42+
confirmation_required: Boolean @doc(description: "Email confirmation is required")
43+
}

0 commit comments

Comments
 (0)