diff --git a/CHANGELOG.md b/CHANGELOG.md index f35daf7..926fccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +- Add `:path` option to `telegram_webhook` route helper. +- __Breaking change!__ Default route is generated using hashed bot token. + Please reconfigure webhook after update (`rake telegram:bot:set_webhook`). + # 0.14.4 - Update to Bot API 4.7 diff --git a/README.md b/README.md index 5768552..f3b8224 100644 --- a/README.md +++ b/README.md @@ -366,7 +366,7 @@ end ### Routes in Rails app There is `telegram_webhook` helper for rails app to define routes for webhooks. -It defines routes at `telegram/#{bot.token}` and connects bots with controller. +It defines routes at `telegram/#{hash_of(bot.token)}` and connects bots with controller. ```ruby # Most off apps would require diff --git a/lib/telegram/bot/routes_helper.rb b/lib/telegram/bot/routes_helper.rb index 4c386ef..8aca8af 100644 --- a/lib/telegram/bot/routes_helper.rb +++ b/lib/telegram/bot/routes_helper.rb @@ -1,3 +1,6 @@ +require 'base64' +require 'openssl' + require 'telegram/bot' require 'active_support/core_ext/array/wrap' @@ -17,10 +20,8 @@ module Telegram end || 'telegram_webhook' end - # Replaces colon with underscore so rails don't treat it as - # route parameter. - def escape_token(token) - token && token.tr(':', '_') + def token_hash(token) + Base64.urlsafe_encode64(OpenSSL::Digest::SHA1.digest(token), padding: false) end end @@ -34,14 +35,17 @@ module Telegram # # # pass additional options # telegram_webhook TelegramController, :default, as: :custom_route_name - def telegram_webhook(controller, bot = :default, **options) + # + # # Default path is generated using hashed bot token. Override it using: + # telegram_webhook TelegramController, :default, path: 'top/secret' + def telegram_webhook(controller, bot = :default, path: nil, **options) bot = Client.wrap(bot) params = { to: Middleware.new(bot, controller), as: RoutesHelper.route_name_for_bot(bot), format: false, }.merge!(options) - post("telegram/#{RoutesHelper.escape_token bot.token}", params) + post(path || "telegram/#{bot.token && RoutesHelper.token_hash(bot.token)}", params) UpdatesPoller.add(bot, controller) if Telegram.bot_poller_mode? end end diff --git a/spec/telegram/bot/routes_helper_spec.rb b/spec/telegram/bot/routes_helper_spec.rb index 0a92af2..15a909b 100644 --- a/spec/telegram/bot/routes_helper_spec.rb +++ b/spec/telegram/bot/routes_helper_spec.rb @@ -41,57 +41,61 @@ RSpec.describe Telegram::Bot::RoutesHelper do end describe '#telegram_webhook' do - subject { mapper.telegram_webhook(*input) } + subject { ->(*args) { mapper.telegram_webhook(*args) } } let(:mapper) { double(:mapper).tap { |x| x.extend described_class } } - let(:bots) { {default: bot, other: other_bot} } let(:controller) { double(:controller, name: :controller) } - let(:other_controller) { double(:other_controller, name: :other_controller) } before { allow(Telegram).to receive(:bots) { bots } } - def assert_routes(bot, controller, route_name, options) # rubocop:disable AbcSize - expected_path = options.delete(:path) || "telegram/#{bot.token}" - expect(mapper).to receive(:post) do |path, params| - expect(path).to eq expected_path - middleware = params[:to] + def assert_route(bot, controller, path: nil, **expected_options) # rubocop:disable AbcSize + path ||= "telegram/#{described_class.token_hash(bot.token)}" + expect(mapper).to receive(:post) do |actual_path, actual_options| + expect(actual_path).to eq(path) + middleware = actual_options[:to] expect(middleware.controller).to eq(controller) expect(middleware.bot.token).to eq(bot.token) expect(middleware.bot.username).to eq(bot.username) - expect(params[:as]).to eq route_name - expect(params).to include(options) if options + expect(actual_options).to include(expected_options) end - subject + yield end - context 'when called with controller' do - let(:input) { [controller, option: :val] } - - it 'creates routes for default bot and this controller' do - assert_routes bot, controller, 'default_telegram_webhook', option: :val + it 'creates routes for default bot' do + assert_route(bot, controller, as: 'default_telegram_webhook') do + subject[controller] end - context 'and bot does not have configured token' do - let(:bot) { create_bot(nil) } - it 'creates routes for default bot and this controller' do - assert_routes bot, controller, 'default_telegram_webhook', option: :val - end + other_controller = double(:other_controller, name: :other_controller) + assert_route(bot, other_controller, as: 'custom_route') do + subject[other_controller, as: 'custom_route'] end + end - context 'and bot has colon in token' do - let(:bot) { create_bot('some:token') } - it 'replaces colon with underscore' do - assert_routes bot, controller, 'default_telegram_webhook', - option: :val, - path: 'telegram/some_token' + it 'passes extra options' do + assert_route(bot, controller, as: 'default_telegram_webhook', option: :val, other: 2) do + subject[controller, option: :val, other: 2] + end + end + + it 'uses :path param to override default path' do + assert_route(bot, controller, as: 'default_telegram_webhook', path: 'custom/path') do + subject[controller, path: 'custom/path'] + end + end + + context 'when bot does not have configured token' do + let(:bot) { create_bot(nil) } + it 'creates route anyway' do + assert_route(bot, controller, as: 'default_telegram_webhook', path: 'telegram/') do + subject[controller] end end end context 'when called with controller and smth castable to bot' do - let(:input) { [controller, 'custom_token', option: :val] } - it 'creates routes for every created bot and controller' do - assert_routes create_bot('custom_token'), controller, 'telegram_webhook', - option: :val + assert_route(create_bot('custom_token'), controller, as: 'telegram_webhook') do + subject[controller, 'custom_token'] + end end end end