1

I have created one graphql extension for customer OTP send and verify for login with OTP in the project's POS app.

There are two requests

  • When POS app request OTP with customer_id and pos session_id, it sends OTP to the customer.
  • When POS app requests verify OTP with customer_id, OTP, and session_id, it checks the sent OTP with the requested OTP if is correct it returns success.

SmsGraphQl/Model/Resolver/CustomerOtpSend.php

public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ) {
        if (!isset($args['customer_id']) || !isset($args['session_id'])) {
            throw new GraphQlInputException(__("Please provide 'customer_id' and 'session_id' first!"));
        }
        $sessionId = $args['session_id'];
        if (!$this->data->validateStaffSession($sessionId)){
            return [
                'success' => false,
                'message' => __("Invalid 'session_id'!")
            ];
        }
        try {
            $customerId = $args['customer_id'];
            $customer = $this->customerRegistry->retrieve($customerId);
            $success = true;
            $message = "OTP send successfully!";
            $this->seeSmsHelper->sendAuthentication($customer);
        } catch (Exception $e) {
            $success = false;
            $message = $e->getMessage();
        }

        return [
            'success' => $success,
            'message' => $message
        ];
    }

SmsGraphQl/Model/Resolver/CustomerOtpVerify.php

public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ) {
        if (!isset($args['customer_id']) || !isset($args['otp']) || !isset($args['session_id'])) {
            throw new GraphQlInputException(__("Please provide 'customer_id', 'session_id' and 'otp' first!"));
        }

        $customerId = $args['customer_id'];
        $sessionId = $args['session_id'];
        $otp = $args['otp'];

        if (!$this->_data->validateStaffSession($sessionId)){
            return [
                'success' => false,
                'message' => __("Invalid 'session_id'!")
            ];
        }

        $collection = $this->_mobileCollection->addFieldToFilter('customer_id', $customerId)
            ->addFieldToFilter('status', Mobile::STATUS_NOT_VERIFIED)
            ->addFieldToFilter('otp', $otp);

        $mobile = $collection->getFirstItem();
        if ($collection->count() == 0) {
            return [
                'success' => false,
                'message' => ConstantsInterface::OTP_INVALID_MSG
            ];
        }

        $mobile->setStatus(Mobile::STATUS_VERIFIED)->save();

        return [
            'success' => true,
            'message' => __("valid OTP.")
        ];
    }

Requirement

POS is already sent session_id in the header. So in place of the use parameter, we need to use the header variable.

If anyone has used the header variable in Magento 2 graphql then help or provide documentation, how to do it?

Thanks in advance.

3 Answers 3

1

I've looked into the core codes but didn't find a Magento way to get the request headers in graphql resolver yet. I will update the answer once I'll have more information. Meanwhile, you can use the below way.

You can get all the request headers using the below function.

$getHeaders = apache_request_headers();
$sessionId = $getHeaders['Session-Id'];

But it also has some limitations, as I checked apache_request_headers() function does not return a header which is having underscore in its key. Instead of underscore, you can use the hyphen.

key   : session-id
value : custom

Thanks!

1

We found a way to validate the header with graphql. We got reference from vendor/magento/module-store-graph-ql, vendor/magento/module-wishlist-graph-ql and vendor/magento/module-customer-graph-ql.

WebposGraphQl/etc/extension_attributes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\GraphQl\Model\Query\ContextInterface">
        <attribute code="pos_session" type="string"/>
    </extension_attributes>
</config>

WebposGraphQl/etc/graphql/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\GraphQl\Controller\HttpRequestProcessor">
        <arguments>
            <argument name="requestValidators" xsi:type="array">
                <item name="posSessionIdValidator" xsi:type="object">*\WebposGraphQl\Controller\HttpRequestValidator\PosSessionValidator</item>
            </argument>
        </arguments>
    </type>
    <type name="Magento\GraphQl\Model\Query\ContextFactory">
        <arguments>
            <argument name="contextParametersProcessors" xsi:type="array">
                <item name="add_pos_session_id_to_context" xsi:type="object">*\WebposGraphQl\Model\Context\AddPosSessionIdToContext</item>
            </argument>
        </arguments>
    </type>
</config>

WebposGraphQl/Model/Context/AddPosSessionIdToContext.php

<?php
namespace *\WebposGraphQl\Model\Context;

use Magento\GraphQl\Model\Query\ContextParametersInterface;
use Magento\GraphQl\Model\Query\ContextParametersProcessorInterface;
use Magento\Framework\App\Http\Context;
use *\WebposGraphQl\Api\Data\ConstantInterface;

/**
 * @inheritdoc
 */
class AddPosSessionIdToContext implements ContextParametersProcessorInterface
{
    /**
     * @var Context
     */
    public $context;

    /**
     * AddPosSessionIdToContext constructor.
     *
     * @param Context $context
     */
    public function __construct(
        Context $context
    ) {
        $this->context = $context;
    }

    /**
     * @inheritdoc
     */
    public function execute(ContextParametersInterface $contextParameters): ContextParametersInterface
    {
        $contextParameters->addExtensionAttribute(
            ConstantInterface::POS_SESSION,
            $this->context->getData(ConstantInterface::POS_SESSION_HEADER)
        );
        return $contextParameters;
    }
}

WebposGraphQl/Controller/HttpRequestValidator/PosSessionValidator.php

<?php
namespace *\WebposGraphQl\Controller\HttpRequestValidator;

use Magento\Framework\App\Http\Context as HttpContext;
use Magento\Framework\App\HttpRequestInterface;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\GraphQl\Controller\HttpRequestValidatorInterface;
use *\Webpos\Helper\Data;
use *\WebposGraphQl\Api\Data\ConstantInterface;

class PosSessionValidator implements HttpRequestValidatorInterface
{
    /**
     * @var Data
     */
    protected $webposHelper;

    /**
     * @var HttpContext
     */
    private $httpContext;

    /**
     * PosSessionValidator constructor.
     *
     * @param Data $webposHelper
     * @param HttpContext $httpContext
     */
    public function __construct(
        Data $webposHelper,
        HttpContext $httpContext
    ) {
        $this->webposHelper = $webposHelper;
        $this->httpContext = $httpContext;
    }

    /**
     * Validate the header 'pos-session' value.
     *
     * @param HttpRequestInterface $request
     * @return void
     * @throws GraphQlInputException
     */
    public function validate(HttpRequestInterface $request): void
    {
        $headerValue = $request->getHeader(ConstantInterface::POS_SESSION_HEADER);

        if (empty($headerValue)) {
            $this->httpContext->setValue(
                ConstantInterface::POS_SESSION_HEADER,
                null,
                0
            );
        } else {
            $posSessionId = trim($headerValue);
            if ($this->isSessionIdExist($posSessionId)) {
                $this->httpContext->setValue(
                    ConstantInterface::POS_SESSION_HEADER,
                    $posSessionId,
                    1
                );
            } else {
                $this->httpContext->setValue(
                    ConstantInterface::POS_SESSION_HEADER,
                    null,
                    0
                );
                throw new GraphQlInputException(__('Requested pos session id %1 is not found', $posSessionId));
            }

        }
    }

    /**
     * Validate session id.
     *
     * @param string $posSessionId
     * @return bool
     */
    private function isSessionIdExist(string $posSessionId): bool
    {
        return $this->webposHelper->validateStaffSession($posSessionId);
    }
}

SmsGraphQl/etc/schema.graphqls

type Query {
    sendCustomerOtp (
        customer_id: Int @doc(description: "Customer id.")
    ): SendCustomerOtpOutput @resolver(class: "*\\SmsGraphql\\Model\\Resolver\\CustomerOtpSend")
    @doc(description: "Send the otp to requested customer id.")
    verifyCustomerOtp (
        customer_id: Int @doc(description: "Customer id.")
        otp: String @doc(description: "OTP provided by customer.")
    ): VerifyCustomerOtpOutput @resolver(class: "*\\SmsGraphql\\Model\\Resolver\\CustomerOtpVerify")
    @doc(description: "Verify otp provided by customer.")
}

type SendCustomerOtpOutput @doc(description: "Response the send status.") {
    success: Boolean @doc(description: "Success status.")
    message: String @doc(description: "Send status message.")
}

type VerifyCustomerOtpOutput @doc(description: "Response the verify status.") {
    success: Boolean @doc(description: "Success status.")
    message: String @doc(description: "Send status message.")
}

SmsGraphQl/Model/Resolver/CustomerOtpVerify.php

<?php
namespace *\SmsGraphql\Model\Resolver;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Vnecoms\Sms\Helper\Data;
use Vnecoms\Sms\Model\ResourceModel\Mobile\Collection;
use Vnecoms\Sms\Model\Mobile;
use *\Sms\Api\ConstantsInterface;
use *\WebposGraphQl\Api\Data\ConstantInterface;

class CustomerOtpVerify implements ResolverInterface
{
    /**
     * @var Collection
     */
    protected $_mobileCollection;

    /**
     * @var Data
     */
    protected $_vnecomsHelper;

    /**
     * @var DateTime
     */
    protected $_date;

    /**
     * CustomerOtpVerify constructor.
     *
     * @param Collection $mobileCollection
     * @param Data $vnecomsHelper
     * @param DateTime $date
     */
    public function __construct(
        Collection $mobileCollection,
        Data $vnecomsHelper,
        DateTime $date
    ) {
        $this->_date = $date;
        $this->_mobileCollection = $mobileCollection;
        $this->_vnecomsHelper = $vnecomsHelper;
    }

    /**
     * Resolve query.
     *
     * @param Field $field
     * @param ContextInterface $context
     * @param ResolveInfo $info
     * @param array|null $value
     * @param array|null $args
     * @return array
     * @throws GraphQlInputException
     */
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ) {
        $posSession = $context->getExtensionAttributes()->getPosSession();
        if (isset($posSession[ConstantInterface::POS_SESSION_HEADER])) {
            if ((bool)$posSession[ConstantInterface::POS_SESSION_HEADER]) {
                if (!isset($args['customer_id']) || !isset($args['otp'])) {
                    throw new GraphQlInputException(__("Please provide 'customer_id' and 'otp' first!"));
                }

                $customerId = $args['customer_id'];
                $otp = $args['otp'];
                $collection = $this->_mobileCollection->addFieldToFilter('customer_id', $customerId)
                    ->addFieldToFilter('status', Mobile::STATUS_NOT_VERIFIED)
                    ->addFieldToFilter('otp', $otp);

                $mobile = $collection->getFirstItem();
                $otpExpireTime = $this->_vnecomsHelper->getOtpExpiredPeriodTime();
                if ((strtotime($mobile->getOtpCreatedAt()) + $otpExpireTime) < $this->_date->timestamp()) {
                    return [
                        'success' => false,
                        'message' => ConstantsInterface::OTP_EXPIRED_MSG
                    ];
                }

                if ($collection->count() == 0) {
                    return [
                        'success' => false,
                        'message' => ConstantsInterface::OTP_INVALID_MSG
                    ];
                }

                $mobile->setStatus(Mobile::STATUS_VERIFIED)->save();
                return [
                    'success' => true,
                    'message' => __("valid OTP.")
                ];
            } else {
                throw new GraphQlAuthorizationException(__('The current POS Session is not authorized.'));
            }
        } else {
            throw new GraphQlAuthorizationException(__('Can not find pos session id in header!'));
        }
    }
}

Thank you.

0

As i correct understand your question, you can initialize in __constructor request \Magento\Framework\App\RequestInterface and use getHeader('key', 'default') method

2
  • Sorry, Can't get the header from the request object. Jun 30, 2022 at 5:43
  • indirect Magento uses a request object and passes a specific header value to the resolver. Jul 4, 2022 at 6:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.