diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index bd94dd647c..0c28fbd64f 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -54,6 +54,14 @@ PasscodeBox::PasscodeBox( if (!hint.isEmpty()) _hintText.setText(st::passcodeTextStyle, lng_signin_hint(lt_password_hint, hint)); } +rpl::producer PasscodeBox::newPasswordSet() const { + return _newPasswordSet.events(); +} + +rpl::producer<> PasscodeBox::passwordReloadNeeded() const { + return _passwordReloadNeeded.events(); +} + void PasscodeBox::prepare() { addButton(langFactory(_turningOff ? lng_passcode_remove_button : lng_settings_save), [=] { save(); }); addButton(langFactory(lng_cancel), [=] { closeBox(); }); @@ -197,9 +205,9 @@ void PasscodeBox::setInnerFocus() { } } -void PasscodeBox::setPasswordDone() { +void PasscodeBox::setPasswordDone(const QByteArray &newPasswordBytes) { _setRequest = 0; - emit reloadPassword(); + _newPasswordSet.fire_copy(newPasswordBytes); auto text = lang(_reenterPasscode->isHidden() ? lng_cloud_password_removed : (_oldPasscode->isHidden() ? lng_cloud_password_was_set : lng_cloud_password_updated)); getDelegate()->show(Box(text), LayerOption::CloseOther); } @@ -236,7 +244,7 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) { QString err = error.type(); if (err == qstr("PASSWORD_HASH_INVALID")) { if (_oldPasscode->isHidden()) { - emit reloadPassword(); + _passwordReloadNeeded.fire({}); closeBox(); } else { badOldPasscode(); @@ -247,20 +255,36 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) { _newError = lang(lng_cloud_password_bad); update(); } else if (err == qstr("NEW_SALT_INVALID")) { - emit reloadPassword(); + _passwordReloadNeeded.fire({}); closeBox(); } else if (err == qstr("EMAIL_INVALID")) { _emailError = lang(lng_cloud_password_bad_email); _recoverEmail->setFocus(); _recoverEmail->showError(); update(); - } else if (err == qstr("EMAIL_UNCONFIRMED")) { + } + return true; +} + +bool PasscodeBox::setPasswordFail( + const QByteArray &newPasswordBytes, + const RPCError &error) { + if (MTP::isFloodError(error)) { + return setPasswordFail(error); + } else if (MTP::isDefaultHandledError(error)) { + return setPasswordFail(error); + } else if (error.type() == qstr("EMAIL_UNCONFIRMED")) { + closeReplacedBy(); + _setRequest = 0; + + _newPasswordSet.fire_copy(newPasswordBytes); getDelegate()->show( Box(lang(lng_cloud_password_almost)), LayerOption::CloseOther); - emit reloadPassword(); + return true; + } else { + return setPasswordFail(error); } - return true; } void PasscodeBox::save(bool force) { @@ -386,14 +410,15 @@ void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) { MTPbytes(), // new_secure_secret MTPlong()) // new_secure_secret_id )).done([=](const MTPBool &result) { - setPasswordDone(); + setPasswordDone({}); }).fail([=](const RPCError &error) { - setPasswordFail(error); + setPasswordFail({}, error); }).send(); } void PasscodeBox::setNewCloudPassword(const QString &newPassword) { - const auto newPasswordData = (_newSalt + newPassword.toUtf8() + _newSalt); + const auto newPasswordBytes = newPassword.toUtf8(); + const auto newPasswordData = (_newSalt + newPasswordBytes + _newSalt); auto newPasswordHash = QByteArray(32, Qt::Uninitialized); hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data()); const auto oldPasswordData = QByteArray(); @@ -416,9 +441,9 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) { MTPbytes(), // new_secure_secret MTPlong()) // new_secure_secret_id )).done([=](const MTPBool &result) { - setPasswordDone(); + setPasswordDone(newPasswordBytes); }).fail([=](const RPCError &error) { - setPasswordFail(error); + setPasswordFail(newPasswordBytes, error); }).send(); } @@ -443,9 +468,10 @@ void PasscodeBox::changeCloudPassword( return; } const auto secret = Passport::DecryptSecureSecret( - bytes::make_span(data.vsecure_salt.v), bytes::make_span(data.vsecure_secret.v), - bytes::make_span(passwordUtf)); + Passport::CountPasswordHashForSecret( + bytes::make_span(data.vsecure_salt.v), + bytes::make_span(passwordUtf))); if (secret.empty()) { LOG(("API Error: Failed to decrypt secure secret.")); suggestSecretReset(oldPasswordHash, newPassword); @@ -505,8 +531,8 @@ void PasscodeBox::sendChangeCloudPassword( const QByteArray &oldPasswordHash, const QString &newPassword, const QByteArray &secureSecret) { - const auto passwordUtf = newPassword.toUtf8(); - const auto newPasswordData = (_newSalt + passwordUtf + _newSalt); + const auto newPasswordBytes = newPassword.toUtf8(); + const auto newPasswordData = (_newSalt + newPasswordBytes + _newSalt); auto newPasswordHash = QByteArray(32, Qt::Uninitialized); hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data()); const auto hint = _passwordHint->getLastText(); @@ -522,9 +548,10 @@ void PasscodeBox::sendChangeCloudPassword( newSecureSecretId = Passport::CountSecureSecretId( bytes::make_span(secureSecret)); newSecureSecret = Passport::EncryptSecureSecret( - bytes::make_span(_newSecureSecretSalt), bytes::make_span(secureSecret), - bytes::make_span(passwordUtf)); + Passport::CountPasswordHashForSecret( + bytes::make_span(_newSecureSecretSalt), + bytes::make_span(newPasswordBytes))); } _setRequest = request(MTPaccount_UpdatePasswordSettings( MTP_bytes(oldPasswordHash), @@ -538,9 +565,9 @@ void PasscodeBox::sendChangeCloudPassword( MTP_bytes(newSecureSecret), MTP_long(newSecureSecretId)) )).done([=](const MTPBool &result) { - setPasswordDone(); + setPasswordDone(newPasswordBytes); }).fail([=](const RPCError &error) { - setPasswordFail(error); + setPasswordFail(newPasswordBytes, error); }).send(); } @@ -603,8 +630,17 @@ void PasscodeBox::recover() { const auto box = getDelegate()->show(Box( _pattern, _notEmptyPassport)); - connect(box, &RecoverBox::reloadPassword, this, &PasscodeBox::reloadPassword); - connect(box, &RecoverBox::recoveryExpired, this, &PasscodeBox::recoverExpired); + + box->passwordCleared( + ) | rpl::map([] { + return QByteArray(); + }) | rpl::start_to_stream(_newPasswordSet, lifetime()); + + box->recoveryExpired( + ) | rpl::start_with_next([=] { + recoverExpired(); + }, lifetime()); + _replacedBy = box; } @@ -630,6 +666,14 @@ RecoverBox::RecoverBox( , _recoverCode(this, st::defaultInputField, langFactory(lng_signin_code)) { } +rpl::producer<> RecoverBox::passwordCleared() const { + return _passwordCleared.events(); +} + +rpl::producer<> RecoverBox::recoveryExpired() const { + return _recoveryExpired.events(); +} + void RecoverBox::prepare() { setTitle(langFactory(lng_signin_recover_title)); @@ -707,10 +751,12 @@ void RecoverBox::codeChanged() { update(); } -void RecoverBox::codeSubmitDone(bool recover, const MTPauth_Authorization &result) { +void RecoverBox::codeSubmitDone( + bool recover, + const MTPauth_Authorization &result) { _submitRequest = 0; - emit reloadPassword(); + _passwordCleared.fire({}); getDelegate()->show( Box(lang(lng_cloud_password_removed)), LayerOption::CloseOther); @@ -730,7 +776,7 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) { const QString &err = error.type(); if (err == qstr("PASSWORD_EMPTY")) { - emit reloadPassword(); + _passwordCleared.fire({}); getDelegate()->show( Box(lang(lng_cloud_password_removed)), LayerOption::CloseOther); @@ -739,7 +785,7 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) { closeBox(); return true; } else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) { - emit recoveryExpired(); + _recoveryExpired.fire({}); closeBox(); return true; } else if (err == qstr("CODE_INVALID")) { diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h index b746b60858..1ce793c2b0 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -17,8 +17,6 @@ class LinkButton; } // namespace Ui class PasscodeBox : public BoxContent, private MTP::Sender { - Q_OBJECT - public: PasscodeBox(QWidget*, bool turningOff); PasscodeBox( @@ -31,8 +29,8 @@ public: const QByteArray &newSecureSecretSalt, bool turningOff = false); -signals: - void reloadPassword(); + rpl::producer newPasswordSet() const; + rpl::producer<> passwordReloadNeeded() const; protected: void prepare() override; @@ -52,8 +50,11 @@ private: void recoverByEmail(); void recoverExpired(); - void setPasswordDone(); + void setPasswordDone(const QByteArray &newPasswordBytes); bool setPasswordFail(const RPCError &error); + bool setPasswordFail( + const QByteArray &newPasswordBytes, + const RPCError &error); void recoverStarted(const MTPauth_PasswordRecovery &result); bool recoverStartFail(const RPCError &error); @@ -101,17 +102,20 @@ private: QString _oldError, _newError, _emailError; + rpl::event_stream _newPasswordSet; + rpl::event_stream<> _passwordReloadNeeded; + }; class RecoverBox : public BoxContent, public RPCSender { - Q_OBJECT - public: RecoverBox(QWidget*, const QString &pattern, bool notEmptyPassport); -signals: - void reloadPassword(); - void recoveryExpired(); + rpl::producer<> passwordCleared() const; + rpl::producer<> recoveryExpired() const; + + //void reloadPassword(); + //void recoveryExpired(); protected: void prepare() override; @@ -135,4 +139,7 @@ private: QString _error; + rpl::event_stream<> _passwordCleared; + rpl::event_stream<> _recoveryExpired; + }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index bcd6034573..8ba9d3e469 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_layout_item.h" #include "storage/localstorage.h" #include "boxes/abstract_box.h" +#include "passport/passport_form_controller.h" #include "data/data_media_types.h" #include "data/data_feed.h" #include "data/data_photo.h" @@ -156,6 +157,30 @@ void Session::stopExport() { _export = nullptr; } +const Passport::SavedCredentials *Session::passportCredentials() const { + return _passportCredentials ? &_passportCredentials->first : nullptr; +} + +void Session::rememberPassportCredentials( + Passport::SavedCredentials data, + TimeMs rememberFor) { + Expects(rememberFor > 0); + + static auto generation = 0; + _passportCredentials = std::make_unique( + std::move(data), + ++generation); + App::CallDelayed(rememberFor, _session, [=, check = generation] { + if (_passportCredentials && _passportCredentials->second == check) { + forgetPassportCredentials(); + } + }); +} + +void Session::forgetPassportCredentials() { + _passportCredentials = nullptr; +} + void Session::setupContactViewsViewer() { Notify::PeerUpdateViewer( Notify::PeerUpdate::Flag::UserIsContact diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 56bcf09715..e52eef4c8a 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -35,6 +35,10 @@ class PanelController; } // namespace View } // namespace Export +namespace Passport { +struct SavedCredentials; +} // namespace Passport + namespace Data { class Feed; @@ -60,6 +64,12 @@ public: void stopExportWithConfirmation(FnMut callback); void stopExport(); + const Passport::SavedCredentials *passportCredentials() const; + void rememberPassportCredentials( + Passport::SavedCredentials data, + TimeMs rememberFor); + void forgetPassportCredentials(); + [[nodiscard]] base::Variable &contactsLoaded() { return _contactsLoaded; } @@ -610,6 +620,11 @@ private: MessageIdsList _mimeForwardIds; + using CredentialsWithGeneration = std::pair< + const Passport::SavedCredentials, + int>; + std::unique_ptr _passportCredentials; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 3ccd8b62ef..61a737ed12 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -1096,11 +1096,6 @@ void ApiWrap::appendChatsSlice( auto filtered = ranges::view::all( info.list ) | ranges::view::filter([&](const Data::DialogInfo &info) { -#ifdef _DEBUG - return (info.name == "Anta"); -#else -#error "test" -#endif return (types & SettingsFromDialogsType(info.type)) != 0; }); auto &list = to.info.list; diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 4bde5fa312..2ae9d6229b 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -19,10 +19,7 @@ namespace Export { namespace Output { namespace { -#ifndef _DEBUG -#error test -#endif -constexpr auto kMessagesInFile = 50; +constexpr auto kMessagesInFile = 1000; constexpr auto kPersonalUserpicSize = 90; constexpr auto kEntryUserpicSize = 48; constexpr auto kServiceMessagePhotoSize = 60; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index a4bccfcb51..0a960aa255 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -90,15 +90,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" #include "styles/style_boxes.h" -#ifdef _DEBUG -#include "export/output/export_output_html.h" -#include "export/output/export_output_stats.h" -#include "export/view/export_view_panel_controller.h" -#include "platform/platform_specific.h" -#else -#error "test" -#endif - namespace { bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) { @@ -261,10 +252,6 @@ MainWidget::MainWidget( Messenger::Instance().mtp()->setUpdatesHandler(rpcDone(&MainWidget::updateReceived)); Messenger::Instance().mtp()->setGlobalFailHandler(rpcFail(&MainWidget::updateFail)); - //Export::Output::HtmlWriter writer; - //writer.produceTestExample(psDownloadPath(), Export::View::PrepareEnvironment()); - //crl::on_main([] { App::quit(); }); - _ptsWaiter.setRequesting(true); updateScrollColors(); setupConnectingWidget(); diff --git a/Telegram/SourceFiles/passport/passport_encryption.cpp b/Telegram/SourceFiles/passport/passport_encryption.cpp index 9c9a175aeb..57fb485ebe 100644 --- a/Telegram/SourceFiles/passport/passport_encryption.cpp +++ b/Telegram/SourceFiles/passport/passport_encryption.cpp @@ -16,6 +16,7 @@ namespace { constexpr auto kAesKeyLength = 32; constexpr auto kAesIvLength = 16; constexpr auto kSecretSize = 32; +constexpr auto kAesParamsHashSize = 64; constexpr auto kMinPadding = 32; constexpr auto kMaxPadding = 255; constexpr auto kAlignTo = 16; @@ -27,18 +28,21 @@ struct AesParams { bytes::vector iv; }; -AesParams PrepareAesParams(bytes::const_span bytesForEncryptionKey) { - const auto hash = openssl::Sha512(bytesForEncryptionKey); - const auto view = gsl::make_span(hash); +AesParams PrepareAesParamsWithHash(bytes::const_span hashForEncryptionKey) { + Expects(hashForEncryptionKey.size() == kAesParamsHashSize); auto result = AesParams(); result.key = bytes::make_vector( - view.subspan(0, kAesKeyLength)); + hashForEncryptionKey.subspan(0, kAesKeyLength)); result.iv = bytes::make_vector( - view.subspan(kAesKeyLength, kAesIvLength)); + hashForEncryptionKey.subspan(kAesKeyLength, kAesIvLength)); return result; } +AesParams PrepareAesParams(bytes::const_span bytesForEncryptionKey) { + return PrepareAesParamsWithHash(openssl::Sha512(bytesForEncryptionKey)); +} + bytes::vector EncryptOrDecrypt( bytes::const_span initial, AesParams &¶ms, @@ -112,9 +116,9 @@ bytes::vector GenerateSecretBytes() { return result; } -bytes::vector DecryptSecretBytes( +bytes::vector DecryptSecretBytesWithHash( bytes::const_span encryptedSecret, - bytes::const_span bytesForEncryptionKey) { + bytes::const_span hashForEncryptionKey) { if (encryptedSecret.empty()) { return {}; } else if (encryptedSecret.size() != kSecretSize) { @@ -122,7 +126,7 @@ bytes::vector DecryptSecretBytes( ).arg(encryptedSecret.size())); return {}; } - auto params = PrepareAesParams(bytesForEncryptionKey); + auto params = PrepareAesParamsWithHash(hashForEncryptionKey); auto result = Decrypt(encryptedSecret, std::move(params)); if (!CheckSecretBytes(result)) { LOG(("API Error: Bad secret bytes.")); @@ -131,6 +135,24 @@ bytes::vector DecryptSecretBytes( return result; } +bytes::vector DecryptSecretBytes( + bytes::const_span encryptedSecret, + bytes::const_span bytesForEncryptionKey) { + return DecryptSecretBytesWithHash( + encryptedSecret, + openssl::Sha512(bytesForEncryptionKey)); +} + +bytes::vector EncryptSecretBytesWithHash( + bytes::const_span secret, + bytes::const_span hashForEncryptionKey) { + Expects(secret.size() == kSecretSize); + Expects(CheckSecretBytes(secret) == true); + + auto params = PrepareAesParamsWithHash(hashForEncryptionKey); + return Encrypt(secret, std::move(params)); +} + bytes::vector EncryptSecretBytes( bytes::const_span secret, bytes::const_span bytesForEncryptionKey) { @@ -141,34 +163,31 @@ bytes::vector EncryptSecretBytes( return Encrypt(secret, std::move(params)); } -bytes::vector DecryptSecureSecret( +bytes::vector CountPasswordHashForSecret( bytes::const_span salt, - bytes::const_span encryptedSecret, bytes::const_span password) { - Expects(!salt.empty()); - Expects(!encryptedSecret.empty()); - Expects(!password.empty()); - - const auto bytesForEncryptionKey = bytes::concatenate( + return openssl::Sha512(bytes::concatenate( salt, password, - salt); - return DecryptSecretBytes(encryptedSecret, bytesForEncryptionKey); + salt)); +} + +bytes::vector DecryptSecureSecret( + bytes::const_span encryptedSecret, + bytes::const_span passwordHashForSecret) { + Expects(!encryptedSecret.empty()); + + return DecryptSecretBytesWithHash( + encryptedSecret, + passwordHashForSecret); } bytes::vector EncryptSecureSecret( - bytes::const_span salt, bytes::const_span secret, - bytes::const_span password) { - Expects(!salt.empty()); + bytes::const_span passwordHashForSecret) { Expects(secret.size() == kSecretSize); - Expects(!password.empty()); - const auto bytesForEncryptionKey = bytes::concatenate( - salt, - password, - salt); - return EncryptSecretBytes(secret, bytesForEncryptionKey); + return EncryptSecretBytesWithHash(secret, passwordHashForSecret); } bytes::vector SerializeData(const std::map &data) { diff --git a/Telegram/SourceFiles/passport/passport_encryption.h b/Telegram/SourceFiles/passport/passport_encryption.h index 71443ccde4..2c54d3ef26 100644 --- a/Telegram/SourceFiles/passport/passport_encryption.h +++ b/Telegram/SourceFiles/passport/passport_encryption.h @@ -11,14 +11,15 @@ namespace Passport { bytes::vector GenerateSecretBytes(); +bytes::vector CountPasswordHashForSecret( + bytes::const_span salt, + bytes::const_span password); bytes::vector EncryptSecureSecret( - bytes::const_span salt, bytes::const_span secret, - bytes::const_span password); + bytes::const_span passwordHashForSecret); bytes::vector DecryptSecureSecret( - bytes::const_span salt, bytes::const_span encryptedSecret, - bytes::const_span password); + bytes::const_span passwordHashForSecret); bytes::vector SerializeData(const std::map &data); std::map DeserializeData(bytes::const_span bytes); diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index c8498a822a..5b2602fa9b 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_hardcoded.h" #include "base/openssl_help.h" #include "base/qthelp_url.h" +#include "data/data_session.h" #include "mainwindow.h" #include "window/window_controller.h" #include "core/click_handler_types.h" @@ -30,6 +31,7 @@ namespace { constexpr auto kDocumentScansLimit = 20; constexpr auto kShortPollTimeout = TimeMs(3000); +constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000); bool ForwardServiceErrorRequired(const QString &error) { return (error == qstr("BOT_INVALID")) @@ -436,32 +438,54 @@ std::vector> FormController::submitGetErrors() { return {}; } -void FormController::submitPassword(const QString &password) { +void FormController::submitPassword(const QByteArray &password) { Expects(!_password.salt.empty()); + const auto submitSaved = !base::take(_savedPasswordValue).isEmpty(); if (_passwordCheckRequestId) { return; } else if (password.isEmpty()) { _passwordError.fire(QString()); + return; } - const auto passwordBytes = password.toUtf8(); _passwordCheckRequestId = request(MTPaccount_GetPasswordSettings( - MTP_bytes(passwordHashForAuth(bytes::make_span(passwordBytes))) + MTP_bytes(passwordHashForAuth(bytes::make_span(password))) )).handleFloodErrors( ).done([=](const MTPaccount_PasswordSettings &result) { Expects(result.type() == mtpc_account_passwordSettings); _passwordCheckRequestId = 0; + _savedPasswordValue = QByteArray(); const auto &data = result.c_account_passwordSettings(); + const auto hashForAuth = passwordHashForAuth( + bytes::make_span(password)); + const auto hashForSecret = (data.vsecure_salt.v.isEmpty() + ? bytes::vector() + : CountPasswordHashForSecret( + bytes::make_span(data.vsecure_salt.v), + bytes::make_span(password))); _password.confirmedEmail = qs(data.vemail); validateSecureSecret( - bytes::make_span(data.vsecure_salt.v), bytes::make_span(data.vsecure_secret.v), - bytes::make_span(passwordBytes), + hashForSecret, + bytes::make_span(password), data.vsecure_secret_id.v); + if (!_secret.empty()) { + auto saved = SavedCredentials(); + saved.hashForAuth = hashForAuth; + saved.hashForSecret = hashForSecret; + saved.secretId = _secretId; + Auth().data().rememberPassportCredentials( + std::move(saved), + kRememberCredentialsDelay); + } }).fail([=](const RPCError &error) { _passwordCheckRequestId = 0; - if (MTP::isFloodError(error)) { + if (submitSaved) { + // Force reload and show form. + _password = PasswordSettings(); + reloadPassword(); + } else if (MTP::isFloodError(error)) { _passwordError.fire(lang(lng_flood_error)); } else if (error.type() == qstr("PASSWORD_HASH_INVALID")) { _passwordError.fire(lang(lng_passport_password_wrong)); @@ -471,6 +495,35 @@ void FormController::submitPassword(const QString &password) { }).send(); } +void FormController::checkSavedPasswordSettings( + const SavedCredentials &credentials) { + _passwordCheckRequestId = request(MTPaccount_GetPasswordSettings( + MTP_bytes(credentials.hashForAuth) + )).done([=](const MTPaccount_PasswordSettings &result) { + Expects(result.type() == mtpc_account_passwordSettings); + + _passwordCheckRequestId = 0; + const auto &data = result.c_account_passwordSettings(); + if (!data.vsecure_secret.v.isEmpty() + && data.vsecure_secret_id.v == credentials.secretId) { + _password.confirmedEmail = qs(data.vemail); + validateSecureSecret( + bytes::make_span(data.vsecure_secret.v), + credentials.hashForSecret, + {}, + data.vsecure_secret_id.v); + } + if (_secret.empty()) { + Auth().data().forgetPassportCredentials(); + showForm(); + } + }).fail([=](const RPCError &error) { + _passwordCheckRequestId = 0; + Auth().data().forgetPassportCredentials(); + showForm(); + }).send(); +} + void FormController::recoverPassword() { if (!_password.hasRecovery) { _view->show(Box(lang(lng_signin_no_email_forgot))); @@ -489,14 +542,16 @@ void FormController::recoverPassword() { const auto box = _view->show(Box( pattern, _password.notEmptyPassport)); - box->connect(box, &RecoverBox::reloadPassword, [=] { + + box->passwordCleared( + ) | rpl::start_with_next([=] { reloadPassword(); - }); - box->connect(box, &RecoverBox::recoveryExpired, [=] { - if (box) { - box->closeBox(); - } - }); + }, box->lifetime()); + + box->recoveryExpired( + ) | rpl::start_with_next([=] { + box->closeBox(); + }, box->lifetime()); }).fail([=](const RPCError &error) { _recoverRequestId = 0; _view->show(Box(Lang::Hard::ServerError() @@ -509,6 +564,11 @@ void FormController::reloadPassword() { requestPassword(); } +void FormController::reloadAndSubmitPassword(const QByteArray &password) { + _savedPasswordValue = password; + requestPassword(); +} + void FormController::cancelPassword() { if (_passwordRequestId) { return; @@ -534,22 +594,30 @@ void FormController::cancelPassword() { } void FormController::validateSecureSecret( - bytes::const_span salt, bytes::const_span encryptedSecret, - bytes::const_span password, + bytes::const_span passwordHashForSecret, + bytes::const_span passwordBytes, uint64 serverSecretId) { - if (!salt.empty() && !encryptedSecret.empty()) { - _secret = DecryptSecureSecret(salt, encryptedSecret, password); + Expects(!passwordBytes.empty() || !passwordHashForSecret.empty()); + + if (!passwordHashForSecret.empty() && !encryptedSecret.empty()) { + _secret = DecryptSecureSecret( + encryptedSecret, + passwordHashForSecret); if (_secret.empty()) { _secretId = 0; LOG(("API Error: Failed to decrypt secure secret.")); - suggestReset(bytes::make_vector(password)); + if (!passwordBytes.empty()) { + suggestReset(bytes::make_vector(passwordBytes)); + } return; } else if (CountSecureSecretId(_secret) != serverSecretId) { _secret.clear(); _secretId = 0; LOG(("API Error: Wrong secure secret id.")); - suggestReset(bytes::make_vector(password)); + if (!passwordBytes.empty()) { + suggestReset(bytes::make_vector(passwordBytes)); + } return; } else { _secretId = serverSecretId; @@ -557,7 +625,7 @@ void FormController::validateSecureSecret( } } if (_secret.empty()) { - generateSecret(password); + generateSecret(passwordBytes); } _secretReady.fire({}); } @@ -569,13 +637,9 @@ void FormController::suggestReset(bytes::vector password) { // } } _view->suggestReset([=] { - const auto hashForAuth = openssl::Sha256(bytes::concatenate( - _password.salt, - password, - _password.salt)); using Flag = MTPDaccount_passwordInputSettings::Flag; _saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(hashForAuth), + MTP_bytes(passwordHashForAuth(password)), MTP_account_passwordInputSettings( MTP_flags(Flag::f_new_secure_salt | Flag::f_new_secure_secret @@ -1696,6 +1760,8 @@ void FormController::valueSaveFailed(not_null value) { } void FormController::generateSecret(bytes::const_span password) { + Expects(!password.empty()); + if (_saveSecretRequestId) { return; } @@ -1707,20 +1773,20 @@ void FormController::generateSecret(bytes::const_span password) { _password.newSecureSalt, randomSaltPart); - auto secureSecretId = CountSecureSecretId(secret); - auto encryptedSecret = EncryptSecureSecret( + auto saved = SavedCredentials(); + saved.hashForAuth = passwordHashForAuth(password); + saved.hashForSecret = CountPasswordHashForSecret( newSecureSaltFull, - secret, password); + saved.secretId = CountSecureSecretId(secret); - const auto hashForAuth = openssl::Sha256(bytes::concatenate( - _password.salt, - password, - _password.salt)); + auto encryptedSecret = EncryptSecureSecret( + secret, + saved.hashForSecret); using Flag = MTPDaccount_passwordInputSettings::Flag; _saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(hashForAuth), + MTP_bytes(saved.hashForAuth), MTP_account_passwordInputSettings( MTP_flags(Flag::f_new_secure_salt | Flag::f_new_secure_secret @@ -1731,11 +1797,15 @@ void FormController::generateSecret(bytes::const_span password) { MTPstring(), // email MTP_bytes(newSecureSaltFull), MTP_bytes(encryptedSecret), - MTP_long(secureSecretId)) + MTP_long(saved.secretId)) )).done([=](const MTPBool &result) { + Auth().data().rememberPassportCredentials( + std::move(saved), + kRememberCredentialsDelay); + _saveSecretRequestId = 0; _secret = secret; - _secretId = secureSecretId; + _secretId = saved.secretId; //_password.salt = newPasswordSaltFull; for (const auto &callback : base::take(_secretCallbacks)) { callback(); @@ -1988,6 +2058,7 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) { } void FormController::formFail(const QString &error) { + _savedPasswordValue = QByteArray(); _serviceErrorText = error; _view->showCriticalError( lang(lng_passport_form_error) + "\n" + error); @@ -2036,7 +2107,13 @@ void FormController::showForm() { return; } if (!_password.salt.empty()) { - _view->showAskPassword(); + if (!_savedPasswordValue.isEmpty()) { + submitPassword(base::duplicate(_savedPasswordValue)); + } else if (const auto saved = Auth().data().passportCredentials()) { + checkSavedPasswordSettings(*saved); + } else { + _view->showAskPassword(); + } } else { _view->showNoPassword(); } diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index 573bd3a5ff..d461ce09a1 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -24,6 +24,12 @@ class Controller; namespace Passport { +struct SavedCredentials { + bytes::vector hashForAuth; + bytes::vector hashForSecret; + uint64 secretId = 0; +}; + class ViewController; struct FormRequest { @@ -247,11 +253,12 @@ public: UserData *bot() const; QString privacyPolicyUrl() const; std::vector> submitGetErrors(); - void submitPassword(const QString &password); + void submitPassword(const QByteArray &password); void recoverPassword(); rpl::producer passwordError() const; const PasswordSettings &passwordSettings() const; void reloadPassword(); + void reloadAndSubmitPassword(const QByteArray &password); void cancelPassword(); bool canAddScan(not_null value) const; @@ -335,10 +342,11 @@ private: bool applyPassword(const MTPDaccount_password &settings); bool applyPassword(PasswordSettings &&settings); bytes::vector passwordHashForAuth(bytes::const_span password) const; + void checkSavedPasswordSettings(const SavedCredentials &credentials); void validateSecureSecret( - bytes::const_span salt, bytes::const_span encryptedSecret, - bytes::const_span password, + bytes::const_span passwordHashForSecret, + bytes::const_span passwordBytes, uint64 serverSecretId); void decryptValues(); void decryptValue(Value &value); @@ -413,6 +421,7 @@ private: mtpRequestId _passwordCheckRequestId = 0; PasswordSettings _password; + QByteArray _savedPasswordValue; Form _form; bool _cancelled = false; mtpRequestId _recoverRequestId = 0; diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index e408989236..119f4b5986 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -362,9 +362,8 @@ PanelController::PanelController(not_null form) , _scopes(ComputeScopes(_form)) { _form->secretReadyEvents( ) | rpl::start_with_next([=] { - if (_panel) { - _panel->showForm(); - } + ensurePanelCreated(); + _panel->showForm(); }, lifetime()); _form->verificationNeeded( @@ -435,7 +434,7 @@ void PanelController::submitForm() { } } -void PanelController::submitPassword(const QString &password) { +void PanelController::submitPassword(const QByteArray &password) { _form->submitPassword(password); } @@ -467,7 +466,10 @@ void PanelController::setupPassword() { Expects(_panel != nullptr); const auto &settings = _form->passwordSettings(); - Assert(settings.salt.empty()); + if (!settings.salt.empty()) { + showAskPassword(); + return; + } constexpr auto kRandomPart = 8; auto newPasswordSalt = QByteArray( @@ -494,9 +496,22 @@ void PanelController::setupPassword() { notEmptyPassport, hint, newSecureSecretSalt)); - box->connect(box, &PasscodeBox::reloadPassword, [=] { + box->newPasswordSet( + ) | rpl::filter([=](const QByteArray &password) { + return !password.isEmpty(); + }) | rpl::start_with_next([=](const QByteArray &password) { + _form->reloadAndSubmitPassword(password); + }, box->lifetime()); + + rpl::merge( + box->passwordReloadNeeded(), + box->newPasswordSet( + ) | rpl::filter([=](const QByteArray &password) { + return password.isEmpty(); + }) | rpl::map([] { return rpl::empty_value(); }) + ) | rpl::start_with_next([=] { _form->reloadPassword(); - }); + }, box->lifetime()); } void PanelController::cancelPasswordSubmit() { diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index e92b97c295..487cb76d22 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -68,7 +68,7 @@ public: not_null bot() const; QString privacyPolicyUrl() const; void submitForm(); - void submitPassword(const QString &password); + void submitPassword(const QByteArray &password); void recoverPassword(); rpl::producer passwordError() const; QString passwordHint() const; diff --git a/Telegram/SourceFiles/passport/passport_panel_password.cpp b/Telegram/SourceFiles/passport/passport_panel_password.cpp index 9fc5fb7888..3cc5958e58 100644 --- a/Telegram/SourceFiles/passport/passport_panel_password.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_password.cpp @@ -93,7 +93,7 @@ void PanelAskPassword::hideError() { } void PanelAskPassword::submit() { - _controller->submitPassword(_password->getLastText()); + _controller->submitPassword(_password->getLastText().toUtf8()); } void PanelAskPassword::recover() { diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp index 6651c2a643..5b5ee2b10a 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp @@ -83,7 +83,12 @@ void CloudPasswordState::onEdit() { _notEmptyPassport, _curPasswordHint, _newSecureSecretSalt)); - connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword())); + rpl::merge( + box->newPasswordSet() | rpl::map([] { return rpl::empty_value(); }), + box->passwordReloadNeeded() + ) | rpl::start_with_next([=] { + onReloadPassword(); + }, box->lifetime()); } void CloudPasswordState::onTurnOff() { @@ -113,7 +118,13 @@ void CloudPasswordState::onTurnOff() { _curPasswordHint, _newSecureSecretSalt, true)); - connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword())); + rpl::merge( + box->newPasswordSet( + ) | rpl::map([] { return rpl::empty_value(); }), + box->passwordReloadNeeded() + ) | rpl::start_with_next([=] { + onReloadPassword(); + }, box->lifetime()); } }