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'),
|
'theme_dark' => $this->config->get('module_tgshop_theme_dark'),
|
||||||
'mainpage_products' => $this->config->get('module_tgshop_mainpage_products'),
|
'mainpage_products' => $this->config->get('module_tgshop_mainpage_products'),
|
||||||
'featured_products' => $this->config->get('module_tgshop_featured_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' => [
|
'telegram' => [
|
||||||
'bot_token' => $this->config->get('module_tgshop_bot_token'),
|
'bot_token' => $this->config->get('module_tgshop_bot_token'),
|
||||||
'chat_id' => $this->config->get('module_tgshop_chat_id'),
|
'chat_id' => $this->config->get('module_tgshop_chat_id'),
|
||||||
@@ -103,7 +103,8 @@ class Controllerextensiontgshophandle extends Controller
|
|||||||
$app->bootAndHandleRequest();
|
$app->bootAndHandleRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPureJs($input) {
|
function extractPureJs($input)
|
||||||
|
{
|
||||||
// Убираем <noscript>...</noscript>
|
// Убираем <noscript>...</noscript>
|
||||||
$input = preg_replace('#<noscript>.*?</noscript>#is', '', $input);
|
$input = preg_replace('#<noscript>.*?</noscript>#is', '', $input);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class Application extends Container
|
|||||||
private static Application $instance;
|
private static Application $instance;
|
||||||
private static array $events = [];
|
private static array $events = [];
|
||||||
private array $serviceProviders = [];
|
private array $serviceProviders = [];
|
||||||
|
private array $middlewareStack = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ExecutionTimeProfiler
|
* @var ExecutionTimeProfiler
|
||||||
@@ -91,9 +92,16 @@ class Application extends Container
|
|||||||
throw new InvalidArgumentException('Invalid action: ' . $controller . '->' . $method);
|
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.');
|
$this->profiler->addCheckpoint('Handle HTTP request.');
|
||||||
|
|
||||||
@@ -114,4 +122,11 @@ class Application extends Container
|
|||||||
|
|
||||||
return $this;
|
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;
|
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;
|
namespace Openguru\OpenCartFramework\Telegram;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use Openguru\OpenCartFramework\Logger\Logger;
|
use Openguru\OpenCartFramework\Logger\Logger;
|
||||||
|
|
||||||
@@ -10,9 +9,9 @@ class TelegramService
|
|||||||
{
|
{
|
||||||
private Logger $logger;
|
private Logger $logger;
|
||||||
private Client $client;
|
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->logger = $logger;
|
||||||
$this->botToken = $botToken;
|
$this->botToken = $botToken;
|
||||||
|
|||||||
@@ -13,8 +13,14 @@ class TelegramServiceProvider extends ServiceProvider
|
|||||||
$this->container->singleton(TelegramService::class, function (Application $app) {
|
$this->container->singleton(TelegramService::class, function (Application $app) {
|
||||||
$botToken = $app->getConfigValue('telegram.bot_token');
|
$botToken = $app->getConfigValue('telegram.bot_token');
|
||||||
return new TelegramService(
|
return new TelegramService(
|
||||||
$botToken,
|
|
||||||
$app->get(Logger::class),
|
$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\Router\RouteServiceProvider;
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
|
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Telegram\TelegramValidateInitDataMiddleware;
|
||||||
|
|
||||||
class ApplicationFactory
|
class ApplicationFactory
|
||||||
{
|
{
|
||||||
@@ -23,6 +24,9 @@ class ApplicationFactory
|
|||||||
RouteServiceProvider::class,
|
RouteServiceProvider::class,
|
||||||
AppServiceProvider::class,
|
AppServiceProvider::class,
|
||||||
TelegramServiceProvider::class,
|
TelegramServiceProvider::class,
|
||||||
|
])
|
||||||
|
->withMiddlewares([
|
||||||
|
TelegramValidateInitDataMiddleware::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,15 @@ use App\Services\CartService;
|
|||||||
use Cart\Cart;
|
use Cart\Cart;
|
||||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
use Openguru\OpenCartFramework\Http\Request;
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
|
||||||
|
|
||||||
class CartHandler
|
class CartHandler
|
||||||
{
|
{
|
||||||
private Cart $cart;
|
private Cart $cart;
|
||||||
private ImageToolInterface $imageTool;
|
|
||||||
private CartService $cartService;
|
private CartService $cartService;
|
||||||
|
|
||||||
public function __construct(Cart $cart, ImageToolInterface $imageTool, CartService $cartService)
|
public function __construct(Cart $cart, CartService $cartService)
|
||||||
{
|
{
|
||||||
$this->cart = $cart;
|
$this->cart = $cart;
|
||||||
$this->imageTool = $imageTool;
|
|
||||||
$this->cartService = $cartService;
|
$this->cartService = $cartService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ const BASE_URL = '/';
|
|||||||
export const apiFetch = ofetch.create({
|
export const apiFetch = ofetch.create({
|
||||||
throwHttpErrors: true,
|
throwHttpErrors: true,
|
||||||
onRequest({request, options}) {
|
onRequest({request, options}) {
|
||||||
const initData = window.Telegram?.WebApp?.initData
|
const data = window.Telegram?.WebApp?.initData;
|
||||||
|
|
||||||
if (initData) {
|
if (data) {
|
||||||
options.headers = {
|
options.headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
'X-Telegram-InitData': initData,
|
'X-Telegram-InitData': data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user