feat(app): telegram init data signature validator
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Telegram;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class TelegramInvalidSignatureException extends RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user