// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // 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/AttachMenuManager.h" #include "td/telegram/AuthManager.hpp" #include "td/telegram/ConfigManager.h" #include "td/telegram/DialogFilterManager.h" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.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/ReactionManager.h" #include "td/telegram/SendCodeHelper.hpp" #include "td/telegram/StateManager.h" #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/TopDialogManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserManager.h" #include "td/telegram/Version.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/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" namespace td { struct AuthManager::DbState { State state_; int32 api_id_; string api_hash_; double expires_at_; // WaitEmailAddress and WaitEmailCode bool allow_apple_id_ = false; bool allow_google_id_ = false; // WaitEmailCode string email_address_; SentEmailCode email_code_info_; int32 reset_available_period_ = -1; int32 reset_pending_date_ = -1; // WaitEmailAddress, WaitEmailCode, WaitCode and WaitRegistration SendCodeHelper send_code_helper_; // WaitQrCodeConfirmation vector other_user_ids_; string login_token_; double login_token_expires_at_ = 0; // WaitPassword WaitPasswordState wait_password_state_; // WaitRegistration TermsOfService terms_of_service_; DbState() = default; static DbState wait_email_address(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, SendCodeHelper send_code_helper) { DbState state(State::WaitEmailAddress, api_id, std::move(api_hash)); state.send_code_helper_ = std::move(send_code_helper); state.allow_apple_id_ = allow_apple_id; state.allow_google_id_ = allow_google_id; return state; } static DbState wait_email_code(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, string email_address, SentEmailCode email_code_info, int32 reset_available_period, int32 reset_pending_date, SendCodeHelper send_code_helper) { DbState state(State::WaitEmailCode, api_id, std::move(api_hash)); state.send_code_helper_ = std::move(send_code_helper); state.allow_apple_id_ = allow_apple_id; state.allow_google_id_ = allow_google_id; state.email_address_ = std::move(email_address); state.email_code_info_ = std::move(email_code_info); state.reset_available_period_ = reset_available_period; state.reset_pending_date_ = reset_pending_date; return state; } static DbState wait_code(int32 api_id, string api_hash, SendCodeHelper send_code_helper) { DbState state(State::WaitCode, api_id, std::move(api_hash)); state.send_code_helper_ = std::move(send_code_helper); return state; } static DbState wait_qr_code_confirmation(int32 api_id, string api_hash, vector other_user_ids, string login_token, double login_token_expires_at) { DbState state(State::WaitQrCodeConfirmation, api_id, std::move(api_hash)); state.other_user_ids_ = std::move(other_user_ids); state.login_token_ = std::move(login_token); state.login_token_expires_at_ = login_token_expires_at; return state; } static DbState wait_password(int32 api_id, string api_hash, WaitPasswordState wait_password_state) { DbState state(State::WaitPassword, api_id, std::move(api_hash)); state.wait_password_state_ = std::move(wait_password_state); return state; } static DbState wait_registration(int32 api_id, string api_hash, SendCodeHelper send_code_helper, TermsOfService terms_of_service) { DbState state(State::WaitRegistration, api_id, std::move(api_hash)); state.send_code_helper_ = std::move(send_code_helper); state.terms_of_service_ = std::move(terms_of_service); return state; } template void store(StorerT &storer) const; template void parse(ParserT &parser); private: DbState(State state, int32 api_id, string &&api_hash) : state_(state), api_id_(api_id), api_hash_(std::move(api_hash)) { auto state_timeout = [state] { switch (state) { case State::WaitPassword: case State::WaitRegistration: return 86400; case State::WaitEmailAddress: case State::WaitEmailCode: case State::WaitCode: case State::WaitQrCodeConfirmation: return 5 * 60; default: UNREACHABLE(); return 0; } }(); expires_at_ = Time::now() + state_timeout; } }; template void AuthManager::DbState::store(StorerT &storer) const { using td::store; bool has_terms_of_service = !terms_of_service_.get_id().empty(); bool is_pbkdf2_supported = true; bool is_srp_supported = true; bool is_wait_registration_supported = true; bool is_wait_registration_stores_phone_number = true; bool is_wait_qr_code_confirmation_supported = true; bool is_time_store_supported = true; bool is_reset_email_address_supported = true; BEGIN_STORE_FLAGS(); STORE_FLAG(has_terms_of_service); STORE_FLAG(is_pbkdf2_supported); STORE_FLAG(is_srp_supported); STORE_FLAG(is_wait_registration_supported); STORE_FLAG(is_wait_registration_stores_phone_number); STORE_FLAG(is_wait_qr_code_confirmation_supported); STORE_FLAG(allow_apple_id_); STORE_FLAG(allow_google_id_); STORE_FLAG(is_time_store_supported); STORE_FLAG(is_reset_email_address_supported); END_STORE_FLAGS(); store(state_, storer); store(api_id_, storer); store(api_hash_, storer); store_time(expires_at_, storer); if (has_terms_of_service) { store(terms_of_service_, storer); } if (state_ == State::WaitEmailAddress) { store(send_code_helper_, storer); } else if (state_ == State::WaitEmailCode) { store(send_code_helper_, storer); store(email_address_, storer); store(email_code_info_, storer); store(reset_available_period_, storer); store(reset_pending_date_, storer); } else if (state_ == State::WaitCode) { store(send_code_helper_, storer); } else if (state_ == State::WaitQrCodeConfirmation) { store(other_user_ids_, storer); store(login_token_, storer); store_time(login_token_expires_at_, storer); } else if (state_ == State::WaitPassword) { store(wait_password_state_, storer); } else if (state_ == State::WaitRegistration) { store(send_code_helper_, storer); } else { UNREACHABLE(); } } template void AuthManager::DbState::parse(ParserT &parser) { using td::parse; bool has_terms_of_service = false; bool is_pbkdf2_supported = false; bool is_srp_supported = false; bool is_wait_registration_supported = false; bool is_wait_registration_stores_phone_number = false; bool is_wait_qr_code_confirmation_supported = false; bool is_time_store_supported = false; bool is_reset_email_address_supported = false; if (parser.version() >= static_cast(Version::AddTermsOfService)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_terms_of_service); PARSE_FLAG(is_pbkdf2_supported); PARSE_FLAG(is_srp_supported); PARSE_FLAG(is_wait_registration_supported); PARSE_FLAG(is_wait_registration_stores_phone_number); PARSE_FLAG(is_wait_qr_code_confirmation_supported); PARSE_FLAG(allow_apple_id_); PARSE_FLAG(allow_google_id_); PARSE_FLAG(is_time_store_supported); PARSE_FLAG(is_reset_email_address_supported); END_PARSE_FLAGS(); } if (!is_reset_email_address_supported) { return parser.set_error("Have no reset email address support"); } CHECK(is_pbkdf2_supported); CHECK(is_srp_supported); CHECK(is_wait_registration_supported); CHECK(is_wait_registration_stores_phone_number); CHECK(is_wait_qr_code_confirmation_supported); CHECK(is_time_store_supported); parse(state_, parser); parse(api_id_, parser); parse(api_hash_, parser); parse_time(expires_at_, parser); if (has_terms_of_service) { parse(terms_of_service_, parser); } if (state_ == State::WaitEmailAddress) { parse(send_code_helper_, parser); } else if (state_ == State::WaitEmailCode) { parse(send_code_helper_, parser); parse(email_address_, parser); parse(email_code_info_, parser); parse(reset_available_period_, parser); parse(reset_pending_date_, parser); } else if (state_ == State::WaitCode) { parse(send_code_helper_, parser); } else if (state_ == State::WaitQrCodeConfirmation) { parse(other_user_ids_, parser); parse(login_token_, parser); parse_time(login_token_expires_at_, parser); } else if (state_ == State::WaitPassword) { parse(wait_password_state_, parser); } else if (state_ == State::WaitRegistration) { parse(send_code_helper_, parser); } else { parser.set_error(PSTRING() << "Unexpected " << tag("state", static_cast(state_))); } } 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"); if (auth_str == "ok") { string is_bot_str = G()->td_db()->get_binlog_pmc()->get("auth_is_bot"); if (is_bot_str == "true") { is_bot_ = true; } auto my_id = UserManager::load_my_id(); if (my_id.is_valid()) { // just in case 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"; UserManager::send_get_me_query(td_, PromiseCreator::lambda([this](Result 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); } } } void AuthManager::start_up() { if (state_ == State::LoggingOut) { send_log_out_query(); } else if (state_ == State::DestroyingKeys) { G()->net_query_dispatcher().destroy_auth_keys(PromiseCreator::lambda([](Result result) { if (result.is_ok()) { send_closure_later(G()->td(), &Td::destroy); } else { LOG(INFO) << "Failed to destroy auth keys"; } })); } } void AuthManager::tear_down() { parent_.reset(); } bool AuthManager::was_authorized() const { return state_ == State::Ok || state_ == State::LoggingOut || state_ == State::DestroyingKeys || state_ == State::Closing; } bool AuthManager::is_authorized() const { return state_ == State::Ok; } tl_object_ptr AuthManager::get_authorization_state_object(State authorization_state) const { switch (authorization_state) { case State::WaitPhoneNumber: return make_tl_object(); case State::WaitEmailAddress: return make_tl_object(allow_apple_id_, allow_google_id_); case State::WaitEmailCode: { td_api::object_ptr reset_state; if (reset_pending_date_ > 0) { reset_state = td_api::make_object(max(reset_pending_date_ - G()->unix_time(), 0)); } else if (reset_available_period_ >= 0) { reset_state = td_api::make_object(reset_available_period_); } return make_tl_object( allow_apple_id_, allow_google_id_, email_code_info_.get_email_address_authentication_code_info_object(), std::move(reset_state)); } case State::WaitCode: return send_code_helper_.get_authorization_state_wait_code(); case State::WaitQrCodeConfirmation: return make_tl_object("tg://login?token=" + base64url_encode(login_token_)); case State::WaitPassword: return make_tl_object( wait_password_state_.hint_, wait_password_state_.has_recovery_, wait_password_state_.has_secure_values_, wait_password_state_.email_address_pattern_); case State::WaitRegistration: return make_tl_object( terms_of_service_.get_terms_of_service_object()); case State::Ok: return make_tl_object(); case State::LoggingOut: case State::DestroyingKeys: return make_tl_object(); case State::Closing: return make_tl_object(); case State::None: default: UNREACHABLE(); return nullptr; } } tl_object_ptr 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); } else { send_closure(G()->td(), &Td::send_result, query_id, get_authorization_state_object(state_)); } } void AuthManager::check_bot_token(uint64 query_id, string bot_token) { 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() || was_qr_code_request_) { return on_query_error( 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(400, "Cannot change bot token. You need to log out first")); } on_new_query(query_id); bot_token_ = std::move(bot_token); was_check_bot_token_ = true; start_net_query(NetQueryType::BotAuthentication, 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 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(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)->auth_manager_->on_update_login_token(); } 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 settings) { 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 setAuthenticationPhoneNumber unexpected")); } } if (was_check_bot_token_) { return on_query_error( 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(400, "Phone number must be non-empty")); } other_user_ids_.clear(); was_qr_code_request_ = false; allow_apple_id_ = false; allow_google_id_ = false; email_address_ = {}; email_code_info_ = {}; reset_available_period_ = -1; reset_pending_date_ = -1; code_ = string(); email_code_ = {}; if (send_code_helper_.phone_number() != phone_number) { send_code_helper_ = SendCodeHelper(); 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_firebase_token(uint64 query_id, string token) { if (state_ != State::WaitCode) { return on_query_error(query_id, Status::Error(400, "Call to sendAuthenticationFirebaseSms unexpected")); } on_new_query(query_id); start_net_query(NetQueryType::RequestFirebaseSms, G()->net_query_creator().create_unauth(send_code_helper_.request_firebase_sms(token))); } 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::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) { 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(); 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_unauth(r_resend_code.move_as_ok())); } 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")); } 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 (email_address_.empty()) { send_auth_sign_in_query(); } else { start_net_query( 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::reset_email_address(uint64 query_id) { if (state_ != State::WaitEmailCode) { return on_query_error(query_id, Status::Error(400, "Call to resetAuthenticationEmailAddress unexpected")); } on_new_query(query_id); start_net_query(NetQueryType::ResetEmailAddress, G()->net_query_creator().create_unauth(telegram_api::auth_resetLoginEmail( send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str()))); } 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, bool disable_notification) { 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_current_query_error(Status::Error(400, "First name must be non-empty")); } last_name = clean_name(last_name, MAX_NAME_LENGTH); int32 flags = 0; if (disable_notification) { flags |= telegram_api::auth_signUp::NO_JOINED_NOTIFICATIONS_MASK; } start_net_query(NetQueryType::SignUp, G()->net_query_creator().create_unauth(telegram_api::auth_signUp( flags, false /*ignored*/, 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(400, "Call to checkAuthenticationPassword unexpected")); } LOG(INFO) << "Have SRP ID " << wait_password_state_.srp_id_; on_new_query(query_id); checking_password_ = true; 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(400, "Call to requestAuthenticationPasswordRecovery unexpected")); } on_new_query(query_id); start_net_query(NetQueryType::RequestPasswordRecovery, 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, string new_password, string new_hint) { if (state_ != State::WaitPassword) { return on_query_error(query_id, Status::Error(400, "Call to recoverAuthenticationPassword unexpected")); } on_new_query(query_id); checking_password_ = true; 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_unauth(telegram_api::auth_recoverPassword(0, code, nullptr))); } void AuthManager::log_out(uint64 query_id) { if (state_ == State::Closing) { return on_query_error(query_id, Status::Error(400, "Already logged 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) { // TODO: could skip full logout if still no authorization // TODO: send auth.cancelCode if state_ == State::WaitCode LOG(WARNING) << "Destroying auth keys by user request"; destroy_auth_keys(); on_current_query_ok(); } else { LOG(WARNING) << "Logging out by user request"; G()->td_db()->get_binlog_pmc()->set("auth", "logout"); update_state(State::LoggingOut); send_log_out_query(); } } 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> 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> 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 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(bool destroy_flag) { auto new_state = destroy_flag ? State::LoggingOut : State::Closing; if (new_state != state_) { update_state(new_state); } } void AuthManager::on_new_query(uint64 query_id) { if (query_id_ != 0) { on_current_query_error(Status::Error(400, "Another authorization query has started")); } checking_password_ = false; net_query_id_ = 0; net_query_type_ = NetQueryType::None; query_id_ = query_id; // TODO: cancel older net_query } void AuthManager::on_current_query_error(Status status) { if (query_id_ == 0) { return; } auto id = query_id_; query_id_ = 0; net_query_id_ = 0; net_query_type_ = NetQueryType::None; checking_password_ = false; on_query_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_current_query_ok() { if (query_id_ == 0) { return; } auto id = query_id_; net_query_id_ = 0; net_query_type_ = NetQueryType::None; query_id_ = 0; send_ok(id); } void AuthManager::send_ok(uint64 query_id) { send_closure(G()->td(), &Td::send_result, query_id, td_api::make_object()); } void AuthManager::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 AuthManager::on_sent_code(telegram_api::object_ptr &&sent_code_ptr) { LOG(INFO) << "Receive " << to_string(sent_code_ptr); auto sent_code_id = sent_code_ptr->get_id(); if (sent_code_id != telegram_api::auth_sentCode::ID) { CHECK(sent_code_id == telegram_api::auth_sentCodeSuccess::ID); auto sent_code_success = move_tl_object_as(sent_code_ptr); return on_get_authorization(std::move(sent_code_success->authorization_)); } auto sent_code = telegram_api::move_object_as(sent_code_ptr); 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(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); } else if (code_type_id == telegram_api::auth_sentCodeTypeEmailCode::ID) { auto code_type = move_tl_object_as(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(); if (!code_type->email_pattern_.empty() || email_code_info_.is_empty()) { email_code_info_ = SentEmailCode(std::move(code_type->email_pattern_), code_type->length_); } reset_available_period_ = -1; reset_pending_date_ = -1; if (code_type->reset_pending_date_ > 0) { reset_pending_date_ = code_type->reset_pending_date_; } else if ((code_type->flags_ & telegram_api::auth_sentCodeTypeEmailCode::RESET_AVAILABLE_PERIOD_MASK) != 0) { reset_available_period_ = max(code_type->reset_available_period_, 0); } if (email_code_info_.is_empty()) { email_code_info_ = SentEmailCode("", code_type->length_); CHECK(!email_code_info_.is_empty()); } update_state(State::WaitEmailCode); } else { send_code_helper_.on_sent_code(std::move(sent_code)); update_state(State::WaitCode); } on_current_query_ok(); } void AuthManager::on_send_code_result(NetQueryPtr &&net_query) { auto r_sent_code = fetch_result(std::move(net_query)); if (r_sent_code.is_error()) { return on_current_query_error(r_sent_code.move_as_error()); } on_sent_code(r_sent_code.move_as_ok()); } void AuthManager::on_send_email_code_result(NetQueryPtr &&net_query) { auto r_sent_code = fetch_result(std::move(net_query)); if (r_sent_code.is_error()) { return on_current_query_error(r_sent_code.move_as_error()); } auto sent_code = r_sent_code.move_as_ok(); LOG(INFO) << "Receive " << to_string(sent_code); email_code_info_ = SentEmailCode(std::move(sent_code)); if (email_code_info_.is_empty()) { return on_current_query_error(Status::Error(500, "Receive invalid response")); } update_state(State::WaitEmailCode); on_current_query_ok(); } void AuthManager::on_verify_email_address_result(NetQueryPtr &&net_query) { auto r_email_verified = fetch_result(std::move(net_query)); if (r_email_verified.is_error()) { return on_current_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_current_query_error(Status::Error(500, "Receive invalid response")); } reset_available_period_ = -1; reset_pending_date_ = -1; auto verified_login = telegram_api::move_object_as(email_verified); on_sent_code(std::move(verified_login->sent_code_)); } void AuthManager::on_reset_email_address_result(NetQueryPtr &&net_query) { auto r_sent_code = fetch_result(std::move(net_query)); if (r_sent_code.is_error()) { if (reset_available_period_ > 0 && reset_pending_date_ == -1 && r_sent_code.error().message() == "TASK_ALREADY_EXISTS") { reset_pending_date_ = G()->unix_time() + reset_available_period_; reset_available_period_ = -1; update_state(State::WaitEmailCode); } return on_current_query_error(r_sent_code.move_as_error()); } on_sent_code(r_sent_code.move_as_ok()); } void AuthManager::on_request_qr_code_result(NetQueryPtr &&net_query, bool is_import) { auto r_login_token = fetch_result(std::move(net_query)); 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; } auto status = r_login_token.move_as_error(); LOG(INFO) << "Receive " << status << " for login token " << (is_import ? "import" : "export"); if (is_import) { imported_dc_id_ = -1; } if (query_id_ != 0) { on_current_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 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(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); on_current_query_ok(); break; } case telegram_api::auth_loginTokenMigrateTo::ID: { auto token = move_tl_object_as(login_token); if (!DcId::is_valid(token->dc_id_)) { LOG(ERROR) << "Receive wrong DC " << token->dc_id_; return; } on_current_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(login_token); on_get_authorization(std::move(token->authorization_)); break; } default: UNREACHABLE(); } } void AuthManager::on_get_password_result(NetQueryPtr &&net_query) { auto r_password = fetch_result(std::move(net_query)); if (r_password.is_error() && query_id_ != 0) { return on_current_query_error(r_password.move_as_error()); } auto password = r_password.is_ok() ? r_password.move_as_ok() : nullptr; LOG(INFO) << "Receive password info: " << to_string(password); wait_password_state_ = WaitPasswordState(); Result r_new_password_state; if (password != nullptr && password->current_algo_ != nullptr) { switch (password->current_algo_->get_id()) { case telegram_api::passwordKdfAlgoUnknown::ID: return on_current_query_error(Status::Error(400, "Application update is needed to log in")); case telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow::ID: { auto algo = move_tl_object_as( 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_; wait_password_state_.has_secure_values_ = password->has_secure_values_; 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 { 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 && checking_password_) { if (!new_password_.empty()) { if (r_new_password_state.is_error()) { return on_current_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_current_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); on_current_query_ok(); } } void AuthManager::on_request_password_recovery_result(NetQueryPtr &&net_query) { auto r_email_address_pattern = fetch_result(std::move(net_query)); if (r_email_address_pattern.is_error()) { return on_current_query_error(r_email_address_pattern.move_as_error()); } auto email_address_pattern = r_email_address_pattern.move_as_ok(); wait_password_state_.email_address_pattern_ = std::move(email_address_pattern->email_pattern_); update_state(State::WaitPassword); on_current_query_ok(); } void AuthManager::on_check_password_recovery_code_result(NetQueryPtr &&net_query) { auto r_success = fetch_result(std::move(net_query)); if (r_success.is_error()) { return on_current_query_error(r_success.move_as_error()); } if (!r_success.ok()) { return on_current_query_error(Status::Error(400, "Invalid recovery code")); } on_current_query_ok(); } void AuthManager::on_request_firebase_sms_result(NetQueryPtr &&net_query) { auto r_bool = fetch_result(std::move(net_query)); if (r_bool.is_error()) { return on_current_query_error(r_bool.move_as_error()); } on_current_query_ok(); } void AuthManager::on_authentication_result(NetQueryPtr &&net_query, bool is_from_current_query) { auto r_sign_in = fetch_result(std::move(net_query)); if (r_sign_in.is_error()) { if (is_from_current_query) { return on_current_query_error(r_sign_in.move_as_error()); } return; } on_get_authorization(r_sign_in.move_as_ok()); } void AuthManager::on_log_out_result(NetQueryPtr &&net_query) { auto r_log_out = fetch_result(std::move(net_query)); if (r_log_out.is_ok()) { 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 if (r_log_out.error().code() != 401) { LOG(ERROR) << "Receive error for auth.logOut: " << r_log_out.error(); } destroy_auth_keys(); on_current_query_ok(); } void AuthManager::on_account_banned() const { if (is_bot()) { return; } LOG(ERROR) << "Your account was banned for suspicious activity. If you think that this is a mistake, please try to " "log in from an official mobile app and send a email to recover the account by following instructions " "provided by the app"; } 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; if (source == "USER_DEACTIVATED_BAN") { on_account_banned(); } destroy_auth_keys(); } void AuthManager::destroy_auth_keys() { if (state_ == State::Closing || state_ == State::DestroyingKeys) { LOG(INFO) << "Already destroying auth keys"; return; } update_state(State::DestroyingKeys); G()->td_db()->get_binlog_pmc()->set("auth", "destroy"); G()->net_query_dispatcher().destroy_auth_keys(PromiseCreator::lambda([](Result result) { if (result.is_ok()) { send_closure_later(G()->td(), &Td::destroy); } else { LOG(INFO) << "Failed to destroy auth keys"; } })); } void AuthManager::on_delete_account_result(NetQueryPtr &&net_query) { auto r_delete_account = fetch_result(std::move(net_query)); if (r_delete_account.is_ok()) { if (!r_delete_account.ok()) { // status = Status::Error(500, "Receive false as result of the request"); } } else { auto status = r_delete_account.move_as_error(); if (status.message() != "USER_DEACTIVATED") { LOG(WARNING) << "Request account.deleteAccount failed: " << status; // TODO handle some errors return on_current_query_error(std::move(status)); } } destroy_auth_keys(); on_current_query_ok(); } void AuthManager::on_get_authorization(tl_object_ptr auth_ptr) { if (state_ == State::Ok) { LOG(WARNING) << "Ignore duplicate auth.Authorization"; return on_current_query_ok(); } CHECK(auth_ptr != nullptr); if (auth_ptr->get_id() == telegram_api::auth_authorizationSignUpRequired::ID) { auto sign_up_required = telegram_api::move_object_as(auth_ptr); terms_of_service_ = TermsOfService(std::move(sign_up_required->terms_of_service_)); update_state(State::WaitRegistration); return on_current_query_ok(); } auto auth = telegram_api::move_object_as(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; if (auth->user_->get_id() == telegram_api::user::ID) { auto *user = static_cast(auth->user_.get()); int32 mask = 1 << 10; if ((user->flags_ & mask) == 0) { LOG(ERROR) << "Receive invalid authorization for " << to_string(auth->user_); user->flags_ |= mask; user->self_ = true; } } td_->user_manager_->on_get_user(std::move(auth->user_), "on_get_authorization"); update_state(State::Ok); if (!td_->user_manager_->get_my_id().is_valid()) { LOG(ERROR) << "Server didsn't send proper authorization"; on_current_query_error(Status::Error(500, "Server didn't send proper authorization")); log_out(0); return; } if (auth->tmp_sessions_ > 0) { 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_); } if (!auth->future_auth_token_.empty()) { td_->option_manager_->set_option_string("authentication_token", base64url_encode(auth->future_auth_token_.as_slice())); } td_->attach_menu_manager_->init(); td_->messages_manager_->on_authorization_success(); td_->dialog_filter_manager_->on_authorization_success(); // must be after MessagesManager::on_authorization_success() // to have folders created td_->notification_manager_->init(); td_->reaction_manager_->init(); td_->stickers_manager_->init(); td_->theme_manager_->init(); td_->top_dialog_manager_->init(); td_->updates_manager_->get_difference("on_get_authorization"); if (!is_bot()) { td_->on_online_updated(false, true); td_->schedule_get_terms_of_service(0); td_->reload_promo_data(); G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1"); } else { td_->set_is_bot_online(true); } send_closure(G()->config_manager(), &ConfigManager::request_config, false); on_current_query_ok(); } void AuthManager::on_result(NetQueryPtr net_query) { NetQueryType type = NetQueryType::None; LOG(INFO) << "Receive result of query " << net_query->id() << ", expecting " << net_query_id_ << " with type " << static_cast(net_query_type_); if (net_query->id() == net_query_id_) { net_query_id_ = 0; type = net_query_type_; net_query_type_ = NetQueryType::None; if (net_query->is_error()) { if ((type == NetQueryType::SendCode || type == NetQueryType::SendEmailCode || type == NetQueryType::VerifyEmailAddress || type == NetQueryType::SignIn || type == NetQueryType::RequestQrCode || type == NetQueryType::ImportQrCode) && net_query->error().code() == 401 && net_query->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_); } net_query->clear(); start_net_query(NetQueryType::GetPassword, G()->net_query_creator().create_unauth(telegram_api::account_getPassword(), dc_id)); return; } if (net_query->error().message() == CSlice("PHONE_NUMBER_BANNED")) { on_account_banned(); } 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_current_query_error(net_query->move_as_error()); return; } if (type != NetQueryType::RequestQrCode && type != NetQueryType::ImportQrCode && type != NetQueryType::GetPassword) { LOG(INFO) << "Ignore error for net query of type " << static_cast(type); type = NetQueryType::None; } } } } else if (net_query->is_ok() && net_query->ok_tl_constructor() == telegram_api::auth_authorization::ID) { type = NetQueryType::Authentication; } switch (type) { case NetQueryType::None: net_query->clear(); break; case NetQueryType::SignIn: case NetQueryType::SignUp: case NetQueryType::BotAuthentication: case NetQueryType::CheckPassword: case NetQueryType::RecoverPassword: on_authentication_result(std::move(net_query), true); break; case NetQueryType::Authentication: on_authentication_result(std::move(net_query), false); break; case NetQueryType::SendCode: on_send_code_result(std::move(net_query)); break; case NetQueryType::SendEmailCode: on_send_email_code_result(std::move(net_query)); break; case NetQueryType::VerifyEmailAddress: on_verify_email_address_result(std::move(net_query)); break; case NetQueryType::ResetEmailAddress: on_reset_email_address_result(std::move(net_query)); break; case NetQueryType::RequestQrCode: on_request_qr_code_result(std::move(net_query), false); break; case NetQueryType::ImportQrCode: on_request_qr_code_result(std::move(net_query), true); break; case NetQueryType::GetPassword: on_get_password_result(std::move(net_query)); break; case NetQueryType::RequestPasswordRecovery: on_request_password_recovery_result(std::move(net_query)); break; case NetQueryType::CheckPasswordRecoveryCode: on_check_password_recovery_code_result(std::move(net_query)); break; case NetQueryType::RequestFirebaseSms: on_request_firebase_sms_result(std::move(net_query)); break; case NetQueryType::LogOut: on_log_out_result(std::move(net_query)); break; case NetQueryType::DeleteAccount: on_delete_account_result(std::move(net_query)); break; default: UNREACHABLE(); } } void AuthManager::update_state(State new_state, bool should_save_state) { 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(); } 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(get_authorization_state_object(state_))); } if (!pending_get_authorization_state_requests_.empty()) { auto query_ids = std::move(pending_get_authorization_state_requests_); for (auto query_id : query_ids) { send_closure(G()->td(), &Td::send_result, query_id, get_authorization_state_object(state_)); } } } 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()) { LOG(INFO) << "Ignore auth_state: " << status; return false; } if (db_state.api_id_ != api_id_ || db_state.api_hash_ != api_hash_) { LOG(INFO) << "Ignore auth_state: api_id or api_hash changed"; return false; } if (db_state.expires_at_ <= Time::now()) { LOG(INFO) << "Ignore auth_state: expired"; return false; } LOG(INFO) << "Load auth_state from database: " << tag("state", static_cast(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_); reset_available_period_ = db_state.reset_available_period_; reset_pending_date_ = db_state.reset_pending_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(); } update_state(db_state.state_, false); return true; } void AuthManager::save_state() { 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::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_, reset_available_period_, reset_pending_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()); } } // namespace td