From bcbf009a6289d031d118c7437617524a5cc3d980 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 9 May 2022 19:24:34 +0300 Subject: [PATCH] Added ability to reset cloud password without recovery email. --- Telegram/Resources/langs/lang.strings | 1 + .../settings_cloud_password_email_confirm.cpp | 62 ++++- .../settings_cloud_password_input.cpp | 236 ++++++++++++++++-- .../settings/settings_privacy_security.cpp | 3 - .../SourceFiles/ui/text/format_values.cpp | 4 + Telegram/SourceFiles/ui/text/format_values.h | 1 + 6 files changed, 282 insertions(+), 25 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 64e3ca4764..8dedebdf55 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -564,6 +564,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_cloud_password_skip_hint" = "Skip hint"; "lng_settings_cloud_password_save" = "Save and Finish"; "lng_settings_cloud_password_email_confirm" = "Confirm and Finish"; +"lng_settings_cloud_password_reset_in" = "You can reset your password in {duration}."; "lng_clear_payment_info_title" = "Clear payment info"; "lng_clear_payment_info_sure" = "Are you sure you want to clear your payment and shipping info?"; diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp index 36edfd9598..3b6cee8e07 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/cloud_password/settings_cloud_password_email_confirm.h" #include "api/api_cloud_password.h" +#include "base/unixtime.h" #include "core/core_cloud_password.h" #include "lang/lang_keys.h" #include "settings/cloud_password/settings_cloud_password_common.h" @@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/cloud_password/settings_cloud_password_manage.h" #include "settings/cloud_password/settings_cloud_password_start.h" #include "ui/boxes/confirm_box.h" +#include "ui/text/format_values.h" #include "ui/widgets/buttons.h" #include "ui/widgets/sent_code_field.h" #include "ui/wrap/padding_wrap.h" @@ -155,7 +157,65 @@ void EmailConfirm::setupContent() { newInput->hideError(); }); }); - resend->setVisible(recoverEmailPattern.isEmpty()); + + if (!recoverEmailPattern.isEmpty()) { + resend->setText(tr::lng_signin_try_password(tr::now)); + + resend->setClickedCallback([=] { + const auto reset = [=](Fn close) { + if (_requestLifetime) { + return; + } + _requestLifetime = cloudPassword().resetPassword( + ) | rpl::start_with_next_error_done([=]( + Api::CloudPassword::ResetRetryDate retryDate) { + _requestLifetime.destroy(); + const auto left = std::max( + retryDate - base::unixtime::now(), + 60); + controller()->show(Ui::MakeInformBox( + tr::lng_cloud_password_reset_later( + tr::now, + lt_duration, + Ui::FormatResetCloudPasswordIn(left)))); + }, [=](const QString &type) { + _requestLifetime.destroy(); + }, [=] { + _requestLifetime.destroy(); + + cloudPassword().reload(); + using PasswordState = Core::CloudPasswordState; + _requestLifetime = cloudPassword().state( + ) | rpl::filter([=](const PasswordState &s) { + return s.pendingResetDate != 0; + }) | rpl::take( + 1 + ) | rpl::start_with_next([=](const PasswordState &s) { + const auto left = (s.pendingResetDate + - base::unixtime::now()); + if (left > 0) { + _requestLifetime.destroy(); + controller()->show(Ui::MakeInformBox( + tr::lng_settings_cloud_password_reset_in( + tr::now, + lt_duration, + Ui::FormatResetCloudPasswordIn(left)))); + setStepData(StepData()); + showBack(); + } + }); + }); + _requestLifetime.add(close); + }; + + controller()->show(Ui::MakeConfirmBox({ + .text = tr::lng_cloud_password_reset_with_email(), + .confirmed = reset, + .confirmText = tr::lng_cloud_password_reset_ok(), + .confirmStyle = &st::attentionBoxButton, + })); + }); + } const auto button = AddDoneButton( content, diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp index 47b2ffe42e..a7671f2788 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_cloud_password.h" #include "base/qt_signal_producer.h" +#include "base/timer.h" +#include "base/unixtime.h" #include "core/core_cloud_password.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" @@ -17,12 +19,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/cloud_password/settings_cloud_password_hint.h" #include "settings/cloud_password/settings_cloud_password_manage.h" #include "ui/boxes/confirm_box.h" +#include "ui/text/format_values.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" +#include "styles/style_layers.h" #include "styles/style_settings.h" namespace Settings { @@ -93,6 +97,12 @@ public: [[nodiscard]] rpl::producer> removeFromStack() override; private: + void setupRecoverButton( + not_null container, + not_null button, + not_null info, + Fn recoverCallback); + rpl::variable> _removesFromStack; rpl::lifetime _requestLifetime; @@ -192,32 +202,72 @@ void Input::setupContent() { } }, hintInfo->lifetime()); - const auto recover = AddLinkButton(content, newInput); - recover->setText(tr::lng_signin_recover(tr::now)); - recover->setClickedCallback([=] { + auto recoverCallback = [=] { if (_requestLifetime) { return; } - _requestLifetime = cloudPassword().requestPasswordRecovery( - ) | rpl::start_with_next_error([=](const QString &pattern) { - _requestLifetime.destroy(); + const auto state = cloudPassword().stateCurrent(); + if (!state) { + return; + } + if (state->hasRecovery) { + _requestLifetime = cloudPassword().requestPasswordRecovery( + ) | rpl::start_with_next_error([=](const QString &pattern) { + _requestLifetime.destroy(); - auto data = stepData(); - data.processRecover = currentStepProcessRecover; - data.processRecover.emailPattern = pattern; - setStepData(std::move(data)); - showOther(CloudPasswordEmailConfirmId()); - }, [=](const QString &type) { - _requestLifetime.destroy(); + auto data = stepData(); + data.processRecover = currentStepProcessRecover; + data.processRecover.emailPattern = pattern; + setStepData(std::move(data)); + showOther(CloudPasswordEmailConfirmId()); + }, [=](const QString &type) { + _requestLifetime.destroy(); - error->show(); - if (MTP::IsFloodError(type)) { - error->setText(tr::lng_flood_error(tr::now)); - } else { - error->setText(Lang::Hard::ServerError()); - } - }); - }); + error->show(); + if (MTP::IsFloodError(type)) { + error->setText(tr::lng_flood_error(tr::now)); + } else { + error->setText(Lang::Hard::ServerError()); + } + }); + } else { + const auto callback = [=](Fn close) { + if (_requestLifetime) { + return; + } + close(); + _requestLifetime = cloudPassword().resetPassword( + ) | rpl::start_with_error_done([=](const QString &type) { + _requestLifetime.destroy(); + }, [=] { + _requestLifetime.destroy(); + }); + }; + controller()->show(Ui::MakeConfirmBox({ + .text = tr::lng_cloud_password_reset_no_email(), + .confirmed = callback, + .confirmText = tr::lng_cloud_password_reset_ok(), + .cancelText = tr::lng_cancel(), + .confirmStyle = &st::attentionBoxButton, + })); + } + }; + + const auto recover = AddLinkButton(content, newInput); + const auto resetInfo = Ui::CreateChild( + content, + QString(), + st::boxDividerLabel); + recover->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + resetInfo->moveToLeft(r.x(), r.y() + st::passcodeTextLine); + }, resetInfo->lifetime()); + + setupRecoverButton( + content, + recover, + resetInfo, + std::move(recoverCallback)); } else if (currentStepProcessRecover.setNewPassword && reenterInput) { const auto skip = AddLinkButton(content, reenterInput); skip->setText(tr::lng_settings_auto_night_disable(tr::now)); @@ -278,6 +328,14 @@ void Input::setupContent() { }, [=] { _requestLifetime.destroy(); + if (const auto state = cloudPassword().stateCurrent()) { + if (state->pendingResetDate > 0) { + auto lifetime = rpl::lifetime(); + lifetime = cloudPassword().cancelResetPassword( + ) | rpl::start_with_next([] {}); + } + } + auto data = stepData(); data.currentPassword = pass; setStepData(std::move(data)); @@ -352,6 +410,142 @@ void Input::setupContent() { Ui::ResizeFitChild(this, content); } +void Input::setupRecoverButton( + not_null container, + not_null button, + not_null info, + Fn recoverCallback) { + + struct Status { + enum class SuggestAction { + Recover, + Reset, + CancelReset, + }; + SuggestAction suggest = SuggestAction::Recover; + TimeId left = 0; + }; + + struct State { + base::Timer timer; + rpl::variable status; + }; + + const auto state = container->lifetime().make_state(); + + const auto updateStatus = [=] { + const auto passwordState = cloudPassword().stateCurrent(); + const auto date = passwordState ? passwordState->pendingResetDate : 0; + const auto left = (date - base::unixtime::now()); + state->status = Status{ + .suggest = ((left > 0) + ? Status::SuggestAction::CancelReset + : date + ? Status::SuggestAction::Reset + : Status::SuggestAction::Recover), + .left = left, + }; + }; + state->timer.setCallback(updateStatus); + updateStatus(); + + state->status.value( + ) | rpl::start_with_next([=](const Status &status) { + switch (status.suggest) { + case Status::SuggestAction::Recover: { + info->setText(QString()); + button->setText(tr::lng_signin_recover(tr::now)); + } break; + case Status::SuggestAction::Reset: { + info->setText(QString()); + button->setText(tr::lng_cloud_password_reset_ready(tr::now)); + } break; + case Status::SuggestAction::CancelReset: { + info->setText( + tr::lng_settings_cloud_password_reset_in( + tr::now, + lt_duration, + Ui::FormatResetCloudPasswordIn(status.left))); + button->setText( + tr::lng_cloud_password_reset_cancel_title(tr::now)); + } break; + } + }, container->lifetime()); + + cloudPassword().state( + ) | rpl::start_with_next([=](const Core::CloudPasswordState &passState) { + updateStatus(); + state->timer.cancel(); + if (passState.pendingResetDate) { + state->timer.callEach(999); + } + }, container->lifetime()); + + button->setClickedCallback([=] { + const auto passState = cloudPassword().stateCurrent(); + if (_requestLifetime || !passState) { + return; + } + updateStatus(); + const auto suggest = state->status.current().suggest; + if (suggest == Status::SuggestAction::Recover) { + recoverCallback(); + } else if (suggest == Status::SuggestAction::CancelReset) { + const auto cancel = [=](Fn close) { + if (_requestLifetime) { + return; + } + close(); + _requestLifetime = cloudPassword().cancelResetPassword( + ) | rpl::start_with_error_done([=](const QString &error) { + _requestLifetime.destroy(); + }, [=] { + _requestLifetime.destroy(); + }); + }; + controller()->show(Ui::MakeConfirmBox({ + .text = tr::lng_cloud_password_reset_cancel_sure(), + .confirmed = cancel, + .confirmText = tr::lng_box_yes(), + .cancelText = tr::lng_box_no(), + })); + } else if (suggest == Status::SuggestAction::Reset) { + _requestLifetime = cloudPassword().resetPassword( + ) | rpl::start_with_next_error_done([=]( + Api::CloudPassword::ResetRetryDate retryDate) { + _requestLifetime.destroy(); + const auto left = std::max( + retryDate - base::unixtime::now(), + 60); + controller()->show(Ui::MakeInformBox( + tr::lng_cloud_password_reset_later( + tr::now, + lt_duration, + Ui::FormatResetCloudPasswordIn(left)))); + }, [=](const QString &type) { + _requestLifetime.destroy(); + }, [=] { + _requestLifetime.destroy(); + + cloudPassword().reload(); + using PasswordState = Core::CloudPasswordState; + _requestLifetime = cloudPassword().state( + ) | rpl::filter([=](const PasswordState &s) { + return !s.hasPassword; + }) | rpl::take( + 1 + ) | rpl::start_with_next([=](const PasswordState &s) { + _requestLifetime.destroy(); + controller()->show(Ui::MakeInformBox( + tr::lng_cloud_password_removed())); + setStepData(StepData()); + showBack(); + }); + }); + } + }); +} + } // namespace CloudPassword Type CloudPasswordInputId() { diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 06afdafb04..78812bc11c 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_local_passcode.h" #include "settings/settings_privacy_controllers.h" #include "base/timer_rpl.h" -#include "base/unixtime.h" #include "boxes/edit_privacy_box.h" #include "boxes/passcode_box.h" #include "boxes/auto_lock_box.h" @@ -457,7 +456,6 @@ void SetupCloudPassword( rpl::duplicate(noconfirmed), _1 && !_2)); disable->entity()->addClickHandler(remove); -#endif auto resetAt = session->api().cloudPassword().state( ) | rpl::map([](const State &state) { @@ -574,7 +572,6 @@ void SetupCloudPassword( } }); -#if 0 const auto abort = container->add( object_ptr>( container, diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp index 7d3556d9d0..ba6019d86f 100644 --- a/Telegram/SourceFiles/ui/text/format_values.cpp +++ b/Telegram/SourceFiles/ui/text/format_values.cpp @@ -436,4 +436,8 @@ QString FormatMuteForTiny(float64 sec) { : QString(); } +QString FormatResetCloudPasswordIn(float64 sec) { + return (sec >= 3600) ? FormatTTL(sec) : FormatDurationText(sec); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/text/format_values.h b/Telegram/SourceFiles/ui/text/format_values.h index 47adafb975..8821052d8c 100644 --- a/Telegram/SourceFiles/ui/text/format_values.h +++ b/Telegram/SourceFiles/ui/text/format_values.h @@ -31,6 +31,7 @@ inline constexpr auto FileStatusSizeFailed = 0x7FFFFFF2; [[nodiscard]] QString FormatTTLTiny(float64 ttl); [[nodiscard]] QString FormatMuteFor(float64 sec); [[nodiscard]] QString FormatMuteForTiny(float64 sec); +[[nodiscard]] QString FormatResetCloudPasswordIn(float64 sec); struct CurrencyRule { const char *international = "";