Comments 17
Отвечу кодом
…
private function signValid(array $sign): bool
$list = [];
ksort($sign);
foreach ($sign as $name => $val) {
if ($name == 'hash') {
continue;
}
$list[] = $name . '=' . (is_array($val) ? json_encode($val, JSON_UNESCAPED_UNICODE) : $val);
}
$secret_key = hash_hmac('sha256', $_ENV['TG_BOT_TOKEN'], "WebAppData", true);
$hash = bin2hex(hash_hmac('sha256', implode("\n", $list), $secret_key, true));
return strcmp($hash, $sign['hash']) === 0;
}
…
Спасибо, в вашем решении я не заметил фильтрацию ключей, возможно вы делаете это до.
Расчетный хэш на данный момент получается из параметров auth_date, query_id, signature, user (то что у вас должно остаться в массиве list). А буквально неделю назад поля signature не участвовало в этом расчете.
Благодарю за пример
нужно не брать конкретные поля, а исключить hash и signature
Я беру из window.Telegram.WebApp.initDataUnsafe
и игнорю hash
Не нужна фильтрация больше никакая. В итоге мне все равно что они там добавят.
Получается тебе можно подсунуть любые данные (устаревшие, чужие, несуществующие) и ты им доверяешь со всеми вытекающими
А для чего выше код проверки подписи!?!?private function signValid(array $sign)…
Извиняюсь, без контекста комментарий прочитал.
Это получается в unsafe они не добавляли подпись и все работает по старому? Я на initData ориентировался и пришлось переделывать на новый метод проверки.
initData надо проверять и initDataUnsafe надо проверять.
Вот разница из доки:
initData - A string with raw data transferred to the Mini App, convenient for validating data.WARNING: Validate data from this field before using it on the bot's server.
initDataUnsafe - An object with input data transferred to the Mini App.WARNING: Data from this field should not be trusted. You should only use data from initData on the bot's server and only after it has been validated.
PS всегда данные от клиента надо проверять. Правило 1: на стороне клиента сидит хакер.
Ты молодец, моя реакция на незащищенные данные была быстрее. Ещё раз извиняюсь.
p.s. глянь сообщения ниже веткой. Я напаролся тоже на эту проблему, но через какое-то время старый код работал как и должно было быть. Это моя ошибка или всё-таки телеграм что-то накосячил с hash?
Спасибо, сделаю так же
Пожалуйста, не используйте хаб Habr для статей не о Хабре.
/**
* @param array<string, mixed> $data
* @return AccessToken
*/
public function auth(array $data): AccessToken
{
if (Arr::has($data, 'signature')) {
$check_params = Arr::except($data, ['hash', 'signature']);
ksort($check_params);
foreach ($check_params as $param => $value) {
$check_params[$param] = "$param=$value";
}
$data_check_string = config('telegram.bot_id') . ":WebAppData\n";
$data_check_string .= implode("\n", $check_params);
try {
$public_key = sodium_hex2bin(config('telegram.public_key'));
$signature = base64_decode(strtr(Arr::get($data, 'signature'), '-_', '+/'));
if (sodium_crypto_sign_verify_detached($signature, $data_check_string, $public_key)) {
$auth_date = Arr::get($data, 'auth_date');
if (time() - $auth_date < 86400) {
$user = $this->firstOrCreate(
json_decode(Arr::get($data, 'user'), true),
);
}
}
} catch (SodiumException $e) {
//
}
}
return isset($user)
? $this->createAccessToken($user, md5(json_encode($data)))
: new AccessToken();
}
что вы собрались ждать в обновлении доки, если там все уже есть?
Здравствуйте, в доке есть две примера авторизации:
1. Validating data received via the Mini App - про который я и написал.
2. Validating data for Third-Party Use - собственно ваш пример.
Я написал про 1-ый вариант, и для этого варианта нет правильно работающего примера на данный момент в документации. Поправьте меня если ошибаюсь.
а два варианта то я и упустил))
сейчас решил проверить, вернул свой код на старую реализацию и все работает как и раньше. не понятно только почему у меня тоже поломалась авторизация. может они что то на своей стороне сломали? т.к. обновление вроде было 17.11.2024, а поломалось все 20.11.2024. на текущий момент 23.11.2024 опять все работает)
судя по тому, что вы пишете, вы в data_check_string собираете только auth_date, query_id, user, а надо собирать all received fields
, кроме hash.
добавлю на всякий случай
/**
* @param array<string, mixed> $data
* @return AccessToken
*/
public function auth(array $data): AccessToken
{
if (Arr::has($data, 'hash')) {
$check_params = Arr::except($data, ['hash']);
ksort($check_params);
foreach ($check_params as $param => $value) {
$check_params[$param] = "$param=$value";
}
$data_check_string = implode("\n", $check_params);
$secret_key = hash_hmac('sha256', config('telegram.token'), 'WebAppData', true);
if (hash_equals(Arr::get($data, 'hash'), hash_hmac('sha256', $data_check_string, $secret_key))) {
$user = $this->firstOrCreate(
json_decode(Arr::get($data, 'user'), true),
);
}
}
return isset($user)
? $this->createAccessToken($user, md5(json_encode($data)))
: new AccessToken();
}
Сломалась авторизация пользователя в Telegram Bot Mini App (Bot Api 8.0)