Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate data received via the Web App #305

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,18 @@ Telegram::Bot.configure do |config|
end
```

## Validating data received via the Web App

To validate data received via the [Web App](https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app), you can use the `Telegram::Bot::WebApp` class.
This class has a method `#verify_data_init` receive string with telegram data init that returns `Telegram::Bot::Types::WebAppUser` if the data is valid and `false` if it is not.

```ruby
bot = Telegram::Bot::Client.new(token)
bot.web_app.verify_data_init(td_data_init)
```
- `td_data_init` - String with telegram data init.


## Boilerplates

If you don't know how to setup database for your bot or how to use it with different languages
Expand Down
3 changes: 2 additions & 1 deletion lib/telegram/bot/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Telegram
module Bot
class Client
attr_reader :api, :options
attr_reader :api, :options, :web_app
attr_accessor :logger

def self.run(*args, &block)
Expand All @@ -13,6 +13,7 @@ def self.run(*args, &block)
def initialize(token, hash = {})
@options = default_options.merge(hash)
@api = Api.new(token, url: options.delete(:url), environment: options.delete(:environment))
@web_app = WebApp.new(token)
@logger = options.delete(:logger)
end

Expand Down
20 changes: 20 additions & 0 deletions lib/telegram/bot/types/web_app_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Telegram
module Bot
module Types
class WebAppUser < Base
attribute :id, Types::Integer
attribute :first_name, Types::String
attribute? :is_bot, Types::Bool
attribute? :last_name, Types::String
attribute? :username, Types::String
attribute? :language_code, Types::String
attribute? :is_premium, Types::True
attribute? :added_to_attachment_menu, Types::True
attribute? :allows_write_to_pm, Types::Bool
attribute? :photo_url, Types::String
end
end
end
end
43 changes: 43 additions & 0 deletions lib/telegram/bot/web_app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require 'openssl'
require 'json'
require 'uri'

module Telegram
module Bot
class WebApp
TG_KEY = 'WebAppData'

def initialize(token)
@token = token
end

def verify_data_init(data_init)
decoded_data = URI.decode_www_form(data_init).to_h
data_check_string = build_data_check_string(decoded_data)

secret_key = hmac_sha256(TG_KEY, @token)
hex = hex_encode(hmac_sha256(secret_key, data_check_string))

return false unless decoded_data['hash'] == hex

Types::WebAppUser.new(JSON.parse(decoded_data['user'], symbolize_names: true))
end

private

def build_data_check_string(decoded_data)
decoded_data.filter_map { |key, value| "#{key}=#{value}" unless key == 'hash' }.sort.join("\n")
end

def hmac_sha256(key, data)
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data)
end

def hex_encode(data)
data.unpack1('H*')
end
end
end
end
26 changes: 26 additions & 0 deletions spec/lib/telegram/bot/web_app_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

RSpec.describe Telegram::Bot::WebApp do
let(:web_app) { described_class.new(token) }
let(:token) { '6066223859:AAHTBqDfGUUXbb9R0HQmYlrJ6_moCPb6-wI' }

describe '#verify_data_init' do
subject { web_app.verify_data_init(data_init) }

context 'when data_init is invalid' do
# rubocop:disable Layout/LineLength
let(:data_init) do
'query_id=AAFiJKgRAAAAAGIkqBEsKipR&user=%7B%22id%22%3A296232034%2C%22first_name%22%3A%22%D0%9C%D0%B8%D1%85%D0%B0%D0%B8%D0%BB%22%2C%22last_name%22%3A%22%D0%92%D0%B0%D0%BB%D0%BE%D0%B2%22%2C%22username%22%3A%22valovm%22%2C%22language_code%22%3A%22en%22%2C%22is_premium%22%3Atrue%2C%22allows_write_to_pm%22%3Atrue%7D&auth_date=1708281435&hash=c7813b4aee476fa0c79a08e182b197ed8d97a8010cebc813320ea61b0ced2b66'
end
# rubocop:enable Layout/LineLength

it { is_expected.to be_a(Telegram::Bot::Types::WebAppUser) }
end

context 'when data_init is valid' do
let(:data_init) { 'data_init_not_valid' }

it { is_expected.to be_falsey }
end
end
end
Loading