Skip to content

Commit

Permalink
Merge pull request #13 from micromagicman/#5/auth_date-check
Browse files Browse the repository at this point in the history
#5 auth_date expiration validation, webAppConfig helper function
  • Loading branch information
micromagicman committed Apr 15, 2024
2 parents f5f4eee + f38a129 commit 37ff470
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 51 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ php artisan vendor:publish --provider="Micromagicman\TelegramWebApp\TelegramWebA

All package configuration available in `config/telegram-webapp.php` file after `publish` command execution:

| Config name | Description | Environment | Default value |
|------------------------|------------------------------------------------------------------------------|-------------------------------------------|-----------------------------------------------|
| `enabled` | Telegram MiniApp data validation switch | `TELEGRAM_WEBAPP_DATA_VALIDATION_ENABLED` | `true` |
| `webAppScriptLocation` | Path to script (.js) which initializes Telegram MiniApp on your frontend app | - | `https://telegram.org/js/telegram-web-app.js` |
| `botToken` | Your Telegram bot token | `TELEGRAM_BOT_TOKEN` | - |
| `error.status` | HTTP status code when Telegram MiniApp data validation fails | - | 403 (Forbidden) |
| `error.message` | HTTP status code when Telegram MiniApp data validation fails | - | 403 (Forbidden) |
| Config name | Description | Environment | Default value |
|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|-----------------------------------------------|
| `enabled` | Telegram MiniApp data validation switch | `TELEGRAM_WEBAPP_DATA_VALIDATION_ENABLED` | `true` |
| `webAppScriptLocation` | Path to script (.js) which initializes Telegram MiniApp on your frontend app | - | `https://telegram.org/js/telegram-web-app.js` |
| `botToken` | Your Telegram bot token | `TELEGRAM_BOT_TOKEN` | - |
| `error.status` | HTTP status code when Telegram MiniApp data validation fails | - | 403 (Forbidden) |
| `error.message` | Error message returned when Telegram MiniApp data validation fails | - | 403 (Forbidden) |
| `authDateLifetimeSeconds` | The lifetime of the Telegram initData auth_date parameter in seconds. The request to the server must be made within this interval, otherwise the data transmitted from Telegram will be considered invalid. The values of the parameter <= 0 imply that there is no verification of the lifetime of data from telegram and the auth_date parameter is not validated | - | 0 |

Example in code:

Expand Down
60 changes: 30 additions & 30 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions config/telegram-webapp.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@
*/
'botToken' => env( 'TELEGRAM_BOT_TOKEN', '' ),

/*
|--------------------------------------------------------------------------
| The lifetime of the {@link https://core.telegram.org/bots/webapps#webappinitdata Telegram initData} auth_date parameter in seconds.
| The request to the server must be made within this interval, otherwise the data transmitted from Telegram
| will be considered invalid. The values of the parameter <= 0 imply that there is no verification of the lifetime
| of data from telegram and the auth_date parameter is not validated.
|--------------------------------------------------------------------------
*/
'authDateLifetimeSeconds' => 0,

/*
|--------------------------------------------------------------------------
| HTTP error response format options
Expand Down
5 changes: 2 additions & 3 deletions src/Http/WebAppDataValidationMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
/**
* Middleware that provides a mechanism for validating Telegram MiniApp users
*/
class WebAppDataValidationMiddleware
readonly class WebAppDataValidationMiddleware
{
public function __construct(
private readonly TelegramWebAppService $webAppService ) {}
public function __construct( private TelegramWebAppService $webAppService ) {}

public function handle( Request $request, Closure $next )
{
Expand Down
61 changes: 54 additions & 7 deletions src/Service/TelegramWebAppService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Micromagicman\TelegramWebApp\Dto\TelegramUser;
use Micromagicman\TelegramWebApp\Util\CryptoUtils;
use Micromagicman\TelegramWebApp\Util\Crypto;
use Micromagicman\TelegramWebApp\Util\Time;

/**
* Telegram MiniApp service functions
Expand All @@ -22,15 +23,30 @@ class TelegramWebAppService
*/
private const USER_QUERY_PARAMETER_KEY = 'user';

/**
* A name of auth_date query parameter from Telegram WebApp
*/
private const AUTH_DATE_QUERY_PARAMETER_KEY = 'auth_date';

/**
* A key for hashing a key that will be used to calculate the hash from the data received via Telegram MiniApp
*/
private const SHA256_TOKEN_HASH_KEY = 'WebAppData';

/**
* Default {@link https://core.telegram.org/bots/webapps#webappinitdata Telegram initData} auth_date lifetime
* (seconds)
*/
private const DEFAULT_AUTH_DATE_LIFETIME = 0;

public function __construct(
private readonly Crypto $crypto,
private readonly Time $time ) {}

public function abortWithError( array $errorMessageParams = [] ): void
{
$errorMessage = config( 'telegram-webapp.error.message' );
$statusCode = config( 'telegram-webapp.error.status' );
$errorMessage = $this->config( 'error.message' );
$statusCode = $this->config( 'error.status' );
abort( $statusCode, __( $errorMessage, $errorMessageParams ) );
}

Expand All @@ -41,7 +57,7 @@ public function abortWithError( array $errorMessageParams = [] ): void
public function verifyInitData( ?Request $request = null ): bool
{
$queryParams = $request->query();
if ( !array_key_exists( self::HASH_QUERY_PARAMETER_KEY, $queryParams ) ) {
if ( !$this->telegramInitDataValid( $queryParams ) ) {
return false;
}
$requestHash = $queryParams[ self::HASH_QUERY_PARAMETER_KEY ];
Expand All @@ -66,15 +82,23 @@ public function getWebAppUser( ?Request $request = null ): ?TelegramUser
return new TelegramUser( $telegramUserData );
}

/**
* Receive configuration value by the key with plugin prefix
*/
public function config( string $key, mixed $defaultValue = null )
{
return config( "telegram-webapp.$key", $defaultValue );
}

/**
* Verify integrity of the data received by comparing the received hash parameter with the hexadecimal
* representation of the HMAC-SHA-256 signature of the data-check-string with the secret key, which is the
* HMAC-SHA-256 signature of the bot's token with the constant string WebAppData used as a key.
*/
private function createHashFromQueryString( array $queryParams ): string
{
$telegramBotToken = config( 'telegram-webapp.botToken' );
$dataDigestKey = CryptoUtils::hmacSHA256( $telegramBotToken, self::SHA256_TOKEN_HASH_KEY, true );
$telegramBotToken = $this->config( 'botToken' );
$dataDigestKey = $this->crypto->hmacSHA256( $telegramBotToken, self::SHA256_TOKEN_HASH_KEY, true );
$dataWithoutHash = array_filter(
$queryParams,
fn( $key ) => $key !== self::HASH_QUERY_PARAMETER_KEY,
Expand All @@ -89,6 +113,29 @@ private function createHashFromQueryString( array $queryParams ): string
$dataWithoutHash
)
);
return CryptoUtils::hmacSHA256( $dataCheckString, $dataDigestKey );
return $this->crypto->hmacSHA256( $dataCheckString, $dataDigestKey );
}

/**
* Checking that {@link https://core.telegram.org/bots/webapps#webappinitdata Telegram initData} is valid
*/
private function telegramInitDataValid( array $telegramInitData ): bool
{
return array_key_exists( self::USER_QUERY_PARAMETER_KEY, $telegramInitData )
&& !$this->authDateExpired( $telegramInitData[ self::AUTH_DATE_QUERY_PARAMETER_KEY ] );
}

/**
* Checking that Telegram {@link https://core.telegram.org/bots/webapps#webappinitdata auth_date} parameter has expired
* The lifetime of {@link https://core.telegram.org/bots/webapps#webappinitdata Telegram initData}
* is set by the telegram-webapp.authDateLifetimeSeconds parameter
*/
private function authDateExpired( int $authDate ): bool
{
$authDateLifetime = $this->config( 'authDateLifetimeSeconds', self::DEFAULT_AUTH_DATE_LIFETIME );
if ( $authDateLifetime <= self::DEFAULT_AUTH_DATE_LIFETIME ) {
return false;
}
return $this->time->expired( $authDate + $authDateLifetime );
}
}
7 changes: 5 additions & 2 deletions src/Util/CryptoUtils.php → src/Util/Crypto.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace Micromagicman\TelegramWebApp\Util;

class CryptoUtils
/**
* Service with util methods for cryptography
*/
class Crypto
{
/**
* SHA256 hashing algorithm name for {@link hash_hmac} function
Expand All @@ -13,7 +16,7 @@ class CryptoUtils
* Generate a SHA256 hash value
* @param bool $binary - When set to true, outputs raw binary data. false outputs lowercase hexits
*/
public static function hmacSHA256( string $plainText, string $key, bool $binary = false ): string
public function hmacSHA256( string $plainText, string $key, bool $binary = false ): string
{
return hash_hmac( self::SHA_256_ALGORITHM, $plainText, $key, $binary );
}
Expand Down
Loading

0 comments on commit 37ff470

Please sign in to comment.