diff options
Diffstat (limited to 'protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp')
-rw-r--r-- | protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp | 1222 |
1 files changed, 757 insertions, 465 deletions
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp index 64915de36b..78e5e05937 100644 --- a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp +++ b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp @@ -1,313 +1,44 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/AuthManager.h" -#include "td/telegram/AuthManager.hpp" - -#include "td/telegram/td_api.h" -#include "td/telegram/telegram_api.h" +#include "td/telegram/AttachMenuManager.h" +#include "td/telegram/AuthManager.hpp" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ConfigShared.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" +#include "td/telegram/net/DcId.h" #include "td/telegram/net/NetQueryDispatcher.h" +#include "td/telegram/NewPasswordState.h" +#include "td/telegram/NotificationManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/PasswordManager.h" +#include "td/telegram/StateManager.h" +#include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/ThemeManager.h" +#include "td/telegram/TopDialogManager.h" #include "td/telegram/UpdatesManager.h" -#include "td/telegram/logevent/LogEvent.h" - -#include "td/actor/PromiseFuture.h" - -#include "td/utils/buffer.h" -#include "td/utils/crypto.h" +#include "td/utils/base64.h" +#include "td/utils/format.h" #include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/Promise.h" #include "td/utils/ScopeGuard.h" +#include "td/utils/Slice.h" #include "td/utils/Time.h" namespace td { -// SendCodeHelper -void SendCodeHelper::on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> sent_code) { - phone_registered_ = (sent_code->flags_ & SENT_CODE_FLAG_IS_USER_REGISTERED) != 0; - phone_code_hash_ = sent_code->phone_code_hash_; - sent_code_info_ = get_authentication_code_info(std::move(sent_code->type_)); - next_code_info_ = get_authentication_code_info(std::move(sent_code->next_type_)); - next_code_timestamp_ = Timestamp::in((sent_code->flags_ & SENT_CODE_FLAG_HAS_TIMEOUT) != 0 ? sent_code->timeout_ : 0); -} - -td_api::object_ptr<td_api::authorizationStateWaitCode> SendCodeHelper::get_authorization_state_wait_code() const { - return make_tl_object<td_api::authorizationStateWaitCode>(phone_registered_, get_authentication_code_info_object()); -} - -td_api::object_ptr<td_api::authenticationCodeInfo> SendCodeHelper::get_authentication_code_info_object() const { - return make_tl_object<td_api::authenticationCodeInfo>( - phone_number_, get_authentication_code_type_object(sent_code_info_), - get_authentication_code_type_object(next_code_info_), - max(static_cast<int32>(next_code_timestamp_.in() + 1 - 1e-9), 0)); -} - -Result<telegram_api::auth_resendCode> SendCodeHelper::resend_code() { - if (next_code_info_.type == AuthenticationCodeInfo::Type::None) { - return Status::Error(8, "Authentication code can't be resend"); - } - sent_code_info_ = next_code_info_; - next_code_info_ = {}; - next_code_timestamp_ = {}; - return telegram_api::auth_resendCode(phone_number_, phone_code_hash_); -} - -Result<telegram_api::auth_sendCode> SendCodeHelper::send_code(Slice phone_number, bool allow_flash_call, - bool is_current_phone_number, int32 api_id, - const string &api_hash) { - if (!phone_number_.empty()) { - return Status::Error(8, "Can't change phone"); - } - phone_number_ = phone_number.str(); - int32 flags = 0; - if (allow_flash_call) { - flags |= AUTH_SEND_CODE_FLAG_ALLOW_FLASH_CALL; - } - return telegram_api::auth_sendCode(flags, false /*ignored*/, phone_number_, is_current_phone_number, api_id, - api_hash); -} - -Result<telegram_api::account_sendChangePhoneCode> SendCodeHelper::send_change_phone_code(Slice phone_number, - bool allow_flash_call, - bool is_current_phone_number) { - phone_number_ = phone_number.str(); - int32 flags = 0; - if (allow_flash_call) { - flags |= AUTH_SEND_CODE_FLAG_ALLOW_FLASH_CALL; - } - return telegram_api::account_sendChangePhoneCode(flags, false /*ignored*/, phone_number_, is_current_phone_number); -} - -SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_info( - tl_object_ptr<telegram_api::auth_CodeType> &&code_type_ptr) { - if (code_type_ptr == nullptr) { - return AuthenticationCodeInfo(); - } - - switch (code_type_ptr->get_id()) { - case telegram_api::auth_codeTypeSms::ID: - return {AuthenticationCodeInfo::Type::Sms, 0, ""}; - case telegram_api::auth_codeTypeCall::ID: - return {AuthenticationCodeInfo::Type::Call, 0, ""}; - case telegram_api::auth_codeTypeFlashCall::ID: - return {AuthenticationCodeInfo::Type::FlashCall, 0, ""}; - default: - UNREACHABLE(); - return AuthenticationCodeInfo(); - } -} - -SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_info( - tl_object_ptr<telegram_api::auth_SentCodeType> &&sent_code_type_ptr) { - CHECK(sent_code_type_ptr != nullptr); - switch (sent_code_type_ptr->get_id()) { - case telegram_api::auth_sentCodeTypeApp::ID: { - auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeApp>(sent_code_type_ptr); - return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Message, code_type->length_, ""}; - } - case telegram_api::auth_sentCodeTypeSms::ID: { - auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeSms>(sent_code_type_ptr); - return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Sms, code_type->length_, ""}; - } - case telegram_api::auth_sentCodeTypeCall::ID: { - auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeCall>(sent_code_type_ptr); - return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Call, code_type->length_, ""}; - } - case telegram_api::auth_sentCodeTypeFlashCall::ID: { - auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeFlashCall>(sent_code_type_ptr); - return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::FlashCall, 0, code_type->pattern_}; - } - default: - UNREACHABLE(); - return AuthenticationCodeInfo(); - } -} - -tl_object_ptr<td_api::AuthenticationCodeType> SendCodeHelper::get_authentication_code_type_object( - const AuthenticationCodeInfo &authentication_code_info) { - switch (authentication_code_info.type) { - case AuthenticationCodeInfo::Type::None: - return nullptr; - case AuthenticationCodeInfo::Type::Message: - return make_tl_object<td_api::authenticationCodeTypeTelegramMessage>(authentication_code_info.length); - case AuthenticationCodeInfo::Type::Sms: - return make_tl_object<td_api::authenticationCodeTypeSms>(authentication_code_info.length); - case AuthenticationCodeInfo::Type::Call: - return make_tl_object<td_api::authenticationCodeTypeCall>(authentication_code_info.length); - case AuthenticationCodeInfo::Type::FlashCall: - return make_tl_object<td_api::authenticationCodeTypeFlashCall>(authentication_code_info.pattern); - default: - UNREACHABLE(); - return nullptr; - } -} - -// ChangePhoneNumberManager -void ChangePhoneNumberManager::get_state(uint64 query_id) { - tl_object_ptr<td_api::Object> obj; - switch (state_) { - case State::Ok: - obj = make_tl_object<td_api::ok>(); - break; - case State::WaitCode: - obj = send_code_helper_.get_authentication_code_info_object(); - break; - } - CHECK(obj); - send_closure(G()->td(), &Td::send_result, query_id, std::move(obj)); -} - -ChangePhoneNumberManager::ChangePhoneNumberManager(ActorShared<> parent) : parent_(std::move(parent)) { -} -void ChangePhoneNumberManager::change_phone_number(uint64 query_id, string phone_number, bool allow_flash_call, - bool is_current_phone_number) { - if (phone_number.empty()) { - return on_query_error(query_id, Status::Error(8, "Phone number can't be empty")); - } - auto r_send_code = send_code_helper_.send_change_phone_code(phone_number, allow_flash_call, is_current_phone_number); - if (r_send_code.is_error()) { - return on_query_error(query_id, r_send_code.move_as_error()); - } - - on_new_query(query_id); - - start_net_query(NetQueryType::SendCode, G()->net_query_creator().create(create_storer(r_send_code.move_as_ok()))); -} - -void ChangePhoneNumberManager::resend_authentication_code(uint64 query_id) { - if (state_ != State::WaitCode) { - return on_query_error(query_id, Status::Error(8, "resendAuthenticationCode unexpected")); - } - - auto r_resend_code = send_code_helper_.resend_code(); - if (r_resend_code.is_error()) { - return on_query_error(query_id, r_resend_code.move_as_error()); - } - - on_new_query(query_id); - - start_net_query(NetQueryType::SendCode, - G()->net_query_creator().create(create_storer(r_resend_code.move_as_ok()), DcId::main(), - NetQuery::Type::Common, NetQuery::AuthFlag::Off)); -} - -void ChangePhoneNumberManager::check_code(uint64 query_id, string code) { - if (state_ != State::WaitCode) { - return on_query_error(query_id, Status::Error(8, "checkAuthenticationCode unexpected")); - } - - on_new_query(query_id); - start_net_query(NetQueryType::ChangePhone, - G()->net_query_creator().create(create_storer(telegram_api::account_changePhone( - send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code)))); -} - -void ChangePhoneNumberManager::on_new_query(uint64 query_id) { - if (query_id_ != 0) { - on_query_error(Status::Error(9, "Another authorization query has started")); - } - net_query_id_ = 0; - net_query_type_ = NetQueryType::None; - query_id_ = query_id; - // TODO: cancel older net_query -} - -void ChangePhoneNumberManager::on_query_error(Status status) { - CHECK(query_id_ != 0); - auto id = query_id_; - query_id_ = 0; - net_query_id_ = 0; - net_query_type_ = NetQueryType::None; - on_query_error(id, std::move(status)); -} - -void ChangePhoneNumberManager::on_query_error(uint64 id, Status status) { - send_closure(G()->td(), &Td::send_error, id, std::move(status)); -} - -void ChangePhoneNumberManager::on_query_ok() { - CHECK(query_id_ != 0); - auto id = query_id_; - net_query_id_ = 0; - net_query_type_ = NetQueryType::None; - query_id_ = 0; - get_state(id); -} - -void ChangePhoneNumberManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_query) { - // TODO: cancel old net_query? - net_query_type_ = net_query_type; - net_query_id_ = net_query->id(); - G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this)); -} - -void ChangePhoneNumberManager::on_change_phone_result(NetQueryPtr &result) { - auto r_change_phone = fetch_result<telegram_api::account_changePhone>(result->ok()); - if (r_change_phone.is_error()) { - return on_query_error(r_change_phone.move_as_error()); - } - state_ = State::Ok; - on_query_ok(); -} - -void ChangePhoneNumberManager::on_send_code_result(NetQueryPtr &result) { - auto r_sent_code = fetch_result<telegram_api::account_sendChangePhoneCode>(result->ok()); - if (r_sent_code.is_error()) { - return on_query_error(r_sent_code.move_as_error()); - } - auto sent_code = r_sent_code.move_as_ok(); - - LOG(INFO) << "Receive " << to_string(sent_code); - - send_code_helper_.on_sent_code(std::move(sent_code)); - - state_ = State::WaitCode; - on_query_ok(); -} - -void ChangePhoneNumberManager::on_result(NetQueryPtr result) { - SCOPE_EXIT { - result->clear(); - }; - NetQueryType type = NetQueryType::None; - if (result->id() == net_query_id_) { - net_query_id_ = 0; - type = net_query_type_; - net_query_type_ = NetQueryType::None; - if (result->is_error()) { - if (query_id_ != 0) { - on_query_error(std::move(result->error())); - } - return; - } - } - switch (type) { - case NetQueryType::None: - result->ignore(); - break; - case NetQueryType::SendCode: - on_send_code_result(result); - break; - case NetQueryType::ChangePhone: - on_change_phone_result(result); - break; - } -} - -void ChangePhoneNumberManager::tear_down() { - parent_.reset(); -} - -// AuthManager AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> parent) : parent_(std::move(parent)), api_id_(api_id), api_hash_(api_hash) { string auth_str = G()->td_db()->get_binlog_pmc()->get("auth"); @@ -319,16 +50,21 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par auto my_id = ContactsManager::load_my_id(); if (my_id.is_valid()) { // just in case - G()->shared_config().set_option_integer("my_id", my_id.get()); + LOG(INFO) << "Logged in as " << my_id; + td_->option_manager_->set_option_integer("my_id", my_id.get()); update_state(State::Ok); } else { LOG(ERROR) << "Restore unknown my_id"; ContactsManager::send_get_me_query( - G()->td().get_actor_unsafe(), - PromiseCreator::lambda([this](Result<Unit> result) { update_state(State::Ok); })); + td_, PromiseCreator::lambda([this](Result<Unit> result) { update_state(State::Ok); })); } + G()->net_query_dispatcher().check_authorization_is_ok(); } else if (auth_str == "logout") { + LOG(WARNING) << "Continue to log out"; update_state(State::LoggingOut); + } else if (auth_str == "destroy") { + LOG(WARNING) << "Continue to destroy auth keys"; + update_state(State::DestroyingKeys); } else { if (!load_state()) { update_state(State::WaitPhoneNumber); @@ -338,7 +74,13 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par void AuthManager::start_up() { if (state_ == State::LoggingOut) { - start_net_query(NetQueryType::LogOut, G()->net_query_creator().create(create_storer(telegram_api::auth_logOut()))); + send_log_out_query(); + } else if (state_ == State::DestroyingKeys) { + G()->net_query_dispatcher().destroy_auth_keys(PromiseCreator::lambda([](Result<Unit> result) { + if (result.is_ok()) { + send_closure_later(G()->td(), &Td::destroy); + } + })); } } void AuthManager::tear_down() { @@ -346,15 +88,15 @@ void AuthManager::tear_down() { } bool AuthManager::is_bot() const { - return is_authorized() && is_bot_; + if (net_query_id_ != 0 && net_query_type_ == NetQueryType::BotAuthentication) { + return true; + } + return is_bot_ && was_authorized(); } -void AuthManager::set_is_bot(bool is_bot) { - if (!is_bot_ && is_bot && api_id_ == 23818) { - LOG(ERROR) << "Fix is_bot to " << is_bot; - G()->td_db()->get_binlog_pmc()->set("auth_is_bot", "true"); - is_bot_ = true; - } +bool AuthManager::was_authorized() const { + return state_ == State::Ok || state_ == State::LoggingOut || state_ == State::DestroyingKeys || + state_ == State::Closing; } bool AuthManager::is_authorized() const { @@ -363,16 +105,29 @@ bool AuthManager::is_authorized() const { tl_object_ptr<td_api::AuthorizationState> AuthManager::get_authorization_state_object(State authorization_state) const { switch (authorization_state) { - case State::Ok: - return make_tl_object<td_api::authorizationStateReady>(); - case State::WaitCode: - return send_code_helper_.get_authorization_state_wait_code(); case State::WaitPhoneNumber: return make_tl_object<td_api::authorizationStateWaitPhoneNumber>(); + case State::WaitEmailAddress: + return make_tl_object<td_api::authorizationStateWaitEmailAddress>(allow_apple_id_, allow_google_id_); + case State::WaitEmailCode: + return make_tl_object<td_api::authorizationStateWaitEmailCode>( + allow_apple_id_, allow_google_id_, email_code_info_.get_email_address_authentication_code_info_object(), + next_phone_number_login_date_); + case State::WaitCode: + return send_code_helper_.get_authorization_state_wait_code(); + case State::WaitQrCodeConfirmation: + return make_tl_object<td_api::authorizationStateWaitOtherDeviceConfirmation>("tg://login?token=" + + base64url_encode(login_token_)); case State::WaitPassword: return make_tl_object<td_api::authorizationStateWaitPassword>( wait_password_state_.hint_, wait_password_state_.has_recovery_, wait_password_state_.email_address_pattern_); + case State::WaitRegistration: + return make_tl_object<td_api::authorizationStateWaitRegistration>( + terms_of_service_.get_terms_of_service_object()); + case State::Ok: + return make_tl_object<td_api::authorizationStateReady>(); case State::LoggingOut: + case State::DestroyingKeys: return make_tl_object<td_api::authorizationStateLoggingOut>(); case State::Closing: return make_tl_object<td_api::authorizationStateClosing>(); @@ -383,6 +138,14 @@ tl_object_ptr<td_api::AuthorizationState> AuthManager::get_authorization_state_o } } +tl_object_ptr<td_api::AuthorizationState> AuthManager::get_current_authorization_state_object() const { + if (state_ == State::None) { + return nullptr; + } else { + return get_authorization_state_object(state_); + } +} + void AuthManager::get_state(uint64 query_id) { if (state_ == State::None) { pending_get_authorization_state_requests_.push_back(query_id); @@ -392,76 +155,165 @@ void AuthManager::get_state(uint64 query_id) { } void AuthManager::check_bot_token(uint64 query_id, string bot_token) { - if (state_ != State::WaitPhoneNumber && state_ != State::Ok) { - // TODO do not allow State::Ok - return on_query_error(query_id, Status::Error(8, "checkAuthenticationBotToken unexpected")); + if (state_ == State::WaitPhoneNumber && net_query_id_ == 0) { + // can ignore previous checks + was_check_bot_token_ = false; // TODO can we remove was_check_bot_token_? + } + if (state_ != State::WaitPhoneNumber) { + return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationBotToken unexpected")); } - if (!send_code_helper_.phone_number().empty()) { + if (!send_code_helper_.phone_number().empty() || was_qr_code_request_) { return on_query_error( - query_id, Status::Error(8, "Cannot set bot token after authentication beginning. You need to log out first")); + query_id, Status::Error(400, "Cannot set bot token after authentication began. You need to log out first")); } if (was_check_bot_token_ && bot_token_ != bot_token) { - return on_query_error(query_id, Status::Error(8, "Cannot change bot token. You need to log out first")); - } - if (state_ == State::Ok) { - if (!is_bot_) { - // fix old bots - const int32 AUTH_IS_BOT_FIXED_DATE = 1500940800; - if (G()->shared_config().get_option_integer("authorization_date") < AUTH_IS_BOT_FIXED_DATE) { - G()->td_db()->get_binlog_pmc()->set("auth_is_bot", "true"); - is_bot_ = true; - } - } - return send_ok(query_id); + return on_query_error(query_id, Status::Error(400, "Cannot change bot token. You need to log out first")); } on_new_query(query_id); - bot_token_ = bot_token; + bot_token_ = std::move(bot_token); was_check_bot_token_ = true; start_net_query(NetQueryType::BotAuthentication, - G()->net_query_creator().create( - create_storer(telegram_api::auth_importBotAuthorization(0, api_id_, api_hash_, bot_token_)), - DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + G()->net_query_creator().create_unauth( + telegram_api::auth_importBotAuthorization(0, api_id_, api_hash_, bot_token_))); +} + +void AuthManager::request_qr_code_authentication(uint64 query_id, vector<UserId> other_user_ids) { + if (state_ != State::WaitPhoneNumber) { + if ((state_ == State::WaitEmailAddress || state_ == State::WaitEmailCode || state_ == State::WaitCode || + state_ == State::WaitPassword || state_ == State::WaitRegistration) && + net_query_id_ == 0) { + // ok + } else { + return on_query_error(query_id, Status::Error(400, "Call to requestQrCodeAuthentication unexpected")); + } + } + if (was_check_bot_token_) { + return on_query_error( + query_id, + Status::Error(400, + "Cannot request QR code authentication after bot token was entered. You need to log out first")); + } + for (auto &other_user_id : other_user_ids) { + if (!other_user_id.is_valid()) { + return on_query_error(query_id, Status::Error(400, "Invalid user_id among other user_ids")); + } + } + + other_user_ids_ = std::move(other_user_ids); + send_code_helper_ = SendCodeHelper(); + terms_of_service_ = TermsOfService(); + was_qr_code_request_ = true; + + on_new_query(query_id); + + send_export_login_token_query(); +} + +void AuthManager::send_export_login_token_query() { + poll_export_login_code_timeout_.cancel_timeout(); + start_net_query(NetQueryType::RequestQrCode, + G()->net_query_creator().create_unauth(telegram_api::auth_exportLoginToken( + api_id_, api_hash_, UserId::get_input_user_ids(other_user_ids_)))); +} + +void AuthManager::set_login_token_expires_at(double login_token_expires_at) { + login_token_expires_at_ = login_token_expires_at; + poll_export_login_code_timeout_.cancel_timeout(); + poll_export_login_code_timeout_.set_callback(std::move(on_update_login_token_static)); + poll_export_login_code_timeout_.set_callback_data(static_cast<void *>(td_)); + poll_export_login_code_timeout_.set_timeout_at(login_token_expires_at_); +} + +void AuthManager::on_update_login_token_static(void *td) { + if (G()->close_flag()) { + return; + } + static_cast<Td *>(td)->auth_manager_->on_update_login_token(); } -void AuthManager::set_phone_number(uint64 query_id, string phone_number, bool allow_flash_call, - bool is_current_phone_number) { +void AuthManager::on_update_login_token() { + if (G()->close_flag()) { + return; + } + if (state_ != State::WaitQrCodeConfirmation) { + return; + } + + send_export_login_token_query(); +} + +void AuthManager::set_phone_number(uint64 query_id, string phone_number, + td_api::object_ptr<td_api::phoneNumberAuthenticationSettings> settings) { if (state_ != State::WaitPhoneNumber) { - if ((state_ == State::WaitCode || state_ == State::WaitPassword) && net_query_id_ == 0) { + if ((state_ == State::WaitEmailAddress || state_ == State::WaitEmailCode || state_ == State::WaitCode || + state_ == State::WaitPassword || state_ == State::WaitRegistration) && + net_query_id_ == 0) { // ok } else { - return on_query_error(query_id, Status::Error(8, "setAuthenticationPhoneNumber unexpected")); + return on_query_error(query_id, Status::Error(400, "Call to setAuthenticationPhoneNumber unexpected")); } } if (was_check_bot_token_) { return on_query_error( - query_id, Status::Error(8, "Cannot set phone number after bot token was entered. You need to log out first")); + query_id, Status::Error(400, "Cannot set phone number after bot token was entered. You need to log out first")); } if (phone_number.empty()) { - return on_query_error(query_id, Status::Error(8, "Phone number can't be empty")); + return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty")); } - auto r_send_code = - send_code_helper_.send_code(phone_number, allow_flash_call, is_current_phone_number, api_id_, api_hash_); - if (r_send_code.is_error()) { + other_user_ids_.clear(); + was_qr_code_request_ = false; + + allow_apple_id_ = false; + allow_google_id_ = false; + email_address_ = {}; + email_code_info_ = {}; + next_phone_number_login_date_ = 0; + code_ = string(); + email_code_ = {}; + + if (send_code_helper_.phone_number() != phone_number) { send_code_helper_ = SendCodeHelper(); - r_send_code = - send_code_helper_.send_code(phone_number, allow_flash_call, is_current_phone_number, api_id_, api_hash_); - if (r_send_code.is_error()) { - return on_query_error(query_id, r_send_code.move_as_error()); + terms_of_service_ = TermsOfService(); + } + + on_new_query(query_id); + + start_net_query(NetQueryType::SendCode, G()->net_query_creator().create_unauth(send_code_helper_.send_code( + std::move(phone_number), settings, api_id_, api_hash_))); +} + +void AuthManager::set_email_address(uint64 query_id, string email_address) { + if (state_ != State::WaitEmailAddress) { + if (state_ == State::WaitEmailCode && net_query_id_ == 0) { + // ok + } else { + return on_query_error(query_id, Status::Error(400, "Call to setAuthenticationEmailAddress unexpected")); } } + if (email_address.empty()) { + return on_query_error(query_id, Status::Error(400, "Email address must be non-empty")); + } + + email_address_ = std::move(email_address); on_new_query(query_id); - start_net_query(NetQueryType::SendCode, - G()->net_query_creator().create(create_storer(r_send_code.move_as_ok()), DcId::main(), - NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + start_net_query(NetQueryType::SendEmailCode, + G()->net_query_creator().create_unauth(send_code_helper_.send_verify_email_code(email_address_))); } void AuthManager::resend_authentication_code(uint64 query_id) { - if (state_ != State::WaitCode || was_check_bot_token_) { - return on_query_error(query_id, Status::Error(8, "resendAuthenticationCode unexpected")); + if (state_ != State::WaitCode) { + if (state_ == State::WaitEmailCode) { + on_new_query(query_id); + start_net_query(NetQueryType::SendEmailCode, + G()->net_query_creator().create_unauth(send_code_helper_.send_verify_email_code(email_address_))); + return; + } + + return on_query_error(query_id, Status::Error(400, "Call to resendAuthenticationCode unexpected")); } auto r_resend_code = send_code_helper_.resend_code(); @@ -471,114 +323,198 @@ void AuthManager::resend_authentication_code(uint64 query_id) { on_new_query(query_id); - start_net_query(NetQueryType::SendCode, - G()->net_query_creator().create(create_storer(r_resend_code.move_as_ok()), DcId::main(), - NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + start_net_query(NetQueryType::SendCode, G()->net_query_creator().create_unauth(r_resend_code.move_as_ok())); } -void AuthManager::check_code(uint64 query_id, string code, string first_name, string last_name) { - if (state_ != State::WaitCode) { - return on_query_error(query_id, Status::Error(8, "checkAuthenticationCode unexpected")); +void AuthManager::send_auth_sign_in_query() { + bool is_email = !email_code_.is_empty(); + int32 flags = + is_email ? telegram_api::auth_signIn::EMAIL_VERIFICATION_MASK : telegram_api::auth_signIn::PHONE_CODE_MASK; + start_net_query(NetQueryType::SignIn, + G()->net_query_creator().create_unauth(telegram_api::auth_signIn( + flags, send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code_, + is_email ? email_code_.get_input_email_verification() : nullptr))); +} + +void AuthManager::check_email_code(uint64 query_id, EmailVerification &&code) { + if (code.is_empty()) { + return on_query_error(query_id, Status::Error(400, "Code must be non-empty")); } - first_name = clean_name(first_name, MAX_NAME_LENGTH); - if (!send_code_helper_.phone_registered() && first_name.empty()) { - return on_query_error(query_id, Status::Error(8, "First name can't be empty")); + if (state_ != State::WaitEmailCode && !(state_ == State::WaitEmailAddress && code.is_email_code())) { + return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationEmailCode unexpected")); } + code_ = string(); + email_code_ = std::move(code); + on_new_query(query_id); - if (send_code_helper_.phone_registered()) { - start_net_query(NetQueryType::SignIn, - G()->net_query_creator().create( - create_storer(telegram_api::auth_signIn(send_code_helper_.phone_number().str(), - send_code_helper_.phone_code_hash().str(), code)), - DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + if (email_address_.empty()) { + send_auth_sign_in_query(); } else { - last_name = clean_name(last_name, MAX_NAME_LENGTH); start_net_query( - NetQueryType::SignUp, - G()->net_query_creator().create(create_storer(telegram_api::auth_signUp( - send_code_helper_.phone_number().str(), - send_code_helper_.phone_code_hash().str(), code, first_name, last_name)), - DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + NetQueryType::VerifyEmailAddress, + G()->net_query_creator().create_unauth(telegram_api::account_verifyEmail( + send_code_helper_.get_email_verify_purpose_login_setup(), email_code_.get_input_email_verification()))); } } +void AuthManager::check_code(uint64 query_id, string code) { + if (state_ != State::WaitCode) { + return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationCode unexpected")); + } + + code_ = std::move(code); + email_code_ = {}; + + on_new_query(query_id); + send_auth_sign_in_query(); +} + +void AuthManager::register_user(uint64 query_id, string first_name, string last_name) { + if (state_ != State::WaitRegistration) { + return on_query_error(query_id, Status::Error(400, "Call to registerUser unexpected")); + } + + on_new_query(query_id); + first_name = clean_name(first_name, MAX_NAME_LENGTH); + if (first_name.empty()) { + return on_query_error(Status::Error(400, "First name must be non-empty")); + } + + last_name = clean_name(last_name, MAX_NAME_LENGTH); + start_net_query(NetQueryType::SignUp, G()->net_query_creator().create_unauth(telegram_api::auth_signUp( + send_code_helper_.phone_number().str(), + send_code_helper_.phone_code_hash().str(), first_name, last_name))); +} + void AuthManager::check_password(uint64 query_id, string password) { if (state_ != State::WaitPassword) { - return on_query_error(query_id, Status::Error(8, "checkAuthenticationPassword unexpected")); + return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationPassword unexpected")); } - BufferSlice buf(32); - password = wait_password_state_.current_salt_ + password + wait_password_state_.current_salt_; - sha256(password, buf.as_slice()); + LOG(INFO) << "Have SRP ID " << wait_password_state_.srp_id_; on_new_query(query_id); - start_net_query(NetQueryType::CheckPassword, - G()->net_query_creator().create(create_storer(telegram_api::auth_checkPassword(std::move(buf))), - DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + password_ = std::move(password); + recovery_code_.clear(); + new_password_.clear(); + new_hint_.clear(); + start_net_query(NetQueryType::GetPassword, + G()->net_query_creator().create_unauth(telegram_api::account_getPassword())); } void AuthManager::request_password_recovery(uint64 query_id) { if (state_ != State::WaitPassword) { - return on_query_error(query_id, Status::Error(8, "requestAuthenticationPasswordRecovery unexpected")); + return on_query_error(query_id, Status::Error(400, "Call to requestAuthenticationPasswordRecovery unexpected")); } on_new_query(query_id); start_net_query(NetQueryType::RequestPasswordRecovery, - G()->net_query_creator().create(create_storer(telegram_api::auth_requestPasswordRecovery()), - DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + G()->net_query_creator().create_unauth(telegram_api::auth_requestPasswordRecovery())); +} + +void AuthManager::check_password_recovery_code(uint64 query_id, string code) { + if (state_ != State::WaitPassword) { + return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationPasswordRecoveryCode unexpected")); + } + + on_new_query(query_id); + start_net_query(NetQueryType::CheckPasswordRecoveryCode, + G()->net_query_creator().create_unauth(telegram_api::auth_checkRecoveryPassword(code))); } -void AuthManager::recover_password(uint64 query_id, string code) { +void AuthManager::recover_password(uint64 query_id, string code, string new_password, string new_hint) { if (state_ != State::WaitPassword) { - return on_query_error(query_id, Status::Error(8, "recoverAuthenticationPassword unexpected")); + return on_query_error(query_id, Status::Error(400, "Call to recoverAuthenticationPassword unexpected")); } on_new_query(query_id); + if (!new_password.empty()) { + password_.clear(); + recovery_code_ = std::move(code); + new_password_ = std::move(new_password); + new_hint_ = std::move(new_hint); + start_net_query(NetQueryType::GetPassword, + G()->net_query_creator().create_unauth(telegram_api::account_getPassword())); + return; + } start_net_query(NetQueryType::RecoverPassword, - G()->net_query_creator().create(create_storer(telegram_api::auth_recoverPassword(code)), DcId::main(), - NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + G()->net_query_creator().create_unauth(telegram_api::auth_recoverPassword(0, code, nullptr))); } -void AuthManager::logout(uint64 query_id) { +void AuthManager::log_out(uint64 query_id) { if (state_ == State::Closing) { - return on_query_error(query_id, Status::Error(8, "Already logged out")); + return on_query_error(query_id, Status::Error(400, "Already logged out")); } - if (state_ == State::LoggingOut) { - return on_query_error(query_id, Status::Error(8, "Already logging out")); + if (state_ == State::LoggingOut || state_ == State::DestroyingKeys) { + return on_query_error(query_id, Status::Error(400, "Already logging out")); } on_new_query(query_id); if (state_ != State::Ok) { - update_state(State::LoggingOut); // TODO: could skip full logout if still no authorization // TODO: send auth.cancelCode if state_ == State::WaitCode - send_closure_later(G()->td(), &Td::destroy); + LOG(WARNING) << "Destroying auth keys by user request"; + destroy_auth_keys(); on_query_ok(); } else { - LOG(INFO) << "Logging out"; + LOG(WARNING) << "Logging out by user request"; G()->td_db()->get_binlog_pmc()->set("auth", "logout"); update_state(State::LoggingOut); - start_net_query(NetQueryType::LogOut, G()->net_query_creator().create(create_storer(telegram_api::auth_logOut()))); + send_log_out_query(); } } -void AuthManager::delete_account(uint64 query_id, const string &reason) { - if (state_ != State::Ok) { - return on_query_error(query_id, Status::Error(8, "Need to log in first")); +void AuthManager::send_log_out_query() { + // we can lose authorization while logging out, but still may need to resend the request, + // so we pretend that it doesn't require authorization + auto query = G()->net_query_creator().create_unauth(telegram_api::auth_logOut()); + query->set_priority(1); + start_net_query(NetQueryType::LogOut, std::move(query)); +} + +void AuthManager::delete_account(uint64 query_id, string reason, string password) { + if (state_ != State::Ok && state_ != State::WaitPassword) { + return on_query_error(query_id, Status::Error(400, "Need to log in first")); } + if (password.empty() || state_ != State::Ok) { + on_new_query(query_id); + LOG(INFO) << "Deleting account"; + start_net_query(NetQueryType::DeleteAccount, + G()->net_query_creator().create_unauth(telegram_api::account_deleteAccount(0, reason, nullptr))); + } else { + send_closure(G()->password_manager(), &PasswordManager::get_input_check_password_srp, password, + PromiseCreator::lambda( + [actor_id = actor_id(this), query_id, reason = std::move(reason)]( + Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> r_input_password) mutable { + send_closure(actor_id, &AuthManager::do_delete_account, query_id, std::move(reason), + std::move(r_input_password)); + })); + } +} + +void AuthManager::do_delete_account(uint64 query_id, string reason, + Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> r_input_password) { + if (r_input_password.is_error()) { + return on_query_error(query_id, r_input_password.move_as_error()); + } + on_new_query(query_id); - LOG(INFO) << "Deleting account"; - update_state(State::LoggingOut); - start_net_query(NetQueryType::DeleteAccount, - G()->net_query_creator().create(create_storer(telegram_api::account_deleteAccount(reason)))); + LOG(INFO) << "Deleting account with password"; + int32 flags = telegram_api::account_deleteAccount::PASSWORD_MASK; + start_net_query(NetQueryType::DeleteAccount, G()->net_query_creator().create(telegram_api::account_deleteAccount( + flags, reason, r_input_password.move_as_ok()))); } -void AuthManager::on_closing() { - update_state(State::Closing); +void AuthManager::on_closing(bool destroy_flag) { + if (destroy_flag) { + update_state(State::LoggingOut); + } else { + update_state(State::Closing); + } } void AuthManager::on_new_query(uint64 query_id) { if (query_id_ != 0) { - on_query_error(Status::Error(9, "Another authorization query has started")); + on_query_error(Status::Error(400, "Another authorization query has started")); } net_query_id_ = 0; net_query_type_ = NetQueryType::None; @@ -595,8 +531,8 @@ void AuthManager::on_query_error(Status status) { on_query_error(id, std::move(status)); } -void AuthManager::on_query_error(uint64 id, Status status) { - send_closure(G()->td(), &Td::send_error, id, std::move(status)); +void AuthManager::on_query_error(uint64 query_id, Status status) { + send_closure(G()->td(), &Td::send_error, query_id, std::move(status)); } void AuthManager::on_query_ok() { @@ -619,6 +555,33 @@ void AuthManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_q G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this)); } +void AuthManager::on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> &&sent_code) { + auto code_type_id = sent_code->type_->get_id(); + if (code_type_id == telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID) { + auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeSetUpEmailRequired>(std::move(sent_code->type_)); + send_code_helper_.on_phone_code_hash(std::move(sent_code->phone_code_hash_)); + allow_apple_id_ = code_type->apple_signin_allowed_; + allow_google_id_ = code_type->google_signin_allowed_; + update_state(State::WaitEmailAddress, true); + } else if (code_type_id == telegram_api::auth_sentCodeTypeEmailCode::ID) { + auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeEmailCode>(std::move(sent_code->type_)); + send_code_helper_.on_phone_code_hash(std::move(sent_code->phone_code_hash_)); + allow_apple_id_ = code_type->apple_signin_allowed_; + allow_google_id_ = code_type->google_signin_allowed_; + email_address_.clear(); + email_code_info_ = SentEmailCode(std::move(code_type->email_pattern_), code_type->length_); + next_phone_number_login_date_ = td::max(static_cast<int32>(0), code_type->next_phone_login_date_); + if (email_code_info_.is_empty()) { + email_code_info_ = SentEmailCode("<unknown>", code_type->length_); + CHECK(!email_code_info_.is_empty()); + } + update_state(State::WaitEmailCode, true); + } else { + send_code_helper_.on_sent_code(std::move(sent_code)); + update_state(State::WaitCode, true); + } +} + void AuthManager::on_send_code_result(NetQueryPtr &result) { auto r_sent_code = fetch_result<telegram_api::auth_sendCode>(result->ok()); if (r_sent_code.is_error()) { @@ -627,33 +590,209 @@ void AuthManager::on_send_code_result(NetQueryPtr &result) { auto sent_code = r_sent_code.move_as_ok(); LOG(INFO) << "Receive " << to_string(sent_code); + on_sent_code(std::move(sent_code)); + on_query_ok(); +} - send_code_helper_.on_sent_code(std::move(sent_code)); +void AuthManager::on_send_email_code_result(NetQueryPtr &result) { + auto r_sent_code = fetch_result<telegram_api::account_sendVerifyEmailCode>(result->ok()); + if (r_sent_code.is_error()) { + return on_query_error(r_sent_code.move_as_error()); + } + auto sent_code = r_sent_code.move_as_ok(); + + LOG(INFO) << "Receive " << to_string(sent_code); - update_state(State::WaitCode, true); + email_code_info_ = SentEmailCode(std::move(sent_code)); + if (email_code_info_.is_empty()) { + return on_query_error(Status::Error(500, "Receive invalid response")); + } + next_phone_number_login_date_ = 0; + + update_state(State::WaitEmailCode, true); on_query_ok(); } +void AuthManager::on_verify_email_address_result(NetQueryPtr &result) { + auto r_email_verified = fetch_result<telegram_api::account_verifyEmail>(result->ok()); + if (r_email_verified.is_error()) { + return on_query_error(r_email_verified.move_as_error()); + } + auto email_verified = r_email_verified.move_as_ok(); + + LOG(INFO) << "Receive " << to_string(email_verified); + if (email_verified->get_id() != telegram_api::account_emailVerifiedLogin::ID) { + return on_query_error(Status::Error(500, "Receive invalid response")); + } + + auto verified_login = telegram_api::move_object_as<telegram_api::account_emailVerifiedLogin>(email_verified); + on_sent_code(std::move(verified_login->sent_code_)); + on_query_ok(); +} + +void AuthManager::on_request_qr_code_result(NetQueryPtr &result, bool is_import) { + Status status; + if (result->is_ok()) { + auto r_login_token = fetch_result<telegram_api::auth_exportLoginToken>(result->ok()); + if (r_login_token.is_ok()) { + auto login_token = r_login_token.move_as_ok(); + + if (is_import) { + CHECK(DcId::is_valid(imported_dc_id_)); + G()->net_query_dispatcher().set_main_dc_id(imported_dc_id_); + imported_dc_id_ = -1; + } + + on_get_login_token(std::move(login_token)); + return; + } + + status = r_login_token.move_as_error(); + } else { + status = std::move(result->error()); + } + CHECK(status.is_error()); + + LOG(INFO) << "Receive " << status << " for login token " << (is_import ? "import" : "export"); + if (is_import) { + imported_dc_id_ = -1; + } + if (query_id_ != 0) { + on_query_error(std::move(status)); + } else { + login_code_retry_delay_ = clamp(2 * login_code_retry_delay_, 1, 60); + set_login_token_expires_at(Time::now() + login_code_retry_delay_); + } +} + +void AuthManager::on_get_login_token(tl_object_ptr<telegram_api::auth_LoginToken> login_token) { + LOG(INFO) << "Receive " << to_string(login_token); + + login_code_retry_delay_ = 0; + + CHECK(login_token != nullptr); + switch (login_token->get_id()) { + case telegram_api::auth_loginToken::ID: { + auto token = move_tl_object_as<telegram_api::auth_loginToken>(login_token); + login_token_ = token->token_.as_slice().str(); + set_login_token_expires_at(Time::now() + td::max(token->expires_ - G()->server_time(), 1.0)); + update_state(State::WaitQrCodeConfirmation, true); + if (query_id_ != 0) { + on_query_ok(); + } + break; + } + case telegram_api::auth_loginTokenMigrateTo::ID: { + auto token = move_tl_object_as<telegram_api::auth_loginTokenMigrateTo>(login_token); + if (!DcId::is_valid(token->dc_id_)) { + LOG(ERROR) << "Receive wrong DC " << token->dc_id_; + return; + } + if (query_id_ != 0) { + on_query_ok(); + } + + imported_dc_id_ = token->dc_id_; + start_net_query(NetQueryType::ImportQrCode, G()->net_query_creator().create_unauth( + telegram_api::auth_importLoginToken(std::move(token->token_)), + DcId::internal(token->dc_id_))); + break; + } + case telegram_api::auth_loginTokenSuccess::ID: { + auto token = move_tl_object_as<telegram_api::auth_loginTokenSuccess>(login_token); + on_get_authorization(std::move(token->authorization_)); + break; + } + default: + UNREACHABLE(); + } +} + void AuthManager::on_get_password_result(NetQueryPtr &result) { - auto r_password = fetch_result<telegram_api::account_getPassword>(result->ok()); - if (r_password.is_error()) { + Result<telegram_api::object_ptr<telegram_api::account_password>> r_password; + if (result->is_error()) { + r_password = std::move(result->error()); + } else { + r_password = fetch_result<telegram_api::account_getPassword>(result->ok()); + } + if (r_password.is_error() && query_id_ != 0) { return on_query_error(r_password.move_as_error()); } - auto password = r_password.move_as_ok(); + auto password = r_password.is_ok() ? r_password.move_as_ok() : nullptr; + LOG(INFO) << "Receive password info: " << to_string(password); + wait_password_state_ = WaitPasswordState(); - if (password->get_id() == telegram_api::account_noPassword::ID) { - auto no_password = move_tl_object_as<telegram_api::account_noPassword>(password); - wait_password_state_.new_salt_ = no_password->new_salt_.as_slice().str(); + Result<NewPasswordState> r_new_password_state; + if (password != nullptr && password->current_algo_ != nullptr) { + switch (password->current_algo_->get_id()) { + case telegram_api::passwordKdfAlgoUnknown::ID: + return on_query_error(Status::Error(400, "Application update is needed to log in")); + case telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow::ID: { + auto algo = move_tl_object_as<telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow>( + password->current_algo_); + wait_password_state_.current_client_salt_ = algo->salt1_.as_slice().str(); + wait_password_state_.current_server_salt_ = algo->salt2_.as_slice().str(); + wait_password_state_.srp_g_ = algo->g_; + wait_password_state_.srp_p_ = algo->p_.as_slice().str(); + wait_password_state_.srp_B_ = password->srp_B_.as_slice().str(); + wait_password_state_.srp_id_ = password->srp_id_; + wait_password_state_.hint_ = std::move(password->hint_); + wait_password_state_.has_recovery_ = password->has_recovery_; + break; + } + default: + UNREACHABLE(); + } + + r_new_password_state = + get_new_password_state(std::move(password->new_algo_), std::move(password->new_secure_algo_)); + } else if (was_qr_code_request_) { + imported_dc_id_ = -1; + login_code_retry_delay_ = clamp(2 * login_code_retry_delay_, 1, 60); + set_login_token_expires_at(Time::now() + login_code_retry_delay_); + return; } else { - CHECK(password->get_id() == telegram_api::account_password::ID); - auto password_info = move_tl_object_as<telegram_api::account_password>(password); - wait_password_state_.current_salt_ = password_info->current_salt_.as_slice().str(); - wait_password_state_.new_salt_ = password_info->new_salt_.as_slice().str(); - wait_password_state_.hint_ = password_info->hint_; - wait_password_state_.has_recovery_ = password_info->has_recovery_; - } - update_state(State::WaitPassword); - on_query_ok(); + send_auth_sign_in_query(); + return; + } + + if (imported_dc_id_ != -1) { + G()->net_query_dispatcher().set_main_dc_id(imported_dc_id_); + imported_dc_id_ = -1; + } + + if (state_ == State::WaitPassword) { + if (!new_password_.empty()) { + if (r_new_password_state.is_error()) { + return on_query_error(r_new_password_state.move_as_error()); + } + + auto r_new_settings = PasswordManager::get_password_input_settings(std::move(new_password_), std::move(new_hint_), + r_new_password_state.ok()); + if (r_new_settings.is_error()) { + return on_query_error(r_new_settings.move_as_error()); + } + + int32 flags = telegram_api::auth_recoverPassword::NEW_SETTINGS_MASK; + start_net_query(NetQueryType::RecoverPassword, + G()->net_query_creator().create_unauth( + telegram_api::auth_recoverPassword(flags, recovery_code_, r_new_settings.move_as_ok()))); + return; + } + LOG(INFO) << "Have SRP ID " << wait_password_state_.srp_id_; + auto hash = PasswordManager::get_input_check_password(password_, wait_password_state_.current_client_salt_, + wait_password_state_.current_server_salt_, + wait_password_state_.srp_g_, wait_password_state_.srp_p_, + wait_password_state_.srp_B_, wait_password_state_.srp_id_); + + start_net_query(NetQueryType::CheckPassword, + G()->net_query_creator().create_unauth(telegram_api::auth_checkPassword(std::move(hash)))); + } else { + update_state(State::WaitPassword); + if (query_id_ != 0) { + on_query_ok(); + } + } } void AuthManager::on_request_password_recovery_result(NetQueryPtr &result) { @@ -663,22 +802,31 @@ void AuthManager::on_request_password_recovery_result(NetQueryPtr &result) { } auto email_address_pattern = r_email_address_pattern.move_as_ok(); CHECK(email_address_pattern->get_id() == telegram_api::auth_passwordRecovery::ID); - wait_password_state_.email_address_pattern_ = email_address_pattern->email_pattern_; + wait_password_state_.email_address_pattern_ = std::move(email_address_pattern->email_pattern_); update_state(State::WaitPassword, true); on_query_ok(); } -void AuthManager::on_authentication_result(NetQueryPtr &result, bool expected_flag) { +void AuthManager::on_check_password_recovery_code_result(NetQueryPtr &result) { + auto r_success = fetch_result<telegram_api::auth_checkRecoveryPassword>(result->ok()); + if (r_success.is_error()) { + return on_query_error(r_success.move_as_error()); + } + if (!r_success.ok()) { + return on_query_error(Status::Error(400, "Invalid recovery code")); + } + on_query_ok(); +} + +void AuthManager::on_authentication_result(NetQueryPtr &result, bool is_from_current_query) { auto r_sign_in = fetch_result<telegram_api::auth_signIn>(result->ok()); if (r_sign_in.is_error()) { - if (expected_flag && query_id_ != 0) { + if (is_from_current_query && query_id_ != 0) { return on_query_error(r_sign_in.move_as_error()); } return; } - auto sign_in = r_sign_in.move_as_ok(); - CHECK(sign_in->get_id() == telegram_api::auth_authorization::ID); - on_authorization(std::move(sign_in)); + on_get_authorization(r_sign_in.move_as_ok()); } void AuthManager::on_log_out_result(NetQueryPtr &result) { @@ -686,8 +834,10 @@ void AuthManager::on_log_out_result(NetQueryPtr &result) { if (result->is_ok()) { auto r_log_out = fetch_result<telegram_api::auth_logOut>(result->ok()); if (r_log_out.is_ok()) { - if (!r_log_out.ok()) { - status = Status::Error(500, "auth.logOut returned false!"); + auto logged_out = r_log_out.move_as_ok(); + if (!logged_out->future_auth_token_.empty()) { + td_->option_manager_->set_option_string("authentication_token", + base64url_encode(logged_out->future_auth_token_.as_slice())); } } else { status = r_log_out.move_as_error(); @@ -695,13 +845,43 @@ void AuthManager::on_log_out_result(NetQueryPtr &result) { } else { status = std::move(result->error()); } - LOG_IF(ERROR, status.is_error()) << "auth.logOut failed: " << status; - // state_ will stay logout, so no queries will work. - send_closure_later(G()->td(), &Td::destroy); + LOG_IF(ERROR, status.is_error() && status.code() != 401) << "Receive error for auth.logOut: " << status; + // state_ will stay LoggingOut, so no queries will work. + destroy_auth_keys(); if (query_id_ != 0) { on_query_ok(); } } +void AuthManager::on_authorization_lost(string source) { + if (state_ == State::LoggingOut && net_query_type_ == NetQueryType::LogOut) { + LOG(INFO) << "Ignore authorization loss because of " << source << ", while logging out"; + return; + } + if (state_ == State::Closing || state_ == State::DestroyingKeys) { + LOG(INFO) << "Ignore duplicate authorization loss because of " << source; + return; + } + LOG(WARNING) << "Lost authorization because of " << source; + destroy_auth_keys(); +} + +void AuthManager::destroy_auth_keys() { + if (state_ == State::Closing || state_ == State::DestroyingKeys) { + return; + } + update_state(State::DestroyingKeys); + auto promise = PromiseCreator::lambda([](Result<Unit> result) { + if (result.is_ok()) { + G()->net_query_dispatcher().destroy_auth_keys(PromiseCreator::lambda([](Result<Unit> result) { + if (result.is_ok()) { + send_closure_later(G()->td(), &Td::destroy); + } + })); + } + }); + G()->td_db()->get_binlog_pmc()->set("auth", "destroy"); + G()->td_db()->get_binlog_pmc()->force_sync(std::move(promise)); +} void AuthManager::on_delete_account_result(NetQueryPtr &result) { Status status; @@ -709,7 +889,7 @@ void AuthManager::on_delete_account_result(NetQueryPtr &result) { auto r_delete_account = fetch_result<telegram_api::account_deleteAccount>(result->ok()); if (r_delete_account.is_ok()) { if (!r_delete_account.ok()) { - status = Status::Error(500, "Receive false as result of the request"); + // status = Status::Error(500, "Receive false as result of the request"); } } else { status = r_delete_account.move_as_error(); @@ -717,45 +897,84 @@ void AuthManager::on_delete_account_result(NetQueryPtr &result) { } else { status = std::move(result->error()); } - if (status.is_error() && status.error().message() != "USER_DEACTIVATED") { - update_state(State::Ok); - LOG(WARNING) << "account.deleteAccount failed: " << status; + if (status.is_error() && status.message() != "USER_DEACTIVATED") { + LOG(WARNING) << "Request account.deleteAccount failed: " << status; // TODO handle some errors if (query_id_ != 0) { on_query_error(std::move(status)); } } else { - send_closure_later(G()->td(), &Td::destroy); + destroy_auth_keys(); if (query_id_ != 0) { on_query_ok(); } } } -void AuthManager::on_authorization(tl_object_ptr<telegram_api::auth_authorization> auth) { - G()->shared_config().set_option_integer("authorization_date", G()->unix_time()); +void AuthManager::on_get_authorization(tl_object_ptr<telegram_api::auth_Authorization> auth_ptr) { + if (state_ == State::Ok) { + LOG(WARNING) << "Ignore duplicated auth.Authorization"; + if (query_id_ != 0) { + on_query_ok(); + } + return; + } + CHECK(auth_ptr != nullptr); + if (auth_ptr->get_id() == telegram_api::auth_authorizationSignUpRequired::ID) { + auto sign_up_required = telegram_api::move_object_as<telegram_api::auth_authorizationSignUpRequired>(auth_ptr); + terms_of_service_ = TermsOfService(std::move(sign_up_required->terms_of_service_)); + update_state(State::WaitRegistration); + if (query_id_ != 0) { + on_query_ok(); + } + return; + } + auto auth = telegram_api::move_object_as<telegram_api::auth_authorization>(auth_ptr); + + td_->option_manager_->set_option_integer("authorization_date", G()->unix_time()); if (was_check_bot_token_) { is_bot_ = true; G()->td_db()->get_binlog_pmc()->set("auth_is_bot", "true"); } G()->td_db()->get_binlog_pmc()->set("auth", "ok"); + code_.clear(); + password_.clear(); + recovery_code_.clear(); + new_password_.clear(); + new_hint_.clear(); state_ = State::Ok; - td->contacts_manager_->on_get_user(std::move(auth->user_), true); + td_->contacts_manager_->on_get_user(std::move(auth->user_), "on_get_authorization", true); update_state(State::Ok, true); - if (!td->contacts_manager_->get_my_id("on_authorization").is_valid()) { + if (!td_->contacts_manager_->get_my_id().is_valid()) { LOG(ERROR) << "Server doesn't send proper authorization"; if (query_id_ != 0) { on_query_error(Status::Error(500, "Server doesn't send proper authorization")); } - logout(0); + log_out(0); return; } if ((auth->flags_ & telegram_api::auth_authorization::TMP_SESSIONS_MASK) != 0) { - G()->shared_config().set_option_integer("session_count", auth->tmp_sessions_); + td_->option_manager_->set_option_integer("session_count", auth->tmp_sessions_); + } + if (auth->setup_password_required_ && auth->otherwise_relogin_days_ > 0) { + td_->option_manager_->set_option_integer("otherwise_relogin_days", auth->otherwise_relogin_days_); + } + td_->attach_menu_manager_->init(); + td_->messages_manager_->on_authorization_success(); + td_->notification_manager_->init(); + td_->stickers_manager_->init(); + td_->theme_manager_->init(); + td_->top_dialog_manager_->init(); + td_->updates_manager_->get_difference("on_get_authorization"); + td_->on_online_updated(false, true); + if (!is_bot()) { + td_->schedule_get_terms_of_service(0); + td_->schedule_get_promo_data(0); + G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1"); + } else { + td_->set_is_bot_online(true); } - td->updates_manager_->get_difference("on_authorization"); - td->on_online_updated(true, true); - send_closure(G()->config_manager(), &ConfigManager::request_config); + send_closure(G()->config_manager(), &ConfigManager::request_config, false); if (query_id_ != 0) { on_query_ok(); } @@ -766,26 +985,48 @@ void AuthManager::on_result(NetQueryPtr result) { result->clear(); }; NetQueryType type = NetQueryType::None; + LOG(INFO) << "Receive result of query " << result->id() << ", expecting " << net_query_id_ << " with type " + << static_cast<int32>(net_query_type_); if (result->id() == net_query_id_) { net_query_id_ = 0; type = net_query_type_; net_query_type_ = NetQueryType::None; if (result->is_error()) { - if (type == NetQueryType::SignIn && result->error().code() == 401 && - result->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) { + if ((type == NetQueryType::SendCode || type == NetQueryType::SendEmailCode || + type == NetQueryType::VerifyEmailAddress || type == NetQueryType::SignIn || + type == NetQueryType::RequestQrCode || type == NetQueryType::ImportQrCode) && + result->error().code() == 401 && result->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) { + auto dc_id = DcId::main(); + if (type == NetQueryType::ImportQrCode) { + CHECK(DcId::is_valid(imported_dc_id_)); + dc_id = DcId::internal(imported_dc_id_); + } start_net_query(NetQueryType::GetPassword, - G()->net_query_creator().create(create_storer(telegram_api::account_getPassword()), - DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off)); + G()->net_query_creator().create_unauth(telegram_api::account_getPassword(), dc_id)); return; } - if (type != NetQueryType::LogOut) { + if (result->error().message() == CSlice("PHONE_NUMBER_BANNED")) { + LOG(PLAIN) + << "Your phone number was banned for suspicious activity. If you think that this is a mistake, please " + "write to recover@telegram.org your phone number and other details to recover the account."; + } + if (type != NetQueryType::LogOut && type != NetQueryType::DeleteAccount) { if (query_id_ != 0) { if (state_ == State::WaitPhoneNumber) { + other_user_ids_.clear(); send_code_helper_ = SendCodeHelper(); + terms_of_service_ = TermsOfService(); + was_qr_code_request_ = false; + was_check_bot_token_ = false; } on_query_error(std::move(result->error())); + return; + } + if (type != NetQueryType::RequestQrCode && type != NetQueryType::ImportQrCode && + type != NetQueryType::GetPassword) { + LOG(INFO) << "Ignore error for net query of type " << static_cast<int32>(net_query_type_); + return; } - return; } } } else if (result->is_ok() && result->ok_tl_constructor() == telegram_api::auth_authorization::ID) { @@ -808,12 +1049,27 @@ void AuthManager::on_result(NetQueryPtr result) { case NetQueryType::SendCode: on_send_code_result(result); break; + case NetQueryType::SendEmailCode: + on_send_email_code_result(result); + break; + case NetQueryType::VerifyEmailAddress: + on_verify_email_address_result(result); + break; + case NetQueryType::RequestQrCode: + on_request_qr_code_result(result, false); + break; + case NetQueryType::ImportQrCode: + on_request_qr_code_result(result, true); + break; case NetQueryType::GetPassword: on_get_password_result(result); break; case NetQueryType::RequestPasswordRecovery: on_request_password_recovery_result(result); break; + case NetQueryType::CheckPasswordRecoveryCode: + on_check_password_recovery_code_result(result); + break; case NetQueryType::LogOut: on_log_out_result(result); break; @@ -827,12 +1083,19 @@ void AuthManager::update_state(State new_state, bool force, bool should_save_sta if (state_ == new_state && !force) { return; } + bool skip_update = (state_ == State::LoggingOut || state_ == State::DestroyingKeys) && + (new_state == State::LoggingOut || new_state == State::DestroyingKeys); state_ = new_state; if (should_save_state) { save_state(); } - send_closure(G()->td(), &Td::send_update, - make_tl_object<td_api::updateAuthorizationState>(get_authorization_state_object(state_))); + if (new_state == State::LoggingOut || new_state == State::DestroyingKeys) { + send_closure(G()->state_manager(), &StateManager::on_logging_out, true); + } + if (!skip_update) { + send_closure(G()->td(), &Td::send_update, + make_tl_object<td_api::updateAuthorizationState>(get_authorization_state_object(state_))); + } if (!pending_get_authorization_state_requests_.empty()) { auto query_ids = std::move(pending_get_authorization_state_requests_); @@ -844,6 +1107,10 @@ void AuthManager::update_state(State new_state, bool force, bool should_save_sta bool AuthManager::load_state() { auto data = G()->td_db()->get_binlog_pmc()->get("auth_state"); + if (data.empty()) { + LOG(INFO) << "Have no saved auth_state. Waiting for phone number"; + return false; + } DbState db_state; auto status = log_event_parse(db_state, data); if (status.is_error()) { @@ -854,20 +1121,34 @@ bool AuthManager::load_state() { LOG(INFO) << "Ignore auth_state: api_id or api_hash changed"; return false; } - if (!db_state.state_timestamp_.is_in_past()) { - LOG(INFO) << "Ignore auth_state: timestamp in future"; - return false; - } - if (Timestamp::at(db_state.state_timestamp_.at() + 5 * 60).is_in_past()) { - LOG(INFO) << "Ignore auth_state: expired " << db_state.state_timestamp_.in(); + if (db_state.expires_at_ <= Time::now()) { + LOG(INFO) << "Ignore auth_state: expired"; return false; } - LOG(INFO) << "Load auth_state from db: " << tag("state", static_cast<int32>(db_state.state_)); - if (db_state.state_ == State::WaitCode) { + LOG(INFO) << "Load auth_state from database: " << tag("state", static_cast<int32>(db_state.state_)); + if (db_state.state_ == State::WaitEmailAddress) { + allow_apple_id_ = db_state.allow_apple_id_; + allow_google_id_ = db_state.allow_google_id_; send_code_helper_ = std::move(db_state.send_code_helper_); + } else if (db_state.state_ == State::WaitEmailCode) { + allow_apple_id_ = db_state.allow_apple_id_; + allow_google_id_ = db_state.allow_google_id_; + email_address_ = std::move(db_state.email_address_); + email_code_info_ = std::move(db_state.email_code_info_); + next_phone_number_login_date_ = db_state.next_phone_number_login_date_; + send_code_helper_ = std::move(db_state.send_code_helper_); + } else if (db_state.state_ == State::WaitCode) { + send_code_helper_ = std::move(db_state.send_code_helper_); + } else if (db_state.state_ == State::WaitQrCodeConfirmation) { + other_user_ids_ = std::move(db_state.other_user_ids_); + login_token_ = std::move(db_state.login_token_); + set_login_token_expires_at(db_state.login_token_expires_at_); } else if (db_state.state_ == State::WaitPassword) { wait_password_state_ = std::move(db_state.wait_password_state_); + } else if (db_state.state_ == State::WaitRegistration) { + send_code_helper_ = std::move(db_state.send_code_helper_); + terms_of_service_ = std::move(db_state.terms_of_service_); } else { UNREACHABLE(); } @@ -876,21 +1157,32 @@ bool AuthManager::load_state() { } void AuthManager::save_state() { - if (state_ != State::WaitCode && state_ != State::WaitPassword) { + if (state_ != State::WaitEmailAddress && state_ != State::WaitEmailCode && state_ != State::WaitCode && + state_ != State::WaitQrCodeConfirmation && state_ != State::WaitPassword && state_ != State::WaitRegistration) { if (state_ != State::Closing) { G()->td_db()->get_binlog_pmc()->erase("auth_state"); } return; } - DbState db_state; - if (state_ == State::WaitCode) { - db_state = DbState::wait_code(api_id_, api_hash_, send_code_helper_); - } else if (state_ == State::WaitPassword) { - db_state = DbState::wait_password(api_id_, api_hash_, wait_password_state_); - } else { - UNREACHABLE(); - } + DbState db_state = [&] { + if (state_ == State::WaitEmailAddress) { + return DbState::wait_email_address(api_id_, api_hash_, allow_apple_id_, allow_google_id_, send_code_helper_); + } else if (state_ == State::WaitEmailCode) { + return DbState::wait_email_code(api_id_, api_hash_, allow_apple_id_, allow_google_id_, email_address_, + email_code_info_, next_phone_number_login_date_, send_code_helper_); + } else if (state_ == State::WaitCode) { + return DbState::wait_code(api_id_, api_hash_, send_code_helper_); + } else if (state_ == State::WaitQrCodeConfirmation) { + return DbState::wait_qr_code_confirmation(api_id_, api_hash_, other_user_ids_, login_token_, + login_token_expires_at_); + } else if (state_ == State::WaitPassword) { + return DbState::wait_password(api_id_, api_hash_, wait_password_state_); + } else { + CHECK(state_ == State::WaitRegistration); + return DbState::wait_registration(api_id_, api_hash_, send_code_helper_, terms_of_service_); + } + }(); G()->td_db()->get_binlog_pmc()->set("auth_state", log_event_store(db_state).as_slice().str()); } |