Support QR code authentication.

GitOrigin-RevId: 140381ee80339b5ac2e15f149ad3a37688200e88
This commit is contained in:
levlam 2019-12-17 19:17:57 +03:00
parent 3a203e6674
commit 9f60d0bae1
16 changed files with 372 additions and 73 deletions

View File

@ -86,12 +86,15 @@ authorizationStateWaitTdlibParameters = AuthorizationState;
//@description TDLib needs an encryption key to decrypt the local database @is_encrypted True, if the database is currently encrypted
authorizationStateWaitEncryptionKey is_encrypted:Bool = AuthorizationState;
//@description TDLib needs the user's phone number to authorize
//@description TDLib needs the user's phone number to authorize. Call `setAuthenticationPhoneNumber` to provide the phone number, or use `requestQrCodeAuthentication`, or `checkAuthenticationBotToken` for other authentication options
authorizationStateWaitPhoneNumber = AuthorizationState;
//@description TDLib needs the user's authentication code to authorize @code_info Information about the authorization code that was sent
authorizationStateWaitCode code_info:authenticationCodeInfo = AuthorizationState;
//@description The user needs to confirm authorization on another logged in device by scanning a QR code with the provided link @link A tg:// URL for the QR code. The link will be updated frequently
authorizationStateWaitOtherDeviceConfirmation link:string = AuthorizationState;
//@description The user is unregistered and need to accept terms of service and enter their first name and last name to finish registration @terms_of_service Telegram terms of service
authorizationStateWaitRegistration terms_of_service:termsOfService = AuthorizationState;
@ -3012,6 +3015,9 @@ resendAuthenticationCode = Ok;
//@description Checks the authentication code. Works only when the current authorization state is authorizationStateWaitCode @code The verification code received via SMS, Telegram message, phone call, or flash call
checkAuthenticationCode code:string = Ok;
//@description Requests QR code authentication by scanning a QR code on another logged in device. Works only when the current authorization state is authorizationStateWaitPhoneNumber @other_user_ids List of user identifiers of other users currently using the client
requestQrCodeAuthentication other_user_ids:vector<int32> = Ok;
//@description Finishes user registration. Works only when the current authorization state is authorizationStateWaitRegistration
//@first_name The first name of the user; 1-64 characters @last_name The last name of the user; 0-64 characters
registerUser first_name:string last_name:string = Ok;
@ -3038,6 +3044,10 @@ close = Ok;
destroy = Ok;
//@description Confirms QR code authentication on another device. Returns created session on success @link A link from a QR code. The link must be scanned by the in-app camera
confirmQrCodeAuthentication link:string = Session;
//@description Returns all updates needed to restore current TDLib state, i.e. all actual UpdateAuthorizationState/UpdateUser/UpdateNewChat and others. This is especially usefull if TDLib is run in a separate process. This is an offline method. Can be called before authorization
getCurrentState = Updates;

Binary file not shown.

View File

@ -1076,8 +1076,6 @@ auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
auth.loginTokenInfo#5203303a dc_id:int auth_key_id:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string ip:string region:string = auth.LoginTokenInfo;
account.contentSettings#57e28221 flags:# sensitive_enabled:flags.0?true sensitive_can_change:flags.1?true = account.ContentSettings;
messages.inactiveChats#a927fec5 dates:Vector<int> chats:Vector<Chat> users:Vector<User> = messages.InactiveChats;
@ -1109,8 +1107,7 @@ auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool;
auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector<long> = Bool;
auth.exportLoginToken#b1b41517 api_id:int api_hash:string except_ids:Vector<int> = auth.LoginToken;
auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken;
auth.acceptLoginToken#42e731b9 token:bytes = Updates;
auth.checkLoginToken#7d4fd4b0 token:bytes = auth.LoginTokenInfo;
auth.acceptLoginToken#e894ad4d token:bytes = Authorization;
account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<int> = Bool;
account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector<int> = Bool;

Binary file not shown.

View File

@ -28,6 +28,7 @@
#include "td/actor/PromiseFuture.h"
#include "td/utils/base64.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/ScopeGuard.h"
@ -96,18 +97,21 @@ 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::WaitRegistration:
return make_tl_object<td_api::authorizationStateWaitRegistration>(
terms_of_service_.get_terms_of_service_object());
case State::WaitCode:
return send_code_helper_.get_authorization_state_wait_code();
case State::WaitQrCodeConfirmation:
return make_tl_object<td_api::authorizationStateWaitOtherDeviceConfirmation>("tg://login?" +
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>();
@ -143,9 +147,9 @@ 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"));
return on_query_error(query_id, Status::Error(8, "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"));
}
@ -173,6 +177,69 @@ void AuthManager::check_bot_token(uint64 query_id, string bot_token) {
DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
}
void AuthManager::request_qr_code_authentication(uint64 query_id, vector<int32> other_user_ids) {
if (state_ != State::WaitPhoneNumber) {
if ((state_ == State::WaitCode || state_ == State::WaitPassword || state_ == State::WaitRegistration) &&
net_query_id_ == 0) {
// ok
} else {
return on_query_error(query_id, Status::Error(8, "Call to requestQrCodeAuthentication unexpected"));
}
}
if (was_check_bot_token_) {
return on_query_error(
query_id,
Status::Error(8,
"Cannot request QR code authentication after bot token was entered. You need to log out first"));
}
for (auto &other_user_id : other_user_ids) {
UserId user_id(other_user_id);
if (!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(create_storer(telegram_api::auth_exportLoginToken(
api_id_, api_hash_, vector<int32>(other_user_ids_))),
DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
}
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 *>(G()->td().get_actor_unsafe()));
poll_export_login_code_timeout_.set_timeout_at(login_token_expires_at_);
}
void AuthManager::on_update_login_token_static(void *td) {
static_cast<Td *>(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<td_api::phoneNumberAuthenticationSettings> settings) {
if (state_ != State::WaitPhoneNumber) {
@ -180,7 +247,7 @@ void AuthManager::set_phone_number(uint64 query_id, string phone_number,
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(8, "Call to setAuthenticationPhoneNumber unexpected"));
}
}
if (was_check_bot_token_) {
@ -191,6 +258,9 @@ void AuthManager::set_phone_number(uint64 query_id, string phone_number,
return on_query_error(query_id, Status::Error(8, "Phone number can't be empty"));
}
other_user_ids_.clear();
was_qr_code_request_ = false;
auto r_send_code = send_code_helper_.send_code(phone_number, settings, api_id_, api_hash_);
if (r_send_code.is_error()) {
send_code_helper_ = SendCodeHelper();
@ -203,15 +273,14 @@ void AuthManager::set_phone_number(uint64 query_id, string phone_number,
on_new_query(query_id);
auto unique_id = UniqueId::next();
start_net_query(NetQueryType::SendCode,
G()->net_query_creator().create(unique_id, create_storer(r_send_code.move_as_ok()), DcId::main(),
G()->net_query_creator().create(create_storer(r_send_code.move_as_ok()), DcId::main(),
NetQuery::Type::Common, NetQuery::AuthFlag::Off));
}
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) {
return on_query_error(query_id, Status::Error(8, "Call to resendAuthenticationCode unexpected"));
}
auto r_resend_code = send_code_helper_.resend_code();
@ -228,7 +297,7 @@ void AuthManager::resend_authentication_code(uint64 query_id) {
void AuthManager::check_code(uint64 query_id, string code) {
if (state_ != State::WaitCode) {
return on_query_error(query_id, Status::Error(8, "checkAuthenticationCode unexpected"));
return on_query_error(query_id, Status::Error(8, "Call to checkAuthenticationCode unexpected"));
}
code_ = std::move(code);
@ -242,7 +311,7 @@ void AuthManager::check_code(uint64 query_id, string code) {
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(8, "registerUser unexpected"));
return on_query_error(query_id, Status::Error(8, "Call to registerUser unexpected"));
}
on_new_query(query_id);
@ -262,7 +331,7 @@ void AuthManager::register_user(uint64 query_id, string first_name, string last_
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(8, "Call to checkAuthenticationPassword unexpected"));
}
LOG(INFO) << "Have SRP id " << wait_password_state_.srp_id_;
@ -275,7 +344,7 @@ void AuthManager::check_password(uint64 query_id, string password) {
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(8, "Call to requestAuthenticationPasswordRecovery unexpected"));
}
on_new_query(query_id);
@ -286,7 +355,7 @@ void AuthManager::request_password_recovery(uint64 query_id) {
void AuthManager::recover_password(uint64 query_id, string code) {
if (state_ != State::WaitPassword) {
return on_query_error(query_id, Status::Error(8, "recoverAuthenticationPassword unexpected"));
return on_query_error(query_id, Status::Error(8, "Call to recoverAuthenticationPassword unexpected"));
}
on_new_query(query_id);
@ -381,10 +450,6 @@ void AuthManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_q
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()) {
if (r_sent_code.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.";
}
return on_query_error(r_sent_code.move_as_error());
}
auto sent_code = r_sent_code.move_as_ok();
@ -397,6 +462,85 @@ void AuthManager::on_send_code_result(NetQueryPtr &result) {
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_ = td::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(
create_storer(telegram_api::auth_importLoginToken(std::move(token->token_))),
DcId::internal(token->dc_id_), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
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()) {
@ -427,6 +571,8 @@ void AuthManager::on_get_password_result(NetQueryPtr &result) {
default:
UNREACHABLE();
}
} else if (was_qr_code_request_) {
send_export_login_token_query();
} else {
start_net_query(NetQueryType::SignIn,
G()->net_query_creator().create(
@ -464,15 +610,15 @@ void AuthManager::on_request_password_recovery_result(NetQueryPtr &result) {
on_query_ok();
}
void AuthManager::on_authentication_result(NetQueryPtr &result, bool expected_flag) {
void AuthManager::on_authentication_result(NetQueryPtr &result, bool is_error_expected) {
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_error_expected && query_id_ != 0) {
return on_query_error(r_sign_in.move_as_error());
}
return;
}
on_authorization(r_sign_in.move_as_ok());
on_get_authorization(r_sign_in.move_as_ok());
}
void AuthManager::on_log_out_result(NetQueryPtr &result) {
@ -543,7 +689,7 @@ void AuthManager::on_delete_account_result(NetQueryPtr &result) {
}
}
void AuthManager::on_authorization(tl_object_ptr<telegram_api::auth_Authorization> auth_ptr) {
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) {
@ -572,7 +718,7 @@ void AuthManager::on_authorization(tl_object_ptr<telegram_api::auth_Authorizatio
code_.clear();
password_.clear();
state_ = State::Ok;
td->contacts_manager_->on_get_user(std::move(auth->user_), "on_authorization", 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().is_valid()) {
LOG(ERROR) << "Server doesn't send proper authorization";
@ -587,7 +733,7 @@ void AuthManager::on_authorization(tl_object_ptr<telegram_api::auth_Authorizatio
}
td->notification_manager_->init();
send_closure(td->top_dialog_manager_, &TopDialogManager::do_start_up);
td->updates_manager_->get_difference("on_authorization");
td->updates_manager_->get_difference("on_get_authorization");
td->on_online_updated(false, true);
td->schedule_get_terms_of_service(0);
if (!is_bot()) {
@ -604,27 +750,40 @@ 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::SignIn || type == NetQueryType::RequestQrCode || type == NetQueryType::ImportQrCode) &&
result->error().code() == 401 && result->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) {
start_net_query(NetQueryType::GetPassword,
G()->net_query_creator().create(create_storer(telegram_api::account_getPassword()),
DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
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) {
return;
}
return;
}
}
} else if (result->is_ok() && result->ok_tl_constructor() == telegram_api::auth_authorization::ID) {
@ -647,6 +806,12 @@ void AuthManager::on_result(NetQueryPtr result) {
case NetQueryType::SendCode:
on_send_code_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;
@ -708,6 +873,10 @@ bool AuthManager::load_state() {
LOG(INFO) << "Load auth_state from database: " << tag("state", static_cast<int32>(db_state.state_));
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) {
@ -721,7 +890,8 @@ bool AuthManager::load_state() {
}
void AuthManager::save_state() {
if (state_ != State::WaitCode && state_ != State::WaitPassword && state_ != State::WaitRegistration) {
if (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");
}
@ -731,6 +901,9 @@ void AuthManager::save_state() {
DbState db_state = [&] {
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 {

View File

@ -15,6 +15,8 @@
#include "td/telegram/telegram_api.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/actor/Timeout.h"
#include "td/utils/common.h"
#include "td/utils/Status.h"
@ -37,6 +39,7 @@ class AuthManager : public NetActor {
void resend_authentication_code(uint64 query_id);
void check_code(uint64 query_id, string code);
void register_user(uint64 query_id, string first_name, string last_name);
void request_qr_code_authentication(uint64 query_id, vector<int32> other_user_ids);
void check_bot_token(uint64 query_id, string bot_token);
void check_password(uint64 query_id, string password);
void request_password_recovery(uint64 query_id);
@ -44,6 +47,8 @@ class AuthManager : public NetActor {
void logout(uint64 query_id);
void delete_account(uint64 query_id, const string &reason);
void on_update_login_token();
void on_authorization_lost();
void on_closing(bool destroy_flag);
@ -57,6 +62,7 @@ class AuthManager : public NetActor {
None,
WaitPhoneNumber,
WaitCode,
WaitQrCodeConfirmation,
WaitPassword,
WaitRegistration,
Ok,
@ -69,6 +75,8 @@ class AuthManager : public NetActor {
SignIn,
SignUp,
SendCode,
RequestQrCode,
ImportQrCode,
GetPassword,
CheckPassword,
RequestPasswordRecovery,
@ -105,6 +113,11 @@ class AuthManager : public NetActor {
// WaitCode
SendCodeHelper send_code_helper_;
// WaitQrCodeConfirmation
vector<int32> other_user_ids_;
string login_token_;
double login_token_expires_at_ = 0;
// WaitPassword
WaitPasswordState wait_password_state_;
@ -118,6 +131,15 @@ class AuthManager : public NetActor {
return state;
}
static DbState wait_qr_code_confirmation(int32 api_id, string api_hash, vector<int32> other_user_ids,
string login_token, double login_token_expires_at) {
DbState state(State::WaitQrCodeConfirmation, api_id, 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, api_hash);
state.wait_password_state_ = std::move(wait_password_state);
@ -157,6 +179,12 @@ class AuthManager : public NetActor {
SendCodeHelper send_code_helper_;
string code_;
// State::WaitQrCodeConfirmation
vector<int32> other_user_ids_;
string login_token_;
double login_token_expires_at_ = 0.0;
int32 imported_dc_id_ = -1;
// State::WaitPassword
string password_;
@ -165,10 +193,15 @@ class AuthManager : public NetActor {
// for bots
string bot_token_;
uint64 query_id_ = 0;
WaitPasswordState wait_password_state_;
int32 login_code_retry_delay_ = 0;
Timeout poll_export_login_code_timeout_;
bool was_qr_code_request_ = false;
bool was_check_bot_token_ = false;
bool is_bot_ = false;
uint64 net_query_id_ = 0;
@ -182,15 +215,20 @@ class AuthManager : public NetActor {
void on_query_ok();
void start_net_query(NetQueryType net_query_type, NetQueryPtr net_query);
static void on_update_login_token_static(void *td);
void send_export_login_token_query();
void set_login_token_expires_at(double login_token_expires_at);
void destroy_auth_keys();
void on_send_code_result(NetQueryPtr &result);
void on_request_qr_code_result(NetQueryPtr &result, bool is_import);
void on_get_password_result(NetQueryPtr &result);
void on_request_password_recovery_result(NetQueryPtr &result);
void on_authentication_result(NetQueryPtr &result, bool expected_flag);
void on_log_out_result(NetQueryPtr &result);
void on_delete_account_result(NetQueryPtr &result);
void on_authorization(tl_object_ptr<telegram_api::auth_Authorization> auth_ptr);
void on_get_login_token(tl_object_ptr<telegram_api::auth_LoginToken> login_token);
void on_get_authorization(tl_object_ptr<telegram_api::auth_Authorization> auth_ptr);
void on_result(NetQueryPtr result) override;

View File

@ -7,7 +7,7 @@
#pragma once
#include "td/telegram/AuthManager.h"
#include "td/telegram/logevent/LogEventHelper.h"
#include "td/telegram/SendCodeHelper.hpp"
#include "td/telegram/Version.h"
@ -53,12 +53,14 @@ void AuthManager::DbState::store(StorerT &storer) const {
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;
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);
END_STORE_FLAGS();
store(state_, storer);
store(api_id_, storer);
@ -71,6 +73,10 @@ void AuthManager::DbState::store(StorerT &storer) const {
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) {
@ -88,6 +94,7 @@ void AuthManager::DbState::parse(ParserT &parser) {
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;
if (parser.version() >= static_cast<int32>(Version::AddTermsOfService)) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_terms_of_service);
@ -95,13 +102,16 @@ void AuthManager::DbState::parse(ParserT &parser) {
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);
END_PARSE_FLAGS();
}
if (!is_wait_registration_supported || !is_wait_registration_stores_phone_number) {
return parser.set_error("Have no wait registration support");
if (!is_wait_qr_code_confirmation_supported) {
return parser.set_error("Have no QR code confirmation support");
}
CHECK(is_pbkdf2_supported);
CHECK(is_srp_supported);
CHECK(is_wait_registration_supported);
CHECK(is_wait_registration_stores_phone_number);
parse(state_, parser);
parse(api_id_, parser);
@ -114,6 +124,10 @@ void AuthManager::DbState::parse(ParserT &parser) {
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) {

View File

@ -47,6 +47,7 @@
#include "td/db/SqliteKeyValue.h"
#include "td/db/SqliteKeyValueAsync.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
@ -124,11 +125,50 @@ class GetAccountTtlQuery : public Td::ResultHandler {
}
};
static td_api::object_ptr<td_api::session> convert_authorization_object(
tl_object_ptr<telegram_api::authorization> &&authorization) {
CHECK(authorization != nullptr);
bool is_current = (authorization->flags_ & telegram_api::authorization::CURRENT_MASK) != 0;
bool is_official_application = (authorization->flags_ & telegram_api::authorization::OFFICIAL_APP_MASK) != 0;
bool is_password_pending = (authorization->flags_ & telegram_api::authorization::PASSWORD_PENDING_MASK) != 0;
return td_api::make_object<td_api::session>(
authorization->hash_, is_current, is_password_pending, authorization->api_id_, authorization->app_name_,
authorization->app_version_, is_official_application, authorization->device_model_, authorization->platform_,
authorization->system_version_, authorization->date_created_, authorization->date_active_, authorization->ip_,
authorization->country_, authorization->region_);
}
class AcceptLoginTokenQuery : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::session>> promise_;
public:
explicit AcceptLoginTokenQuery(Promise<td_api::object_ptr<td_api::session>> &&promise)
: promise_(std::move(promise)) {
}
void send(const string &login_token) {
send_query(
G()->net_query_creator().create(create_storer(telegram_api::auth_acceptLoginToken(BufferSlice(login_token)))));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::auth_acceptLoginToken>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
LOG(DEBUG) << "Receive result for AcceptLoginTokenQuery: " << to_string(result_ptr.ok());
promise_.set_value(convert_authorization_object(std::move(result_ptr.move_as_ok())));
}
void on_error(uint64 id, Status status) override {
promise_.set_error(std::move(status));
}
};
class GetAuthorizationsQuery : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::sessions>> promise_;
static constexpr int32 AUTHORIZATION_FLAG_IS_CURRENT = 1 << 0;
static constexpr int32 AUTHORIZATION_FLAG_IS_OFFICIAL_APPLICATION = 1 << 1;
static constexpr int32 AUTHORIZATION_FLAG_IS_PASSWORD_PENDING = 1 << 2;
public:
explicit GetAuthorizationsQuery(Promise<tl_object_ptr<td_api::sessions>> &&promise) : promise_(std::move(promise)) {
@ -147,21 +187,8 @@ class GetAuthorizationsQuery : public Td::ResultHandler {
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetAuthorizationsQuery: " << to_string(ptr);
auto results = make_tl_object<td_api::sessions>();
results->sessions_.reserve(ptr->authorizations_.size());
for (auto &authorization : ptr->authorizations_) {
CHECK(authorization != nullptr);
bool is_current = (authorization->flags_ & AUTHORIZATION_FLAG_IS_CURRENT) != 0;
bool is_official_application = (authorization->flags_ & AUTHORIZATION_FLAG_IS_OFFICIAL_APPLICATION) != 0;
bool is_password_pending = (authorization->flags_ & AUTHORIZATION_FLAG_IS_PASSWORD_PENDING) != 0;
results->sessions_.push_back(make_tl_object<td_api::session>(
authorization->hash_, is_current, is_password_pending, authorization->api_id_, authorization->app_name_,
authorization->app_version_, is_official_application, authorization->device_model_, authorization->platform_,
authorization->system_version_, authorization->date_created_, authorization->date_active_, authorization->ip_,
authorization->country_, authorization->region_));
}
auto results =
make_tl_object<td_api::sessions>(transform(std::move(ptr->authorizations_), convert_authorization_object));
std::sort(results->sessions_.begin(), results->sessions_.end(),
[](const td_api::object_ptr<td_api::session> &lhs, const td_api::object_ptr<td_api::session> &rhs) {
if (lhs->is_current_ != rhs->is_current_) {
@ -4112,6 +4139,19 @@ void ContactsManager::get_account_ttl(Promise<int32> &&promise) const {
td_->create_handler<GetAccountTtlQuery>(std::move(promise))->send();
}
void ContactsManager::confirm_qr_code_authentication(string link,
Promise<td_api::object_ptr<td_api::session>> &&promise) {
Slice prefix("tg://login?");
if (!begins_with(to_lower(link), prefix)) {
return promise.set_error(Status::Error(400, "AUTH_TOKEN_INVALID"));
}
auto r_token = base64url_decode(Slice(link).substr(prefix.size()));
if (r_token.is_error()) {
return promise.set_error(Status::Error(400, "AUTH_TOKEN_INVALID"));
}
td_->create_handler<AcceptLoginTokenQuery>(std::move(promise))->send(r_token.ok());
}
void ContactsManager::get_active_sessions(Promise<tl_object_ptr<td_api::sessions>> &&promise) const {
td_->create_handler<GetAuthorizationsQuery>(std::move(promise))->send();
}

View File

@ -260,6 +260,8 @@ class ContactsManager : public Actor {
void set_account_ttl(int32 account_ttl, Promise<Unit> &&promise) const;
void get_account_ttl(Promise<int32> &&promise) const;
void confirm_qr_code_authentication(string link, Promise<td_api::object_ptr<td_api::session>> &&promise);
void get_active_sessions(Promise<tl_object_ptr<td_api::sessions>> &&promise) const;
void terminate_session(int64 session_id, Promise<Unit> &&promise) const;
void terminate_all_other_sessions(Promise<Unit> &&promise) const;

View File

@ -3450,6 +3450,7 @@ bool Td::is_authentication_request(int32 id) {
case td_api::resendAuthenticationCode::ID:
case td_api::checkAuthenticationCode::ID:
case td_api::registerUser::ID:
case td_api::requestQrCodeAuthentication::ID:
case td_api::checkAuthenticationPassword::ID:
case td_api::requestAuthenticationPasswordRecovery::ID:
case td_api::recoverAuthenticationPassword::ID:
@ -4957,6 +4958,11 @@ void Td::on_request(uint64 id, td_api::registerUser &request) {
std::move(request.last_name_));
}
void Td::on_request(uint64 id, td_api::requestQrCodeAuthentication &request) {
send_closure(auth_manager_actor_, &AuthManager::request_qr_code_authentication, id,
std::move(request.other_user_ids_));
}
void Td::on_request(uint64 id, td_api::checkAuthenticationPassword &request) {
CLEAN_INPUT_STRING(request.password_);
send_closure(auth_manager_actor_, &AuthManager::check_password, id, std::move(request.password_));
@ -4993,6 +4999,12 @@ void Td::on_request(uint64 id, td_api::checkAuthenticationBotToken &request) {
send_closure(auth_manager_actor_, &AuthManager::check_bot_token, id, std::move(request.token_));
}
void Td::on_request(uint64 id, td_api::confirmQrCodeAuthentication &request) {
CLEAN_INPUT_STRING(request.link_);
CREATE_REQUEST_PROMISE();
contacts_manager_->confirm_qr_code_authentication(std::move(request.link_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getCurrentState &request) {
vector<td_api::object_ptr<td_api::Update>> updates;

View File

@ -374,6 +374,8 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::registerUser &request);
void on_request(uint64 id, td_api::requestQrCodeAuthentication &request);
void on_request(uint64 id, td_api::checkAuthenticationPassword &request);
void on_request(uint64 id, const td_api::requestAuthenticationPasswordRecovery &request);
@ -388,6 +390,8 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::checkAuthenticationBotToken &request);
void on_request(uint64 id, td_api::confirmQrCodeAuthentication &request);
void on_request(uint64 id, const td_api::getCurrentState &request);
void on_request(uint64 id, td_api::getPasswordState &request);

View File

@ -623,7 +623,12 @@ void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updat
LOG(INFO) << "Receive " << to_string(updates_ptr);
}
if (!td_->auth_manager_->is_authorized()) {
LOG(INFO) << "Ignore updates received before authorization or after logout";
if (updates_type == telegram_api::updateShort::ID &&
static_cast<const telegram_api::updateShort *>(updates_ptr.get())->update_->get_id() ==
telegram_api::updateLoginToken::ID) {
return td_->auth_manager_->on_update_login_token();
}
LOG(INFO) << "Ignore received before authorization or after logout " << to_string(updates_ptr);
return;
}
@ -1975,12 +1980,13 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteScheduled
td_->messages_manager_->on_update_delete_scheduled_messages(DialogId(update->peer_), std::move(message_ids));
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLoginToken> update, bool /*force_apply*/) {
LOG(INFO) << "Receive updateLoginToken after authorization";
}
// unsupported updates
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateTheme> update, bool /*force_apply*/) {
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLoginToken> update, bool /*force_apply*/) {
}
} // namespace td

View File

@ -285,11 +285,11 @@ class UpdatesManager : public Actor {
void on_update(tl_object_ptr<telegram_api::updateNewScheduledMessage> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateDeleteScheduledMessages> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateLoginToken> update, bool /*force_apply*/);
// unsupported updates
void on_update(tl_object_ptr<telegram_api::updateTheme> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateLoginToken> update, bool /*force_apply*/);
};
} // namespace td

View File

@ -1327,8 +1327,12 @@ class CliClient final : public Actor {
send_request(td_api::make_object<td_api::registerUser>(first_name, last_name));
} else if (op == "cap") {
send_request(td_api::make_object<td_api::checkAuthenticationPassword>(args));
} else if (op == "cab" || op == "cabt") {
} else if (op == "cabt") {
send_request(td_api::make_object<td_api::checkAuthenticationBotToken>(args));
} else if (op == "qr") {
send_request(td_api::make_object<td_api::requestQrCodeAuthentication>(as_user_ids(args)));
} else if (op == "cqr") {
send_request(td_api::make_object<td_api::confirmQrCodeAuthentication>(args));
} else if (op == "gcs") {
send_request(td_api::make_object<td_api::getCurrentState>());
} else if (op == "rapr") {

View File

@ -324,7 +324,7 @@ void NetQueryDispatcher::set_main_dc_id(int32 new_main_dc_id) {
return;
}
LOG(INFO) << "Update: " << tag("main_dc_id", main_dc_id_.load(std::memory_order_relaxed));
LOG(INFO) << "Update main DcId from " << main_dc_id_.load(std::memory_order_relaxed) << " to " << new_main_dc_id;
if (is_dc_inited(main_dc_id_.load(std::memory_order_relaxed))) {
send_closure_later(dcs_[main_dc_id_ - 1].main_session_, &SessionMultiProxy::update_main_flag, false);
}

View File

@ -38,6 +38,9 @@ class Timeout final : public Actor {
void set_timeout_in(double timeout) {
Actor::set_timeout_in(timeout);
}
void set_timeout_at(double timeout) {
Actor::set_timeout_at(timeout);
}
void cancel_timeout() {
if (has_timeout()) {
Actor::cancel_timeout();
@ -52,10 +55,6 @@ class Timeout final : public Actor {
Callback callback_{};
Data data_{};
void set_timeout_at(double timeout) {
Actor::set_timeout_at(timeout);
}
void timeout_expired() override {
CHECK(!has_timeout());
CHECK(callback_ != Callback());