From 3d6f6cdd8f71c061ffb7e083b17ea133608b9e93 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 7 May 2022 00:17:41 +0300 Subject: [PATCH] Added ability to set cloud password from Api::CloudPassword. --- .../SourceFiles/api/api_cloud_password.cpp | 208 +++++++++++++++++- Telegram/SourceFiles/api/api_cloud_password.h | 13 ++ 2 files changed, 210 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/api/api_cloud_password.cpp b/Telegram/SourceFiles/api/api_cloud_password.cpp index 03ca032ad..1a3f7da2a 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.cpp +++ b/Telegram/SourceFiles/api/api_cloud_password.cpp @@ -7,11 +7,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_cloud_password.h" +#include "apiwrap.h" #include "base/random.h" #include "core/core_cloud_password.h" -#include "apiwrap.h" +#include "passport/passport_encryption.h" namespace Api { +namespace { + +[[nodiscard]] Core::CloudPasswordState ProcessMtpState( + const MTPaccount_password &state) { + return state.match([&](const MTPDaccount_password &data) { + base::RandomAddSeed(bytes::make_span(data.vsecure_random().v)); + return Core::ParseCloudPasswordState(data); + }); +} + +} // namespace // #TODO Add ability to set recovery email separately. @@ -19,6 +31,15 @@ CloudPassword::CloudPassword(not_null api) : _api(&api->instance()) { } +void CloudPassword::apply(Core::CloudPasswordState state) { + if (_state) { + *_state = std::move(state); + } else { + _state = std::make_unique(std::move(state)); + } + _stateChanges.fire_copy(*_state); +} + void CloudPassword::reload() { if (_requestId) { return; @@ -26,16 +47,7 @@ void CloudPassword::reload() { _requestId = _api.request(MTPaccount_GetPassword( )).done([=](const MTPaccount_Password &result) { _requestId = 0; - result.match([&](const MTPDaccount_password &data) { - base::RandomAddSeed(bytes::make_span(data.vsecure_random().v)); - if (_state) { - *_state = Core::ParseCloudPasswordState(data); - } else { - _state = std::make_unique( - Core::ParseCloudPasswordState(data)); - } - _stateChanges.fire_copy(*_state); - }); + apply(ProcessMtpState(result)); }).fail([=] { _requestId = 0; }).send(); @@ -109,4 +121,178 @@ auto CloudPassword::cancelResetPassword() }; } +rpl::producer CloudPassword::set( + const QString &oldPassword, + const QString &newPassword, + const QString &hint, + bool hasRecoveryEmail, + const QString &recoveryEmail) { + + const auto generatePasswordCheck = [=]( + const Core::CloudPasswordState &latestState) { + if (oldPassword.isEmpty() || !latestState.hasPassword) { + return Core::CloudPasswordResult{ + MTP_inputCheckPasswordEmpty() + }; + } + const auto hash = Core::ComputeCloudPasswordHash( + latestState.mtp.request.algo, + bytes::make_span(oldPassword.toUtf8())); + return Core::ComputeCloudPasswordCheck( + latestState.mtp.request, + hash); + }; + + const auto finish = [=](auto consumer, int unconfirmedEmailLengthCode) { + _api.request(MTPaccount_GetPassword( + )).done([=](const MTPaccount_Password &result) { + apply(ProcessMtpState(result)); + if (unconfirmedEmailLengthCode) { + consumer.put_next(SetOk{ unconfirmedEmailLengthCode }); + } else { + consumer.put_done(); + } + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).handleFloodErrors().send(); + }; + + const auto sendMTPaccountUpdatePasswordSettings = [=]( + const Core::CloudPasswordState &latestState, + const QByteArray &secureSecret, + auto consumer) { + const auto newPasswordBytes = newPassword.toUtf8(); + const auto newPasswordHash = Core::ComputeCloudPasswordDigest( + latestState.mtp.newPassword, + bytes::make_span(newPasswordBytes)); + if (!newPassword.isEmpty() && newPasswordHash.modpow.empty()) { + consumer.put_error("INTERNAL_SERVER_ERROR"); + return; + } + using Flag = MTPDaccount_passwordInputSettings::Flag; + const auto flags = Flag::f_new_algo + | Flag::f_new_password_hash + | Flag::f_hint + | (secureSecret.isEmpty() ? Flag(0) : Flag::f_new_secure_settings) + | ((!hasRecoveryEmail) ? Flag(0) : Flag::f_email); + + auto newSecureSecret = bytes::vector(); + auto newSecureSecretId = 0ULL; + if (!secureSecret.isEmpty()) { + newSecureSecretId = Passport::CountSecureSecretId( + bytes::make_span(secureSecret)); + newSecureSecret = Passport::EncryptSecureSecret( + bytes::make_span(secureSecret), + Core::ComputeSecureSecretHash( + latestState.mtp.newSecureSecret, + bytes::make_span(newPasswordBytes))); + } + const auto settings = MTP_account_passwordInputSettings( + MTP_flags(flags), + Core::PrepareCloudPasswordAlgo(newPassword.isEmpty() + ? v::null + : latestState.mtp.newPassword), + newPassword.isEmpty() + ? MTP_bytes() + : MTP_bytes(newPasswordHash.modpow), + MTP_string(hint), + MTP_string(recoveryEmail), + MTP_secureSecretSettings( + Core::PrepareSecureSecretAlgo( + latestState.mtp.newSecureSecret), + MTP_bytes(newSecureSecret), + MTP_long(newSecureSecretId))); + _api.request(MTPaccount_UpdatePasswordSettings( + generatePasswordCheck(latestState).result, + settings + )).done([=] { + finish(consumer, 0); + }).fail([=](const MTP::Error &error) { + const auto &type = error.type(); + const auto prefix = u"EMAIL_UNCONFIRMED_"_q; + if (type.startsWith(prefix)) { + const auto codeLength = base::StringViewMid( + type, + prefix.size()).toInt(); + + finish(consumer, codeLength); + } else { + consumer.put_error_copy(type); + } + }).handleFloodErrors().send(); + }; + + return [=](auto consumer) { + _api.request(MTPaccount_GetPassword( + )).done([=](const MTPaccount_Password &result) { + const auto latestState = ProcessMtpState(result); + + if (latestState.hasPassword + && !oldPassword.isEmpty() + && !newPassword.isEmpty()) { + + _api.request(MTPaccount_GetPasswordSettings( + generatePasswordCheck(latestState).result + )).done([=](const MTPaccount_PasswordSettings &result) { + using Settings = MTPDaccount_passwordSettings; + const auto &data = result.match([&]( + const Settings &data) -> const Settings & { + return data; + }); + auto secureSecret = QByteArray(); + if (const auto wrapped = data.vsecure_settings()) { + using Secure = MTPDsecureSecretSettings; + const auto &settings = wrapped->match([]( + const Secure &data) -> const Secure & { + return data; + }); + const auto passwordUtf = oldPassword.toUtf8(); + const auto secret = Passport::DecryptSecureSecret( + bytes::make_span(settings.vsecure_secret().v), + Core::ComputeSecureSecretHash( + Core::ParseSecureSecretAlgo( + settings.vsecure_algo()), + bytes::make_span(passwordUtf))); + if (secret.empty()) { + LOG(("API Error: " + "Failed to decrypt secure secret.")); + consumer.put_error("SUGGEST_SECRET_RESET"); + return; + } else if (Passport::CountSecureSecretId(secret) + != settings.vsecure_secret_id().v) { + LOG(("API Error: Wrong secure secret id.")); + consumer.put_error("SUGGEST_SECRET_RESET"); + return; + } else { + secureSecret = QByteArray( + reinterpret_cast(secret.data()), + secret.size()); + } + } + _api.request(MTPaccount_GetPassword( + )).done([=](const MTPaccount_Password &result) { + const auto latestState = ProcessMtpState(result); + sendMTPaccountUpdatePasswordSettings( + latestState, + secureSecret, + consumer); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + } else { + sendMTPaccountUpdatePasswordSettings( + latestState, + QByteArray(), + consumer); + } + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + return rpl::lifetime(); + }; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_cloud_password.h b/Telegram/SourceFiles/api/api_cloud_password.h index 00adf3ddb..a01f973df 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.h +++ b/Telegram/SourceFiles/api/api_cloud_password.h @@ -23,6 +23,10 @@ namespace Api { class CloudPassword final { public: + struct SetOk { + int unconfirmedEmailLengthCode = 0; + }; + using ResetRetryDate = int; explicit CloudPassword(not_null api); @@ -34,7 +38,16 @@ public: rpl::producer resetPassword(); rpl::producer cancelResetPassword(); + rpl::producer set( + const QString &oldPassword, + const QString &newPassword, + const QString &hint, + bool hasRecoveryEmail, + const QString &recoveryEmail); + private: + void apply(Core::CloudPasswordState state); + MTP::Sender _api; mtpRequestId _requestId = 0; std::unique_ptr _state;