// // 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/AttachMenuManager.h" #include "td/telegram/AuthManager.hpp" #include "td/telegram/ConfigManager.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/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 { 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 = ContactsManager::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"; ContactsManager::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); } })); } } void AuthManager::tear_down() { parent_.reset(); } bool AuthManager::is_bot() const { if (net_query_id_ != 0 && net_query_type_ == NetQueryType::BotAuthentication) { return true; } return is_bot_ && was_authorized(); } 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: return make_tl_object( 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("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_.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_ = {}; next_phone_number_login_date_ = 0; 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_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) { 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::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(400, "Call to checkAuthenticationPassword unexpected")); } LOG(INFO) << "Have SRP ID " << wait_password_state_.srp_id_; on_new_query(query_id); 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); 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_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) { 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(400, "Another authorization query has started")); } net_query_id_ = 0; net_query_type_ = NetQueryType::None; query_id_ = query_id; // TODO: cancel older net_query } void AuthManager::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 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() { CHECK(query_id_ != 0); 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) { 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, true); } 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(); email_code_info_ = SentEmailCode(std::move(code_type->email_pattern_), code_type->length_); next_phone_number_login_date_ = td::max(static_cast(0), code_type->next_phone_login_date_); if (email_code_info_.is_empty()) { email_code_info_ = SentEmailCode("", 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(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); on_sent_code(std::move(sent_code)); on_query_ok(); } void AuthManager::on_send_email_code_result(NetQueryPtr &result) { auto r_sent_code = fetch_result(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); 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(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(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(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 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, true); if (query_id_ != 0) { on_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; } 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(login_token); on_get_authorization(std::move(token->authorization_)); break; } default: UNREACHABLE(); } } void AuthManager::on_get_password_result(NetQueryPtr &result) { Result> r_password; if (result->is_error()) { r_password = std::move(result->error()); } else { r_password = fetch_result(result->ok()); } if (r_password.is_error() && query_id_ != 0) { return on_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_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_; 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) { 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) { auto r_email_address_pattern = fetch_result(result->ok()); if (r_email_address_pattern.is_error()) { return on_query_error(r_email_address_pattern.move_as_error()); } 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_ = std::move(email_address_pattern->email_pattern_); update_state(State::WaitPassword, true); on_query_ok(); } void AuthManager::on_check_password_recovery_code_result(NetQueryPtr &result) { auto r_success = fetch_result(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(result->ok()); if (r_sign_in.is_error()) { if (is_from_current_query && query_id_ != 0) { return on_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 &result) { Status status; if (result->is_ok()) { auto r_log_out = fetch_result(result->ok()); 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 { status = r_log_out.move_as_error(); } } else { status = std::move(result->error()); } LOG_IF(ERROR, status.is_error() && status.error().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 result) { if (result.is_ok()) { G()->net_query_dispatcher().destroy_auth_keys(PromiseCreator::lambda([](Result 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; if (result->is_ok()) { auto r_delete_account = fetch_result(result->ok()); if (r_delete_account.is_ok()) { if (!r_delete_account.ok()) { // status = Status::Error(500, "Receive false as result of the request"); } } else { status = r_delete_account.move_as_error(); } } else { status = std::move(result->error()); } if (status.is_error() && status.error().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 { destroy_auth_keys(); if (query_id_ != 0) { on_query_ok(); } } } void AuthManager::on_get_authorization(tl_object_ptr 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(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(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_), "on_get_authorization", true); update_state(State::Ok, true); 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")); } log_out(0); return; } if ((auth->flags_ & telegram_api::auth_authorization::TMP_SESSIONS_MASK) != 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_); } 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); } send_closure(G()->config_manager(), &ConfigManager::request_config, false); if (query_id_ != 0) { on_query_ok(); } } void AuthManager::on_result(NetQueryPtr result) { SCOPE_EXIT { result->clear(); }; NetQueryType type = NetQueryType::None; LOG(INFO) << "Receive result of query " << result->id() << ", expecting " << net_query_id_ << " with type " << static_cast(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::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_unauth(telegram_api::account_getPassword(), dc_id)); return; } 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(net_query_type_); return; } } } } else if (result->is_ok() && result->ok_tl_constructor() == telegram_api::auth_authorization::ID) { type = NetQueryType::Authentication; } switch (type) { case NetQueryType::None: result->ignore(); break; case NetQueryType::SignIn: case NetQueryType::SignUp: case NetQueryType::BotAuthentication: case NetQueryType::CheckPassword: case NetQueryType::RecoverPassword: on_authentication_result(result, true); break; case NetQueryType::Authentication: on_authentication_result(result, false); break; 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; case NetQueryType::DeleteAccount: on_delete_account_result(result); break; } } void AuthManager::update_state(State new_state, bool force, bool should_save_state) { 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(); } 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.state_timestamp_.is_in_past()) { LOG(INFO) << "Ignore auth_state: timestamp in the future"; return false; } auto state_timeout = [state = db_state.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; } }(); if (Timestamp::at(db_state.state_timestamp_.at() + state_timeout).is_in_past()) { LOG(INFO) << "Ignore auth_state: expired " << db_state.state_timestamp_.in(); 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_); 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(); } update_state(db_state.state_, false, 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_, 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()); } } // namespace td