Allow Users to use the bot api (#19)

Co-authored-by: Jannik <none>
This commit is contained in:
Jannik 2020-12-12 00:45:36 +01:00 committed by GitHub
parent 14bf658b86
commit 100a7cc846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 437 additions and 65 deletions

View File

@ -15,6 +15,7 @@ Please note that only TDLight-specific issues are suitable for this repository.
- [TDLight features](#tdlight-features)
- [Added features](#added-features)
- [Modified features](#modified-features)
- [User Mode](#user-mode)
- [Installation](#installation)
- [Dependencies](#dependencies)
- [Usage](#usage)
@ -126,6 +127,81 @@ In addition, the member list now shows the full bot list (previously only the bo
The bot will now receive Updates for all received media, even if a destruction timer is set.
<a name="user-mode"></a>
### User Mode
You can allow user accounts to access the bot api with the command-line option `--allow-users` or set the env variable
`TELEGRAM_ALLOW_USERS` to `1` when using docker. User Mode is disabled by default, so only bots can access the api.
You can now log into the bot api with user accounts to create userbots running on your account.
Note: Never send your 2fa password over a plain http connection. Make sure https is enabled or use this api locally.
#### User Authorization Process
1. Send a request to `{api_url}/userlogin`
Parameters:
- `phone_number`: `string`. The phone number of your Telegram Account.
Returns your `user_token` as `string`. You can use this just like a normal bot token on the `/user` endpoint
2. Send the received code to `{api_url}/user{user_token}/authcode`
Parameters:
- `code`: `int`. The code send to you by Telegram In-App or by SMS
Will send `{"ok": true, "result": true}` on success.
3. Optional: Send your 2fa password to `{api_url}/user{user_token}/2fapassword`
Parameters:
- `password`: `string`. Password for 2fa authentication
Will send `{"ok": true, "result": true}` on success.
4. Optional: Register the user by calling `{api_url}/user{user_token}/registerUser`.
User registration is disabled by default. You can enable it with the `--allow-users-registration` command line
option or the env variable `TELEGRAM_ALLOW_USERS_REGISTRATION` set to `1` when using docker.
Parameters:
- `first_name`: `string`. First name for the new account.
- `last_name`: `string`, optional. Last name for the new account.
Will send `{"ok": true, "result": true}` on success.
You are now logged in and can use all methods like in the bot api, just replace the
`/bot{bot_token}/` in your urls with `/user{token}/`.
You only need to authenticate once, the account will stay logged in. You can use the `logOut` method to log out
or simply close the session in your account settings.
Some methods are (obviously) not available as a user. This includes:
- `answerCallbackQuery`
- `setMyCommands`
- `editMessageReplyMarkup`
- `uploadStickerFile`
- `createNewStickerSet`
- `addStickerToSet`
- `setStickerPositionInSet`
- `deleteStickerFromSet`
- `setStickerSetThumb`
- `sendInvoice`
- `answerShippingQuery`
- `answerPreCheckoutQuery`
- `setPassportDataErrors`
- `sendGame`
- `setGameScore`
- `getGameHighscores`
It is also not possible to attach a `reply_markup` to any message.
Your api wrapper may behave different in
some cases, for examples command message-entities are not created in chats that don't contain any
bots, so your Command Handler may not detect it.
It is possible to have multiple user-tokens to multiple client instances on the same bot api server.
<a name="installation"></a>
## Installation

View File

@ -40,6 +40,12 @@ fi
if [ -n "$TELEGRAM_NO_FILE_LIMIT" ]; then
CUSTOM_ARGS="${CUSTOM_ARGS} --no-file-limit"
fi
if [ -n "$TELEGRAM_ALLOW_USERS" ]; then
CUSTOM_ARGS="${CUSTOM_ARGS} --allow-users"
fi
if [ -n "$TELEGRAM_ALLOW_USERS_REGISTRATION" ]; then
CUSTOM_ARGS="${CUSTOM_ARGS} --allow-users-registration"
fi
if [ -n "$TELEGRAM_INSECURE" ]; then
CUSTOM_ARGS="${CUSTOM_ARGS} --insecure"
fi

2
td

@ -1 +1 @@
Subproject commit b47fab11cd7dddbb2829ef1830007632332b717b
Subproject commit 2b92c16998d39e7bfee580defcb0109151e4fb81

View File

@ -34,6 +34,21 @@
#include <cstdlib>
#define CHECK_IS_BOT() \
if (is_user_) { \
return Status::Error(BOT_ONLY_ERROR_CODE, BOT_ONLY_ERROR_DESCRIPTION); \
}
#define CHECK_IS_USER() \
if (!is_user_) { \
return Status::Error(USER_ONLY_ERROR_CODE, USER_ONLY_ERROR_DESCRIPTION); \
}
#define CHECK_USER_REPLY_MARKUP() \
if (reply_markup != nullptr && is_user_) { \
return Status::Error(BOT_ONLY_ERROR_CODE, BOT_ONLY_ERROR_DESCRIPTION); \
}
namespace telegram_bot_api {
using td::Jsonable;
@ -124,6 +139,9 @@ void Client::fail_query_with_error(PromisedQueryPtr query, int32 error_code, Sli
case 403:
prefix = Slice("Forbidden");
break;
case 405:
prefix = Slice("Method Not Allowed");
break;
case 500:
prefix = Slice("Internal Server Error");
break;
@ -156,11 +174,31 @@ void Client::fail_query_with_error(PromisedQueryPtr &&query, object_ptr<td_api::
fail_query_with_error(std::move(query), error->code_, error->message_, default_message);
}
Client::Client(td::ActorShared<> parent, const td::string &bot_token, bool is_test_dc, int64 tqueue_id,
Client::Client(td::ActorShared<> parent, const td::string &bot_token, bool is_user, bool is_test_dc, int64 tqueue_id,
std::shared_ptr<const ClientParameters> parameters, td::ActorId<BotStatActor> stat_actor)
: parent_(std::move(parent))
, bot_token_(bot_token)
, bot_token_id_("<unknown>")
, is_user_(is_user)
, is_test_dc_(is_test_dc)
, tqueue_id_(tqueue_id)
, parameters_(std::move(parameters))
, stat_actor_(std::move(stat_actor)) {
messages_lru_root_.lru_next = &messages_lru_root_;
messages_lru_root_.lru_prev = &messages_lru_root_;
static auto is_inited = init_methods();
CHECK(is_inited);
}
Client::Client(td::ActorShared<> parent, const td::string &bot_token, const td::string &phone_number, bool is_user,
bool is_test_dc, int64 tqueue_id, std::shared_ptr<const ClientParameters> parameters,
td::ActorId<BotStatActor> stat_actor)
: parent_(std::move(parent))
, bot_token_(bot_token)
, bot_token_id_("<unknown>")
, phone_number_(phone_number)
, is_user_(is_user)
, is_test_dc_(is_test_dc)
, tqueue_id_(tqueue_id)
, parameters_(std::move(parameters))
@ -2295,7 +2333,10 @@ class Client::TdOnAuthorizationCallback : public TdQueryCallback {
}
void on_result(object_ptr<td_api::Object> result) override {
bool was_ready = client_->authorization_state_->get_id() != td_api::authorizationStateWaitPhoneNumber::ID;
bool was_ready = client_->authorization_state_->get_id() != td_api::authorizationStateWaitPhoneNumber::ID &&
client_->authorization_state_->get_id() != td_api::authorizationStateWaitCode::ID &&
client_->authorization_state_->get_id() != td_api::authorizationStateWaitPassword::ID &&
client_->authorization_state_->get_id() != td_api::authorizationStateWaitRegistration::ID;
if (result->get_id() == td_api::error::ID) {
auto error = move_object_as<td_api::error>(result);
if (error->code_ == 429 || error->code_ >= 500 || (error->code_ != 401 && was_ready)) {
@ -2314,6 +2355,38 @@ class Client::TdOnAuthorizationCallback : public TdQueryCallback {
Client *client_;
};
class Client::TdOnAuthorizationQueryCallback : public TdQueryCallback {
public:
TdOnAuthorizationQueryCallback(Client *client, PromisedQueryPtr query) :
client_(client), query_(std::move(query)) {
}
void on_result(object_ptr<td_api::Object> result) override {
bool was_ready = client_->authorization_state_->get_id() != td_api::authorizationStateWaitPhoneNumber::ID &&
client_->authorization_state_->get_id() != td_api::authorizationStateWaitCode::ID &&
client_->authorization_state_->get_id() != td_api::authorizationStateWaitPassword::ID &&
client_->authorization_state_->get_id() != td_api::authorizationStateWaitRegistration::ID;
if (result->get_id() == td_api::error::ID) {
auto error = move_object_as<td_api::error>(result);
if (error->code_ == 429 || error->code_ >= 500 || (error->code_ != 401 && was_ready)) {
// try again
return client_->on_update_authorization_state();
}
fail_query(401, "Unauthorized: Log in failed, logging out due to " + td::oneline(to_string(error)),
std::move(query_));
LOG(WARNING) << "Logging out due to " << td::oneline(to_string(error));
client_->log_out();
} else {
answer_query(td::JsonTrue(), std::move(query_));
client_->on_update_authorization_state();
}
}
private:
Client *client_;
PromisedQueryPtr query_;
};
class Client::TdOnInitCallback : public TdQueryCallback {
public:
explicit TdOnInitCallback(Client *client) : client_(client) {
@ -3237,7 +3310,7 @@ class Client::TdOnSendCustomRequestCallback : public TdQueryCallback {
class Client::TdOnPingCallback : public TdQueryCallback {
public:
TdOnPingCallback(PromisedQueryPtr query)
explicit TdOnPingCallback(PromisedQueryPtr query)
: query_(std::move(query)) {
}
@ -3245,7 +3318,7 @@ class Client::TdOnPingCallback : public TdQueryCallback {
if (result->get_id() == td_api::error::ID) {
return fail_query_with_error(std::move(query_), move_object_as<td_api::error>(result), "Server not available");
}
CHECK(result->get_id() == 959899022); // id for return type `seconds`
CHECK(result->get_id() == td_api::seconds::ID);
auto seconds_ = move_object_as<td_api::seconds>(result);
answer_query(td::VirtuallyJsonableString(std::to_string(seconds_->seconds_)), std::move(query_));
@ -3386,7 +3459,7 @@ void Client::raw_event(const td::Event::Raw &event) {
}
void Client::loop() {
if (logging_out_ || closing_ || was_authorized_) {
if (logging_out_ || closing_ || was_authorized_ || waiting_for_auth_input_) {
while (!cmd_queue_.empty()) {
auto query = std::move(cmd_queue_.front());
cmd_queue_.pop();
@ -3947,9 +4020,20 @@ void Client::on_update_authorization_state() {
case td_api::authorizationStateWaitEncryptionKey::ID:
return send_request(make_object<td_api::checkDatabaseEncryptionKey>(), std::make_unique<TdOnInitCallback>(this));
case td_api::authorizationStateWaitPhoneNumber::ID:
return send_request(make_object<td_api::checkAuthenticationBotToken>(bot_token_),
std::make_unique<TdOnAuthorizationCallback>(this));
if (is_user_) {
return send_request(make_object<td_api::setAuthenticationPhoneNumber>(phone_number_, nullptr),
std::make_unique<TdOnAuthorizationCallback>(this));
} else {
return send_request(make_object<td_api::checkAuthenticationBotToken>(bot_token_),
std::make_unique<TdOnAuthorizationCallback>(this));
}
case td_api::authorizationStateWaitCode::ID:
case td_api::authorizationStateWaitPassword::ID:
case td_api::authorizationStateWaitRegistration::ID:
waiting_for_auth_input_ = true;
return loop();
case td_api::authorizationStateReady::ID: {
waiting_for_auth_input_ = false;
auto user_info = get_user_info(my_id_);
if (my_id_ <= 0 || user_info == nullptr) {
return send_request(make_object<td_api::getMe>(), std::make_unique<TdOnAuthorizationCallback>(this));
@ -3970,12 +4054,14 @@ void Client::on_update_authorization_state() {
return loop();
}
case td_api::authorizationStateLoggingOut::ID:
waiting_for_auth_input_ = false;
if (!logging_out_) {
LOG(WARNING) << "Logging out";
logging_out_ = true;
}
break;
case td_api::authorizationStateClosing::ID:
waiting_for_auth_input_ = false;
if (!closing_) {
LOG(WARNING) << "Closing";
closing_ = true;
@ -4334,6 +4420,7 @@ void Client::on_closed() {
if (logging_out_) {
parameters_->shared_data_->webhook_db_->erase(bot_token_with_dc_);
parameters_->shared_data_->user_db_->erase(bot_token_with_dc_);
class RmWorker : public td::Actor {
public:
@ -6029,6 +6116,17 @@ void Client::on_cmd(PromisedQueryPtr query) {
return do_send_request(make_object<td_api::logOut>(), std::make_unique<TdOnOkQueryCallback>(std::move(query)));
}
}
if (waiting_for_auth_input_) {
if (query->method() == "authcode") {
return process_authcode_query(query);
} else if (query->method() == "2fapassword") {
return process_2fapassword_query(query);
} else if (query->method() == "registeruser" && parameters_->allow_users_registration_) {
return process_register_user_query(query);
} else {
return fail_query(404, "Not Found: method not found", std::move(query));
}
}
if (logging_out_) {
return fail_query(LOGGING_OUT_ERROR_CODE, LOGGING_OUT_ERROR_DESCRIPTION, std::move(query));
@ -6063,6 +6161,7 @@ td::Status Client::process_get_my_commands_query(PromisedQueryPtr &query) {
}
td::Status Client::process_set_my_commands_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(bot_commands, get_bot_commands(query.get()));
send_request(make_object<td_api::setCommands>(std::move(bot_commands)),
std::make_unique<TdOnOkQueryCallback>(std::move(query)));
@ -6208,12 +6307,14 @@ td::Status Client::process_send_voice_query(PromisedQueryPtr &query) {
}
td::Status Client::process_send_game_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(game_short_name, get_required_string_arg(query.get(), "game_short_name"));
do_send_message(make_object<td_api::inputMessageGame>(my_id_, game_short_name.str()), std::move(query));
return Status::OK();
}
td::Status Client::process_send_invoice_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(title, get_required_string_arg(query.get(), "title"));
TRY_RESULT(description, get_required_string_arg(query.get(), "description"));
TRY_RESULT(payload, get_required_string_arg(query.get(), "payload"));
@ -6351,6 +6452,7 @@ td::Status Client::process_stop_poll_query(PromisedQueryPtr &query) {
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
CHECK_USER_REPLY_MARKUP();
resolve_reply_markup_bot_usernames(
std::move(reply_markup), std::move(query),
@ -6446,6 +6548,7 @@ td::Status Client::process_edit_message_text_query(PromisedQueryPtr &query) {
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
CHECK_USER_REPLY_MARKUP();
if (chat_id.empty() && message_id == 0) {
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
@ -6485,6 +6588,7 @@ td::Status Client::process_edit_message_live_location_query(PromisedQueryPtr &qu
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
CHECK_USER_REPLY_MARKUP();
if (chat_id.empty() && message_id == 0) {
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
@ -6520,6 +6624,7 @@ td::Status Client::process_edit_message_media_query(PromisedQueryPtr &query) {
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
CHECK_USER_REPLY_MARKUP();
TRY_RESULT(input_media, get_input_media(query.get(), "media", false));
if (chat_id.empty() && message_id == 0) {
@ -6554,6 +6659,7 @@ td::Status Client::process_edit_message_caption_query(PromisedQueryPtr &query) {
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
CHECK_USER_REPLY_MARKUP();
TRY_RESULT(caption, get_caption(query.get()));
if (chat_id.empty() && message_id == 0) {
@ -6584,9 +6690,11 @@ td::Status Client::process_edit_message_caption_query(PromisedQueryPtr &query) {
}
td::Status Client::process_edit_message_reply_markup_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(reply_markup, get_reply_markup(query.get()));
CHECK_USER_REPLY_MARKUP();
if (chat_id.empty() && message_id == 0) {
TRY_RESULT(inline_message_id, get_inline_message_id(query.get()));
@ -6636,6 +6744,7 @@ td::Status Client::process_delete_message_query(PromisedQueryPtr &query) {
}
td::Status Client::process_set_game_score_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(user_id, get_user_id(query.get()));
@ -6673,6 +6782,7 @@ td::Status Client::process_set_game_score_query(PromisedQueryPtr &query) {
}
td::Status Client::process_get_game_high_scores_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto chat_id = query->arg("chat_id");
auto message_id = get_message_id(query.get());
TRY_RESULT(user_id, get_user_id(query.get()));
@ -6698,6 +6808,7 @@ td::Status Client::process_get_game_high_scores_query(PromisedQueryPtr &query) {
}
td::Status Client::process_answer_inline_query_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto inline_query_id = td::to_integer<int64>(query->arg("inline_query_id"));
auto is_personal = to_bool(query->arg("is_personal"));
int32 cache_time = get_integer_arg(query.get(), "cache_time", 300, 0, 24 * 60 * 60);
@ -6721,6 +6832,7 @@ td::Status Client::process_answer_inline_query_query(PromisedQueryPtr &query) {
}
td::Status Client::process_answer_callback_query_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto callback_query_id = td::to_integer<int64>(query->arg("callback_query_id"));
td::string text = query->arg("text").str();
bool show_alert = to_bool(query->arg("show_alert"));
@ -6733,6 +6845,7 @@ td::Status Client::process_answer_callback_query_query(PromisedQueryPtr &query)
}
td::Status Client::process_answer_shipping_query_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto shipping_query_id = td::to_integer<int64>(query->arg("shipping_query_id"));
auto ok = to_bool(query->arg("ok"));
td::vector<object_ptr<td_api::shippingOption>> shipping_options;
@ -6749,6 +6862,7 @@ td::Status Client::process_answer_shipping_query_query(PromisedQueryPtr &query)
}
td::Status Client::process_answer_pre_checkout_query_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto pre_checkout_query_id = td::to_integer<int64>(query->arg("pre_checkout_query_id"));
auto ok = to_bool(query->arg("ok"));
td::MutableSlice error_message;
@ -7226,6 +7340,7 @@ td::Status Client::process_get_sticker_set_query(PromisedQueryPtr &query) {
}
td::Status Client::process_upload_sticker_file_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(user_id, get_user_id(query.get()));
auto png_sticker = get_input_file(query.get(), "png_sticker");
@ -7238,6 +7353,7 @@ td::Status Client::process_upload_sticker_file_query(PromisedQueryPtr &query) {
}
td::Status Client::process_create_new_sticker_set_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(user_id, get_user_id(query.get()));
auto name = query->arg("name");
auto title = query->arg("title");
@ -7254,6 +7370,7 @@ td::Status Client::process_create_new_sticker_set_query(PromisedQueryPtr &query)
}
td::Status Client::process_add_sticker_to_set_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(user_id, get_user_id(query.get()));
auto name = query->arg("name");
TRY_RESULT(stickers, get_input_stickers(query.get()));
@ -7268,6 +7385,7 @@ td::Status Client::process_add_sticker_to_set_query(PromisedQueryPtr &query) {
}
td::Status Client::process_set_sticker_set_thumb_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(user_id, get_user_id(query.get()));
auto name = query->arg("name");
auto thumbnail = get_input_file(query.get(), "thumb");
@ -7280,6 +7398,7 @@ td::Status Client::process_set_sticker_set_thumb_query(PromisedQueryPtr &query)
}
td::Status Client::process_set_sticker_position_in_set_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto file_id = trim(query->arg("sticker"));
if (file_id.empty()) {
return Status::Error(400, "Sticker is not specified");
@ -7293,6 +7412,7 @@ td::Status Client::process_set_sticker_position_in_set_query(PromisedQueryPtr &q
}
td::Status Client::process_delete_sticker_from_set_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
auto file_id = trim(query->arg("sticker"));
if (file_id.empty()) {
return Status::Error(400, "Sticker is not specified");
@ -7304,6 +7424,7 @@ td::Status Client::process_delete_sticker_from_set_query(PromisedQueryPtr &query
}
td::Status Client::process_set_passport_data_errors_query(PromisedQueryPtr &query) {
CHECK_IS_BOT();
TRY_RESULT(user_id, get_user_id(query.get()));
TRY_RESULT(passport_element_errors, get_passport_element_errors(query.get()));
@ -7557,6 +7678,45 @@ td::Status Client::process_ping_query(PromisedQueryPtr &query) {
}
//end custom methods impl
//start costom auth methods impl
void Client::process_authcode_query(PromisedQueryPtr &query) {
auto code = query->arg("code");
if (code.empty()) {
return fail_query(400, "Bad Request: code not found", std::move(query));
}
if (authorization_state_->get_id() != td_api::authorizationStateWaitCode::ID) {
return fail_query(400, "Bad Request: currently not waiting for a code", std::move(query));
}
send_request(make_object<td_api::checkAuthenticationCode>(code.str()),
std::make_unique<TdOnAuthorizationQueryCallback>(this, std::move(query)));
}
void Client::process_2fapassword_query(PromisedQueryPtr &query) {
auto password = query->arg("password");
if (password.empty()) {
return fail_query(400, "Bad Request: password not found", std::move(query));
}
if (authorization_state_->get_id() != td_api::authorizationStateWaitPassword::ID) {
return fail_query(400, "Bad Request: currently not waiting for a password", std::move(query));
}
send_request(make_object<td_api::checkAuthenticationPassword>(password.str()),
std::make_unique<TdOnAuthorizationQueryCallback>(this, std::move(query)));
}
void Client::process_register_user_query(PromisedQueryPtr &query) {
auto first_name = query->arg("first_name");
if (first_name.empty()) {
return fail_query(400, "Bad Request: first_name not found", std::move(query));
}
auto last_name = query->arg("last_name");
if (authorization_state_->get_id() != td_api::authorizationStateWaitRegistration::ID) {
return fail_query(400, "Bad Request: currently not waiting for registration", std::move(query));
}
send_request(make_object<td_api::registerUser>(first_name.str(), last_name.str()),
std::make_unique<TdOnAuthorizationQueryCallback>(this, std::move(query)));
}
//end custom auth methods impl
void Client::do_get_file(object_ptr<td_api::file> file, PromisedQueryPtr query) {
if ((!parameters_->local_mode_ || !parameters_->no_file_limit_) &&
@ -7776,6 +7936,9 @@ void Client::do_send_message(object_ptr<td_api::InputMessageContent> input_messa
return fail_query_with_error(std::move(query), 400, r_reply_markup.error().message());
}
auto reply_markup = r_reply_markup.move_as_ok();
if (reply_markup != nullptr && is_user_) {
return fail_query_with_error(std::move(query), 405, "Method Not Allowed: reply markup not available as user.");
}
resolve_reply_markup_bot_usernames(
std::move(reply_markup), std::move(query),
@ -9335,6 +9498,12 @@ constexpr Client::Slice Client::LOGGING_OUT_ERROR_DESCRIPTION;
constexpr int Client::CLOSING_ERROR_CODE;
constexpr Client::Slice Client::CLOSING_ERROR_DESCRIPTION;
constexpr int Client::BOT_ONLY_ERROR_CODE;
constexpr Client::Slice Client::BOT_ONLY_ERROR_DESCRIPTION;
constexpr int Client::USER_ONLY_ERROR_CODE;
constexpr Client::Slice Client::USER_ONLY_ERROR_DESCRIPTION;
std::unordered_map<td::string, td::Status (Client::*)(PromisedQueryPtr &query)> Client::methods_;
} // namespace telegram_bot_api

View File

@ -38,9 +38,13 @@ namespace td_api = td::td_api;
class Client : public WebhookActor::Callback {
public:
Client(td::ActorShared<> parent, const td::string &bot_token, bool is_test_dc, td::int64 tqueue_id,
Client(td::ActorShared<> parent, const td::string &bot_token, bool is_user, bool is_test_dc, td::int64 tqueue_id,
std::shared_ptr<const ClientParameters> parameters, td::ActorId<BotStatActor> stat_actor);
Client(td::ActorShared<> parent, const td::string &bot_token, const td::string &phone_number, bool is_user,
bool is_test_dc, td::int64 tqueue_id, std::shared_ptr<const ClientParameters> parameters,
td::ActorId<BotStatActor> stat_actor);
void send(PromisedQueryPtr query) override;
void close();
@ -82,6 +86,12 @@ class Client : public WebhookActor::Callback {
static constexpr int CLOSING_ERROR_CODE = 500;
static constexpr Slice CLOSING_ERROR_DESCRIPTION = "Internal Server Error: restart";
static constexpr int BOT_ONLY_ERROR_CODE = 405;
static constexpr Slice BOT_ONLY_ERROR_DESCRIPTION = "Method Not Allowed: You can only use this method as a bot";
static constexpr int USER_ONLY_ERROR_CODE = 405;
static constexpr Slice USER_ONLY_ERROR_DESCRIPTION = "Method Not Allowed: You can only use this method as a user";
class JsonFile;
class JsonDatedFile;
class JsonDatedFiles;
@ -149,6 +159,7 @@ class Client : public WebhookActor::Callback {
class TdOnOkCallback;
class TdOnAuthorizationCallback;
class TdOnAuthorizationQueryCallback;
class TdOnInitCallback;
class TdOnGetUserProfilePhotosCallback;
class TdOnSendMessageCallback;
@ -498,6 +509,11 @@ class Client : public WebhookActor::Callback {
Status process_toggle_group_invites_query(PromisedQueryPtr &query);
Status process_ping_query(PromisedQueryPtr &query);
//custom auth methods
void process_authcode_query(PromisedQueryPtr &query);
void process_2fapassword_query(PromisedQueryPtr &query);
void process_register_user_query(PromisedQueryPtr &query);
void webhook_verified(td::string cached_ip_address) override;
void webhook_success() override;
@ -823,11 +839,14 @@ class Client : public WebhookActor::Callback {
bool logging_out_ = false;
bool need_close_ = false;
bool clear_tqueue_ = false;
bool waiting_for_auth_input_ = false;
td::ActorShared<> parent_;
td::string bot_token_;
td::string bot_token_with_dc_;
td::string bot_token_id_;
td::string phone_number_;
bool is_user_;
bool is_test_dc_;
int64 tqueue_id_;
double start_time_ = 0;

View File

@ -33,6 +33,8 @@
#include "td/utils/StackAllocator.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/Random.h"
#include "td/utils/base64.h"
#include <map>
#include <tuple>
@ -62,7 +64,9 @@ void ClientManager::send(PromisedQueryPtr query) {
// automatically send 429
return;
}
if (!parameters_->allow_users_ && query->is_user()) {
return fail_query(405, "Method Not Allowed: Users are not allowed to use the api", std::move(query));
}
td::string token = query->token().str();
if (token[0] == '0' || token.size() > 80u || token.find('/') != td::string::npos ||
token.find(':') == td::string::npos) {
@ -77,55 +81,29 @@ void ClientManager::send(PromisedQueryPtr query) {
token += "/test";
}
auto bot_token_with_dc = PSTRING() << query->token() << (query->is_test_dc() ? ":T" : "");
if (parameters_->shared_data_->user_db_->isset(bot_token_with_dc) != query->is_user()) {
return fail_query(400, "Bad Request: Please use the correct api endpoint for bots or users", std::move(query));
}
auto id_it = token_to_id_.find(token);
if (id_it == token_to_id_.end()) {
std::string ip_address;
if (query->peer_address().is_valid() && !query->peer_address().is_reserved()) { // external connection
ip_address = query->peer_address().get_ip_str().str();
} else {
// invalid peer address or connection from the local network
ip_address = query->get_header("x-real-ip").str();
if (!check_flood_limits(query)) {
return;
}
if (!ip_address.empty()) {
td::IPAddress tmp;
tmp.init_host_port(ip_address, 0).ignore();
tmp.clear_ipv6_interface();
if (tmp.is_valid()) {
ip_address = tmp.get_ip_str().str();
}
}
LOG(DEBUG) << "Receive incoming query for new bot " << token << " from " << query->peer_address();
if (!ip_address.empty()) {
LOG(DEBUG) << "Check Client creation flood control for IP address " << ip_address;
auto res = flood_controls_.emplace(std::move(ip_address), td::FloodControlFast());
auto &flood_control = res.first->second;
if (res.second) {
flood_control.add_limit(60, 20); // 20 in a minute
flood_control.add_limit(60 * 60, 600); // 600 in an hour
}
td::uint32 now = static_cast<td::uint32>(td::Time::now());
td::uint32 wakeup_at = flood_control.get_wakeup_at();
if (wakeup_at > now) {
LOG(INFO) << "Failed to create Client from IP address " << ip_address;
return query->set_retry_after_error(static_cast<int>(wakeup_at - now) + 1);
}
flood_control.add_event(static_cast<td::int32>(now));
}
auto id = clients_.create(ClientInfo{BotStatActor(stat_.actor_id(&stat_)), token, td::ActorOwn<Client>()});
auto *client_info = clients_.get(id);
auto stat_actor = client_info->stat_.actor_id(&client_info->stat_);
auto client_id = td::create_actor<Client>(
PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_test_dc(),
get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_, std::move(stat_actor));
PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_user(),
query->is_test_dc(), get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_, std::move(stat_actor));
auto method = query->method();
if (method != "deletewebhook" && method != "setwebhook") {
auto bot_token_with_dc = PSTRING() << query->token() << (query->is_test_dc() ? ":T" : "");
auto webhook_info = parameters_->shared_data_->webhook_db_->get(bot_token_with_dc);
if (!webhook_info.empty()) {
send_closure(client_id, &Client::send,
get_webhook_restore_query(bot_token_with_dc, webhook_info, parameters_->shared_data_));
get_webhook_restore_query(bot_token_with_dc, query->is_user(), webhook_info, parameters_->shared_data_));
}
}
@ -140,6 +118,89 @@ void ClientManager::send(PromisedQueryPtr query) {
send_closure(client_info->client_, &Client::send, std::move(query)); // will send 429 if the client is already closed
}
void ClientManager::user_login(PromisedQueryPtr query) {
if (!check_flood_limits(query, true)) {
return;
}
if (!parameters_->allow_users_) {
return fail_query(405, "Method Not Allowed: Users are not allowed to use the api", std::move(query));
}
td::MutableSlice r_phone_number = query->arg("phone_number");
if (r_phone_number.size() < 5 || r_phone_number.size() > 15) {
return fail_query(401, "Unauthorized: invalid phone number specified", std::move(query));
}
td::int64 phone_number = 0;
for (char const &c: r_phone_number) {
if (isdigit(c)) {
phone_number = phone_number * 10 + (c - 48);
}
}
td::UInt256 token_data;
td::Random::secure_bytes(token_data.raw, sizeof(token_data));
td::string user_token = td::to_string(phone_number) + ":" + td::base64url_encode(token_data.as_slice());
auto user_token_with_dc = PSTRING() << user_token << (query->is_test_dc() ? ":T" : "");
long token_hash = std::hash<td::string>{}(user_token);
auto id = clients_.create(ClientInfo{BotStatActor(stat_.actor_id(&stat_)), user_token, td::ActorOwn<Client>()});
auto *client_info = clients_.get(id);
auto stat_actor = client_info->stat_.actor_id(&client_info->stat_);
auto client_id = td::create_actor<Client>(
PSLICE() << "Client/" << user_token, actor_shared(this, id), user_token, td::to_string(phone_number),
true, query->is_test_dc(), get_tqueue_id(token_hash, query->is_test_dc()), parameters_, std::move(stat_actor));
clients_.get(id)->client_ = std::move(client_id);
auto id_it = token_to_id_.end();
std::tie(id_it, std::ignore) = token_to_id_.emplace(user_token, id);
parameters_->shared_data_->user_db_->set(user_token_with_dc, "1");
answer_query(td::VirtuallyJsonableString(user_token), std::move(query));
}
bool ClientManager::check_flood_limits(PromisedQueryPtr &query, bool is_user_login) {
td::string ip_address;
if (query->peer_address().is_valid() && !query->peer_address().is_reserved()) { // external connection
ip_address = query->peer_address().get_ip_str().str();
} else {
// invalid peer address or connection from the local network
ip_address = query->get_header("x-real-ip").str();
}
if (!ip_address.empty()) {
td::IPAddress tmp;
tmp.init_host_port(ip_address, 0).ignore();
tmp.clear_ipv6_interface();
if (tmp.is_valid()) {
ip_address = tmp.get_ip_str().str();
}
}
LOG(DEBUG) << "Receive incoming query for new bot " << query->token() << " from " << query->peer_address();
if (!ip_address.empty()) {
LOG(DEBUG) << "Check Client creation flood control for IP address " << ip_address;
if (is_user_login) {
ip_address += "/user";
}
auto res = flood_controls_.emplace(std::move(ip_address), td::FloodControlFast());
auto &flood_control = res.first->second;
if (res.second) {
if (is_user_login) {
flood_control.add_limit(60, 5); // 5 in a minute
flood_control.add_limit(60 * 60, 15); // 15 in an hour
} else {
flood_control.add_limit(60, 20); // 20 in a minute
flood_control.add_limit(60 * 60, 600); // 600 in an hour
}
}
auto now = static_cast<td::uint32>(td::Time::now());
td::uint32 wakeup_at = flood_control.get_wakeup_at();
if (wakeup_at > now) {
LOG(INFO) << "Failed to create Client from IP address " << ip_address;
query->set_retry_after_error(static_cast<int>(wakeup_at - now) + 1);
return false;
}
flood_control.add_event(static_cast<td::int32>(now));
}
return true;
}
void ClientManager::get_stats(td::PromiseActor<td::BufferSlice> promise,
td::vector<std::pair<td::string, td::string>> args) {
if (close_flag_) {
@ -297,13 +358,19 @@ void ClientManager::start_up() {
parameters_->shared_data_->tqueue_ = std::move(tqueue);
}
// init webhook_db
// init webhook_db and user_db
auto concurrent_webhook_db = td::make_unique<td::BinlogKeyValue<td::ConcurrentBinlog>>();
auto status = concurrent_webhook_db->init("webhooks_db.binlog", td::DbKey::empty(), scheduler_id);
LOG_IF(FATAL, status.is_error()) << "Can't open webhooks_db.binlog " << status.error();
parameters_->shared_data_->webhook_db_ = std::move(concurrent_webhook_db);
auto concurrent_user_db = td::make_unique<td::BinlogKeyValue<td::ConcurrentBinlog>>();
status = concurrent_user_db->init("user_db.binlog", td::DbKey::empty(), scheduler_id);
LOG_IF(FATAL, status.is_error()) << "Can't open user_db.binlog " << status.error();
parameters_->shared_data_->user_db_ = std::move(concurrent_user_db);
auto &webhook_db = *parameters_->shared_data_->webhook_db_;
auto &user_db = *parameters_->shared_data_->user_db_;
for (auto key_value : webhook_db.get_all()) {
if (!token_range_(td::to_integer<td::uint64>(key_value.first))) {
LOG(WARNING) << "DROP WEBHOOK: " << key_value.first << " ---> " << key_value.second;
@ -311,12 +378,13 @@ void ClientManager::start_up() {
continue;
}
auto query = get_webhook_restore_query(key_value.first, key_value.second, parameters_->shared_data_);
auto query = get_webhook_restore_query(key_value.first, user_db.isset(key_value.first), key_value.second, parameters_->shared_data_);
send_closure_later(actor_id(this), &ClientManager::send, std::move(query));
}
}
PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, td::Slice webhook_info,
PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, bool is_user, td::Slice webhook_info,
std::shared_ptr<SharedData> shared_data) {
// create Query with empty promise
td::vector<td::BufferSlice> containers;
@ -364,7 +432,7 @@ PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, td::S
args.emplace_back(add_string("url"), add_string(parser.read_all()));
const auto method = add_string("setwebhook");
auto query = std::make_unique<Query>(std::move(containers), token, is_test_dc, method, std::move(args),
auto query = std::make_unique<Query>(std::move(containers), token, is_user, is_test_dc, method, std::move(args),
td::vector<std::pair<td::MutableSlice, td::MutableSlice>>(),
td::vector<td::HttpFile>(), std::move(shared_data), td::IPAddress());
query->set_internal(true);
@ -392,6 +460,7 @@ void ClientManager::close_db() {
parameters_->shared_data_->tqueue_->close(mpromise.get_promise());
parameters_->shared_data_->webhook_db_->close(mpromise.get_promise());
parameters_->shared_data_->user_db_->close(mpromise.get_promise());
}
void ClientManager::finish_close() {

View File

@ -42,6 +42,9 @@ class ClientManager final : public td::Actor {
}
void send(PromisedQueryPtr query);
void user_login(PromisedQueryPtr query);
bool check_flood_limits(PromisedQueryPtr &query, bool is_user_login=false);
void get_stats(td::PromiseActor<td::BufferSlice> promise, td::vector<std::pair<td::string, td::string>> args);
@ -68,7 +71,7 @@ class ClientManager final : public td::Actor {
static td::int64 get_tqueue_id(td::int64 user_id, bool is_test_dc);
static PromisedQueryPtr get_webhook_restore_query(td::Slice token, td::Slice webhook_info,
static PromisedQueryPtr get_webhook_restore_query(td::Slice token, bool is_user, td::Slice webhook_info,
std::shared_ptr<SharedData> shared_data);
void start_up() override;

View File

@ -34,6 +34,7 @@ struct SharedData {
// not thread-safe
td::ListNode query_list_;
td::unique_ptr<td::KeyValueSyncInterface> webhook_db_;
td::unique_ptr<td::KeyValueSyncInterface> user_db_;
td::unique_ptr<td::TQueue> tqueue_;
double unix_time_difference_{-1e100};
@ -58,6 +59,8 @@ struct ClientParameters {
bool allow_http_ = false;
bool use_relative_path_ = false;
bool no_file_limit_ = true;
bool allow_users_ = false;
bool allow_users_registration_ = false;
td::int32 api_id_ = 0;
td::string api_hash_;

View File

@ -28,22 +28,37 @@ void HttpConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
return send_http_error(404, "Not Found: absolute URI is specified in the Request-Line");
}
if (!url_path_parser.try_skip("/bot")) {
bool is_login = false;
bool is_user = false;
if (url_path_parser.try_skip("/bot")) {
} else if (url_path_parser.try_skip("/userlogin")) {
is_user = true;
is_login = true;
} else if (url_path_parser.try_skip("/user")) {
is_user = true;
} else {
return send_http_error(404, "Not Found");
}
auto token = url_path_parser.read_till('/');
td::MutableSlice token;
bool is_test_dc = false;
if (url_path_parser.try_skip("/test")) {
is_test_dc = true;
}
url_path_parser.skip('/');
if (url_path_parser.status().is_error()) {
return send_http_error(404, "Not Found");
td::MutableSlice method;
if (!is_login) {
token = url_path_parser.read_till('/');
is_test_dc = false;
if (url_path_parser.try_skip("/test")) {
is_test_dc = true;
}
url_path_parser.skip('/');
if (url_path_parser.status().is_error()) {
return send_http_error(404, "Not Found");
}
method = url_path_parser.data();
}
auto method = url_path_parser.data();
auto query = std::make_unique<Query>(std::move(http_query->container_), token, is_test_dc, method,
auto query = std::make_unique<Query>(std::move(http_query->container_), token, is_user, is_test_dc, method,
std::move(http_query->args_), std::move(http_query->headers_),
std::move(http_query->files_), shared_data_, http_query->peer_address_);
@ -52,7 +67,11 @@ void HttpConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
td::init_promise_future(&promise, &future);
future.set_event(td::EventCreator::yield(actor_id()));
auto promised_query = PromisedQueryPtr(query.release(), PromiseDeleter(std::move(promise)));
send_closure(client_manager_, &ClientManager::send, std::move(promised_query));
if (is_login) {
send_closure(client_manager_, &ClientManager::user_login, std::move(promised_query));
} else {
send_closure(client_manager_, &ClientManager::send, std::move(promised_query));
}
result_ = std::move(future);
}

View File

@ -22,7 +22,7 @@ namespace telegram_bot_api {
std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> empty_parameters;
Query::Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_test_dc, td::MutableSlice method,
Query::Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_user, bool is_test_dc, td::MutableSlice method,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address)
@ -31,6 +31,7 @@ Query::Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_t
, peer_address_(peer_address)
, container_(std::move(container))
, token_(token)
, is_user_(is_user)
, is_test_dc_(is_test_dc)
, method_(method)
, args_(std::move(args))

View File

@ -37,6 +37,9 @@ class Query : public td::ListNode {
td::Slice token() const {
return token_;
}
bool is_user() const {
return is_user_;
}
bool is_test_dc() const {
return is_test_dc_;
}
@ -122,7 +125,7 @@ class Query : public td::ListNode {
is_internal_ = is_internal;
}
Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_test_dc, td::MutableSlice method,
Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_user, bool is_test_dc, td::MutableSlice method,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address);
@ -155,6 +158,7 @@ class Query : public td::ListNode {
// request
td::vector<td::BufferSlice> container_;
td::Slice token_;
bool is_user_;
bool is_test_dc_;
td::MutableSlice method_;
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> args_;

View File

@ -586,7 +586,7 @@ void WebhookActor::handle(td::unique_ptr<td::HttpQuery> response) {
method != "logout" && !td::begins_with(method, "get")) {
VLOG(webhook) << "Receive request " << method << " in response to webhook";
auto query =
std::make_unique<Query>(std::move(response->container_), td::MutableSlice(), false, td::MutableSlice(),
std::make_unique<Query>(std::move(response->container_), td::MutableSlice(), false, false, td::MutableSlice(),
std::move(response->args_), std::move(response->headers_),
std::move(response->files_), parameters_->shared_data_, response->peer_address_);
auto promised_query =

View File

@ -176,6 +176,9 @@ int main(int argc, char *argv[]) {
[&] { parameters->no_file_limit_ = true; });
options.add_option('\0', "insecure", "allow the Bot API to send request via insecure HTTP", [&] { parameters->allow_http_ = true; });
options.add_option('\0', "relative", "use relative file path in local mode", [&] { parameters->use_relative_path_ = true; });
options.add_option('\0', "allow-users", "allow user accounts to use the API", [&] { parameters->allow_users_ = true; });
options.add_option('\0', "allow-users-registration", "allow user accounts to be registered on the API",
[&] { parameters->allow_users_registration_ = true; });
options.add_checked_option(
'\0', "api-id",