feat(app): telegram init data signature validator

This commit is contained in:
Nikita Kiselev
2025-08-03 11:16:01 +03:00
parent 14d42c6ecb
commit 350ec4f64b
12 changed files with 159 additions and 28 deletions

View File

@@ -48,7 +48,7 @@ class Controllerextensiontgshophandle extends Controller
'theme_dark' => $this->config->get('module_tgshop_theme_dark'),
'mainpage_products' => $this->config->get('module_tgshop_mainpage_products'),
'featured_products' => $this->config->get('module_tgshop_featured_products'),
'ya_metrika_enabled' => !empty(trim($this->config->get('module_tgshop_yandex_metrika'))),
'ya_metrika_enabled' => ! empty(trim($this->config->get('module_tgshop_yandex_metrika'))),
'telegram' => [
'bot_token' => $this->config->get('module_tgshop_bot_token'),
'chat_id' => $this->config->get('module_tgshop_chat_id'),
@@ -103,7 +103,8 @@ class Controllerextensiontgshophandle extends Controller
$app->bootAndHandleRequest();
}
function extractPureJs($input) {
function extractPureJs($input)
{
// Убираем <noscript>...</noscript>
$input = preg_replace('#<noscript>.*?</noscript>#is', '', $input);

View File

@@ -17,6 +17,7 @@ class Application extends Container
private static Application $instance;
private static array $events = [];
private array $serviceProviders = [];
private array $middlewareStack = [];
/**
* @var ExecutionTimeProfiler
@@ -91,9 +92,16 @@ class Application extends Container
throw new InvalidArgumentException('Invalid action: ' . $controller . '->' . $method);
}
$this->profiler->addCheckpoint('Handle Router.');
$this->profiler->addCheckpoint('Handle Middlewares.');
$response = $this->call($controller, $method);
$next = fn($req) => $this->call($controller, $method);
foreach (array_reverse($this->middlewareStack) as $class) {
$instance = $this->get($class);
$next = static fn($req) => $instance->handle($req, $next);
}
$response = $next($request);
$this->profiler->addCheckpoint('Handle HTTP request.');
@@ -114,4 +122,11 @@ class Application extends Container
return $this;
}
public function withMiddlewares(array $middlewares): Application
{
$this->middlewareStack = $middlewares;
return $this;
}
}

View File

@@ -80,4 +80,14 @@ class Request
{
return $_SERVER['HTTP_USER_AGENT'] ?? null;
}
public function header(string $name): ?string
{
$headers = [];
foreach (getallheaders() as $key => $value) {
$headers[mb_strtolower($key)] = trim($value);
}
return $headers[mb_strtolower($name)] ?? null;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Openguru\OpenCartFramework\Telegram;
use Openguru\OpenCartFramework\Http\Request;
class SignatureValidator
{
private ?string $botToken;
public function __construct(?string $botToken = null)
{
$this->botToken = $botToken;
}
public function validate(Request $request): void
{
if (! $this->botToken) {
return;
}
$initDataString = rawurldecode($request->header('X-Telegram-Initdata'));
if (! $initDataString) {
throw new TelegramInvalidSignatureException('Invalid Telegram signature!');
}
$data = $this->parseInitDataStringToArray($initDataString);
if (!isset($data['hash'])) {
throw new TelegramInvalidSignatureException('Missing hash in init data');
}
$checkString = $this->getCheckString($data);
$algorithm = 'sha256';
$secretKey = hash_hmac($algorithm, $this->botToken, 'WebAppData', true);
$calculatedHash = bin2hex(hash_hmac($algorithm, $checkString, $secretKey, true));
if (! hash_equals($calculatedHash, $data['hash'])) {
throw new TelegramInvalidSignatureException('Invalid Telegram signature!');
}
}
private function parseInitDataStringToArray(string $initData): array
{
parse_str($initData, $parsed);
foreach ($parsed as $key => $value) {
if ($this->isValidJson($value)) {
$parsed[$key] = json_decode(urldecode($value), true);
}
}
return $parsed;
}
private function isValidJson(string $jsonString): bool
{
json_decode($jsonString);
return (json_last_error() === JSON_ERROR_NONE);
}
private function getCheckString(array $data): string
{
unset($data['hash']);
ksort($data);
$array = [];
foreach ($data as $key => $value) {
if (is_array($value)) {
$array[] = $key . '=' . json_encode($value, JSON_UNESCAPED_UNICODE);
} else {
$array[] = $key . '=' . $value;
}
}
return implode(PHP_EOL, $array);
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Openguru\OpenCartFramework\Telegram;
use RuntimeException;
class TelegramInvalidSignatureException extends RuntimeException
{
}

View File

@@ -2,7 +2,6 @@
namespace Openguru\OpenCartFramework\Telegram;
use Exception;
use GuzzleHttp\Client;
use Openguru\OpenCartFramework\Logger\Logger;
@@ -10,9 +9,9 @@ class TelegramService
{
private Logger $logger;
private Client $client;
private string $botToken;
private ?string $botToken;
public function __construct(string $botToken, Logger $logger)
public function __construct(Logger $logger, ?string $botToken = null)
{
$this->logger = $logger;
$this->botToken = $botToken;

View File

@@ -13,8 +13,14 @@ class TelegramServiceProvider extends ServiceProvider
$this->container->singleton(TelegramService::class, function (Application $app) {
$botToken = $app->getConfigValue('telegram.bot_token');
return new TelegramService(
$botToken,
$app->get(Logger::class),
$botToken,
);
});
$this->container->singleton(SignatureValidator::class, function (Application $app) {
return new SignatureValidator(
$app->getConfigValue('telegram.bot_token'),
);
});
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Openguru\OpenCartFramework\Telegram;
use Closure;
class TelegramValidateInitDataMiddleware
{
private SignatureValidator $signatureValidator;
public function __construct(SignatureValidator $signatureValidator)
{
$this->signatureValidator = $signatureValidator;
}
public function handle($request, Closure $next)
{
$this->signatureValidator->validate($request);
return $next($request);
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Openguru\OpenCartFramework\Telegram;
use Openguru\OpenCartFramework\Http\Request;
class ValidateInitDataMiddleware
{
public function handle(Request $request, callable $next): void
{
$next($request);
}
}

View File

@@ -9,6 +9,7 @@ use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
use Openguru\OpenCartFramework\Router\RouteServiceProvider;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
use Openguru\OpenCartFramework\Telegram\TelegramValidateInitDataMiddleware;
class ApplicationFactory
{
@@ -23,6 +24,9 @@ class ApplicationFactory
RouteServiceProvider::class,
AppServiceProvider::class,
TelegramServiceProvider::class,
])
->withMiddlewares([
TelegramValidateInitDataMiddleware::class,
]);
}
}

View File

@@ -6,18 +6,15 @@ use App\Services\CartService;
use Cart\Cart;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
class CartHandler
{
private Cart $cart;
private ImageToolInterface $imageTool;
private CartService $cartService;
public function __construct(Cart $cart, ImageToolInterface $imageTool, CartService $cartService)
public function __construct(Cart $cart, CartService $cartService)
{
$this->cart = $cart;
$this->imageTool = $imageTool;
$this->cartService = $cartService;
}