From 9dcf3616ff30676f1e5643440899df2b00caae78 Mon Sep 17 00:00:00 2001 From: KristofersOzolinsMagebit Date: Tue, 14 Oct 2025 10:59:05 +0300 Subject: [PATCH 1/2] feat: bearer token validation --- Api/ConfigInterface.php | 7 +++++++ Model/Config.php | 16 ++++++++++++++++ Service/ComplianceService.php | 23 ++++++++++++++++++++++- etc/adminhtml/system.xml | 18 ++++++++++++------ etc/config.xml | 2 ++ 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/Api/ConfigInterface.php b/Api/ConfigInterface.php index c70d0fd..caa45d2 100644 --- a/Api/ConfigInterface.php +++ b/Api/ConfigInterface.php @@ -22,6 +22,7 @@ interface ConfigInterface public const CONFIG_WEBHOOK_URL = 'agentic_commerce/agentic_checkout/webhook_url'; public const CONFIG_WEBHOOK_SECRET = 'agentic_commerce/agentic_checkout/webhook_secret'; public const CONFIG_WEBHOOKS_ENABLED = 'agentic_commerce/agentic_checkout/enable_webhooks'; + public const CONFIG_API_TOKEN = 'agentic_commerce/agentic_checkout/api_token'; public const CONFIG_ORDER_STATUS_MAP = 'agentic_commerce/agentic_checkout/order_status_map'; public const CONFIG_GTIN_SOURCE = 'agentic_commerce/product_feed/gtin_source'; @@ -124,6 +125,12 @@ public function getIdempotencyTtl(?int $storeId = null): int; */ public function getIsWebhooksEnabled(?int $storeId = null): bool; + /** + * @param int|null $storeId + * @return string + */ + public function getApiToken(?int $storeId = null): string; + /** * Get order status mapping configuration * diff --git a/Model/Config.php b/Model/Config.php index b7eaa97..3a96dd6 100644 --- a/Model/Config.php +++ b/Model/Config.php @@ -269,6 +269,22 @@ public function getIsWebhooksEnabled(?int $storeId = null): bool return $this->scopeConfig->isSetFlag(ConfigInterface::CONFIG_WEBHOOKS_ENABLED, ScopeInterface::SCOPE_STORE, $storeId); } + /** + * @param int|null $storeId + * @return string + */ + public function getApiToken(?int $storeId = null): string + { + /** @var string|null $token */ + $token = $this->scopeConfig->getValue( + ConfigInterface::CONFIG_API_TOKEN, + ScopeInterface::SCOPE_STORE, + $storeId + ); + + return $token ?? ''; + } + /** * Get order status mapping configuration * diff --git a/Service/ComplianceService.php b/Service/ComplianceService.php index aada4ef..1f21b95 100644 --- a/Service/ComplianceService.php +++ b/Service/ComplianceService.php @@ -19,6 +19,7 @@ use Magebit\AgenticCommerce\Api\Data\Response\ErrorResponseInterfaceFactory; use Magebit\AgenticCommerce\Model\Idempotency\Management as IdempotencyManagement; use Magento\Framework\Encryption\EncryptorInterface; +use Magebit\AgenticCommerce\Api\ConfigInterface; class ComplianceService { @@ -29,12 +30,14 @@ class ComplianceService * @param IdempotencyManagement $idempotencyManagement * @param JsonFactory $resultJsonFactory * @param EncryptorInterface $encryptor + * @param ConfigInterface $config */ public function __construct( protected readonly ErrorResponseInterfaceFactory $errorResponseFactory, protected readonly IdempotencyManagement $idempotencyManagement, protected readonly JsonFactory $resultJsonFactory, - protected readonly EncryptorInterface $encryptor + protected readonly EncryptorInterface $encryptor, + protected readonly ConfigInterface $config ) { } @@ -47,6 +50,16 @@ public function validateApiVersion(Http $request): bool return $request->getHeader('API-Version') === self::API_VERSION; } + /** + * @param Http $request + * @return bool + */ + public function validateApiToken(Http $request): bool + { + $apiToken = $this->config->getApiToken(); + return $request->getHeader('Authorization') === 'Bearer ' . $apiToken; + } + /** * @param Http $request * @return null|ErrorResponseInterface @@ -61,6 +74,14 @@ public function validateRequest(Http $request): ?ErrorResponseInterface ]]); } + if (!$this->validateApiToken($request)) { + return $this->errorResponseFactory->create(['data' => [ + 'type' => ErrorResponseInterface::TYPE_INVALID_REQUEST, + 'code' => 'invalid_api_token', + 'message' => 'Invalid API token', + ]]); + } + if ($idempotencyError = $this->validateIdempotency($request)) { return $idempotencyError; } diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 566cf26..74dd46e 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -37,21 +37,26 @@ Base path to use for the router. Default is `checkout_sessions` required-entry - + + + Leave empty to skip Bearer token validation + Magento\Config\Model\Config\Backend\Encrypted + + required-entry - + List of links (e.g. ToS/privacy policy/etc.) to be displayed to the customer. List of links (e.g. ToS/privacy policy/etc.) to be displayed to the customer Magebit\AgenticCommerce\Block\Adminhtml\Form\Field\SessionLinks Magento\Config\Model\Config\Backend\Serialized\ArraySerialized - + Magento\Config\Model\Config\Source\Yesno - + URL to send webhooks to required-entry @@ -59,14 +64,15 @@ 1 - + Secret to use for the webhook + Magento\Config\Model\Config\Backend\Encrypted 1 - + Map Magento order statuses to Agentic Commerce order statuses for webhooks Magebit\AgenticCommerce\Block\Adminhtml\Form\Field\OrderStatusMap diff --git a/etc/config.xml b/etc/config.xml index d2abf7d..6474a13 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -19,6 +19,8 @@ checkout_sessions checkout/onepage/success 0 + + pending From d5837c926623c7a60cc010fb697c5b2aeb72d31e Mon Sep 17 00:00:00 2001 From: KristofersOzolinsMagebit Date: Tue, 14 Oct 2025 11:07:00 +0300 Subject: [PATCH 2/2] fix: return correct status code --- Api/ConfigInterface.php | 4 ++-- Controller/ApiController.php | 8 +++++++- Model/Config.php | 6 +++--- Service/ComplianceService.php | 6 ++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Api/ConfigInterface.php b/Api/ConfigInterface.php index caa45d2..e6cde16 100644 --- a/Api/ConfigInterface.php +++ b/Api/ConfigInterface.php @@ -127,9 +127,9 @@ public function getIsWebhooksEnabled(?int $storeId = null): bool; /** * @param int|null $storeId - * @return string + * @return string|null */ - public function getApiToken(?int $storeId = null): string; + public function getApiToken(?int $storeId = null): ?string; /** * Get order status mapping configuration diff --git a/Controller/ApiController.php b/Controller/ApiController.php index e338aaf..0d95bb5 100644 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -83,8 +83,14 @@ protected function createRequestObjectAndValidate(callable $factory): mixed public function makeErrorResponse(ErrorResponseInterface $errorResponse, int $statusCode = 400): ResultJson { /** @var ErrorResponse $errorResponse */ + if ($errorResponse->getData('_statusCode')) { + // @phpstan-ignore cast.int + $statusCode = (int) $errorResponse->getData('_statusCode'); + $errorResponse->unsetData('_statusCode'); + } + /** @var array $data */ - $data = $errorResponse->getData(); + $data = $errorResponse->toArray(); return $this->makeJsonResponse($data, $statusCode); } diff --git a/Model/Config.php b/Model/Config.php index 3a96dd6..e5946cc 100644 --- a/Model/Config.php +++ b/Model/Config.php @@ -271,9 +271,9 @@ public function getIsWebhooksEnabled(?int $storeId = null): bool /** * @param int|null $storeId - * @return string + * @return string|null */ - public function getApiToken(?int $storeId = null): string + public function getApiToken(?int $storeId = null): ?string { /** @var string|null $token */ $token = $this->scopeConfig->getValue( @@ -282,7 +282,7 @@ public function getApiToken(?int $storeId = null): string $storeId ); - return $token ?? ''; + return $token ?: null; } /** diff --git a/Service/ComplianceService.php b/Service/ComplianceService.php index 1f21b95..66eeba2 100644 --- a/Service/ComplianceService.php +++ b/Service/ComplianceService.php @@ -57,6 +57,11 @@ public function validateApiVersion(Http $request): bool public function validateApiToken(Http $request): bool { $apiToken = $this->config->getApiToken(); + + if (!$apiToken) { + return true; + } + return $request->getHeader('Authorization') === 'Bearer ' . $apiToken; } @@ -79,6 +84,7 @@ public function validateRequest(Http $request): ?ErrorResponseInterface 'type' => ErrorResponseInterface::TYPE_INVALID_REQUEST, 'code' => 'invalid_api_token', 'message' => 'Invalid API token', + '_statusCode' => 401, ]]); }