Confirm 2sv recovery email by code.

This commit is contained in:
John Preston 2018-11-07 13:39:31 +04:00
parent 93678a07a8
commit be3e43e6cb
15 changed files with 441 additions and 108 deletions

View File

@ -342,6 +342,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_sessions_about" = "Control your sessions on other devices."; "lng_settings_sessions_about" = "Control your sessions on other devices.";
"lng_settings_passcode_disable" = "Disable passcode"; "lng_settings_passcode_disable" = "Disable passcode";
"lng_settings_password_disable" = "Disable cloud password"; "lng_settings_password_disable" = "Disable cloud password";
"lng_settings_password_abort" = "Abort two-step verification setup";
"lng_settings_password_reenter_email" = "Re-enter recovery email";
"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco"; "lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco";
"lng_settings_name_label" = "Name"; "lng_settings_name_label" = "Name";
"lng_settings_username_label" = "Username"; "lng_settings_username_label" = "Username";
@ -449,7 +451,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passcode_logout" = "Log out"; "lng_passcode_logout" = "Log out";
"lng_passcode_need_unblock" = "You need to unlock me first."; "lng_passcode_need_unblock" = "You need to unlock me first.";
"lng_cloud_password_waiting" = "Confirmation link sent to {email}..."; "lng_cloud_password_waiting_code" = "Confirmation code sent to {email}...";
"lng_cloud_password_confirm" = "Confirm recovery email";
"lng_cloud_password_change" = "Change cloud password"; "lng_cloud_password_change" = "Change cloud password";
"lng_cloud_password_create" = "Cloud password"; "lng_cloud_password_create" = "Cloud password";
"lng_cloud_password_remove" = "Remove cloud password"; "lng_cloud_password_remove" = "Remove cloud password";
@ -475,6 +478,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_cloud_password_wrong" = "Wrong cloud password"; "lng_cloud_password_wrong" = "Wrong cloud password";
"lng_cloud_password_is_same" = "Password was not changed"; "lng_cloud_password_is_same" = "Password was not changed";
"lng_cloud_password_passport_losing" = "Warning! All data saved in your Telegram Passport will be lost!"; "lng_cloud_password_passport_losing" = "Warning! All data saved in your Telegram Passport will be lost!";
"lng_cloud_password_resend" = "Resend code";
"lng_cloud_password_resent" = "Code was resent.";
"lng_connection_auto_connecting" = "Default (connecting...)"; "lng_connection_auto_connecting" = "Default (connecting...)";
"lng_connection_auto" = "Default ({transport} used)"; "lng_connection_auto" = "Default ({transport} used)";
@ -1602,7 +1607,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_create_password" = "Please create a password which will be used\nto encrypt your personal data."; "lng_passport_create_password" = "Please create a password which will be used\nto encrypt your personal data.";
"lng_passport_about_password" = "This password will also be required whenever\nyou log in to a new device."; "lng_passport_about_password" = "This password will also be required whenever\nyou log in to a new device.";
"lng_passport_password_create" = "Create a password"; "lng_passport_password_create" = "Create a password";
"lng_passport_link_sent" = "A confirmation link was sent to your email\n{email}"; "lng_passport_email_validate" = "Validate";
"lng_passport_code_sent" = "A confirmation code was sent to\n{email}";
"lng_passport_stop_password_sure" = "Are you sure you want to cancel setting up your password?"; "lng_passport_stop_password_sure" = "Are you sure you want to cancel setting up your password?";
"lng_passport_password_placeholder" = "Your password"; "lng_passport_password_placeholder" = "Your password";
"lng_passport_next" = "Next"; "lng_passport_next" = "Next";

View File

@ -5038,16 +5038,7 @@ void ApiWrap::reloadPasswordState() {
} }
void ApiWrap::clearUnconfirmedPassword() { void ApiWrap::clearUnconfirmedPassword() {
_passwordRequestId = request(MTPaccount_UpdatePasswordSettings( _passwordRequestId = request(MTPaccount_CancelPasswordEmail(
MTP_inputCheckPasswordEmpty(),
MTP_account_passwordInputSettings(
MTP_flags(
MTPDaccount_passwordInputSettings::Flag::f_email),
MTP_passwordKdfAlgoUnknown(), // new_algo
MTP_bytes(QByteArray()), // new_password_hash
MTP_string(QString()), // hint
MTP_string(QString()), // email
MTPSecureSecretSettings())
)).done([=](const MTPBool &result) { )).done([=](const MTPBool &result) {
_passwordRequestId = 0; _passwordRequestId = 0;
reloadPasswordState(); reloadPasswordState();

View File

@ -118,12 +118,15 @@ public:
_boxClosingStream.fire({}); _boxClosingStream.fire({});
} }
void setDelegate(BoxContentDelegate *newDelegate) { void setDelegate(not_null<BoxContentDelegate*> newDelegate) {
_delegate = newDelegate; _delegate = newDelegate;
_preparing = true; _preparing = true;
prepare(); prepare();
finishPrepare(); finishPrepare();
} }
not_null<BoxContentDelegate*> getDelegate() const {
return _delegate;
}
public slots: public slots:
void onScrollToY(int top, int bottom = -1); void onScrollToY(int top, int bottom = -1);
@ -187,10 +190,6 @@ protected:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void keyPressEvent(QKeyEvent *e) override; void keyPressEvent(QKeyEvent *e) override;
not_null<BoxContentDelegate*> getDelegate() const {
return _delegate;
}
private slots: private slots:
void onScroll(); void onScroll();
void onInnerResize(); void onInnerResize();

View File

@ -10,12 +10,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/bytes.h" #include "base/bytes.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "boxes/confirm_box.h" #include "boxes/confirm_box.h"
#include "boxes/confirm_phone_box.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "auth_session.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/fade_wrap.h"
#include "passport/passport_encryption.h" #include "passport/passport_encryption.h"
#include "passport/passport_panel_edit_contact.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_passport.h"
namespace {
} // namespace
PasscodeBox::PasscodeBox(QWidget*, bool turningOff) PasscodeBox::PasscodeBox(QWidget*, bool turningOff)
: _turningOff(turningOff) : _turningOff(turningOff)
@ -64,6 +75,10 @@ rpl::producer<> PasscodeBox::passwordReloadNeeded() const {
return _passwordReloadNeeded.events(); return _passwordReloadNeeded.events();
} }
rpl::producer<> PasscodeBox::clearUnconfirmedPassword() const {
return _clearUnconfirmedPassword.events();
}
bool PasscodeBox::currentlyHave() const { bool PasscodeBox::currentlyHave() const {
return _cloudPwd ? (!!_curRequest) : Global::LocalPasscode(); return _cloudPwd ? (!!_curRequest) : Global::LocalPasscode();
} }
@ -266,20 +281,96 @@ void PasscodeBox::setPasswordFail(const RPCError &error) {
void PasscodeBox::setPasswordFail( void PasscodeBox::setPasswordFail(
const QByteArray &newPasswordBytes, const QByteArray &newPasswordBytes,
const QString &email,
const RPCError &error) { const RPCError &error) {
if (error.type() == qstr("EMAIL_UNCONFIRMED")) { const auto prefix = qstr("EMAIL_UNCONFIRMED_");
if (error.type().startsWith(prefix)) {
const auto codeLength = error.type().mid(prefix.size()).toInt();
closeReplacedBy(); closeReplacedBy();
_setRequest = 0; _setRequest = 0;
_newPasswordSet.fire_copy(newPasswordBytes); validateEmail(email, codeLength, newPasswordBytes);
getDelegate()->show(
Box<InformBox>(lang(lng_cloud_password_almost)),
LayerOption::CloseOther);
} else { } else {
setPasswordFail(error); setPasswordFail(error);
} }
} }
void PasscodeBox::validateEmail(
const QString &email,
int codeLength,
const QByteArray &newPasswordBytes) {
const auto errors = std::make_shared<rpl::event_stream<QString>>();
const auto resent = std::make_shared<rpl::event_stream<QString>>();
const auto set = std::make_shared<bool>(false);
const auto submit = [=](QString code) {
if (_setRequest) {
return;
}
_setRequest = request(MTPaccount_ConfirmPasswordEmail(
MTP_string(code)
)).done([=](const MTPBool &result) {
*set = true;
setPasswordDone(newPasswordBytes);
}).fail([=](const RPCError &error) {
_setRequest = 0;
if (MTP::isFloodError(error)) {
errors->fire(lang(lng_flood_error));
} else if (error.type() == qstr("CODE_INVALID")) {
errors->fire(lang(lng_signin_wrong_code));
} else if (error.type() == qstr("EMAIL_HASH_EXPIRED")) {
const auto weak = make_weak(this);
_clearUnconfirmedPassword.fire({});
if (weak) {
auto box = Box<InformBox>(
Lang::Hard::EmailConfirmationExpired());
weak->getDelegate()->show(
std::move(box),
LayerOption::CloseOther);
}
} else {
errors->fire(Lang::Hard::ServerError());
}
}).handleFloodErrors().send();
};
const auto resend = [=] {
if (_setRequest) {
return;
}
_setRequest = request(MTPaccount_ResendPasswordEmail(
)).done([=](const MTPBool &result) {
_setRequest = 0;
resent->fire(lang(lng_cloud_password_resent));
}).fail([=](const RPCError &error) {
_setRequest = 0;
errors->fire(Lang::Hard::ServerError());
}).send();
};
const auto box = getDelegate()->show(
Passport::VerifyEmailBox(
email,
codeLength,
submit,
resend,
errors->events(),
resent->events()),
LayerOption::KeepOther);
box->setCloseByOutsideClick(false);
box->setCloseByEscape(false);
box->boxClosing(
) | rpl::filter([=] {
return !*set;
}) | start_with_next([=, weak = make_weak(this)] {
if (weak) {
weak->_clearUnconfirmedPassword.fire({});
}
if (weak) {
weak->closeBox();
}
}, box->lifetime());
}
void PasscodeBox::handleSrpIdInvalid() { void PasscodeBox::handleSrpIdInvalid() {
const auto now = getms(true); const auto now = getms(true);
if (_lastSrpIdInvalidTime > 0 if (_lastSrpIdInvalidTime > 0
@ -474,7 +565,7 @@ void PasscodeBox::sendClearCloudPassword(
)).done([=](const MTPBool &result) { )).done([=](const MTPBool &result) {
setPasswordDone({}); setPasswordDone({});
}).handleFloodErrors().fail([=](const RPCError &error) mutable { }).handleFloodErrors().fail([=](const RPCError &error) mutable {
setPasswordFail({}, error); setPasswordFail({}, QString(), error);
}).send(); }).send();
} }
@ -505,7 +596,7 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
)).done([=](const MTPBool &result) { )).done([=](const MTPBool &result) {
setPasswordDone(newPasswordBytes); setPasswordDone(newPasswordBytes);
}).fail([=](const RPCError &error) { }).fail([=](const RPCError &error) {
setPasswordFail(newPasswordBytes, error); setPasswordFail(newPasswordBytes, email, error);
}).send(); }).send();
} }
@ -655,7 +746,7 @@ void PasscodeBox::sendChangeCloudPassword(
)).done([=](const MTPBool &result) { )).done([=](const MTPBool &result) {
setPasswordDone(newPasswordBytes); setPasswordDone(newPasswordBytes);
}).handleFloodErrors().fail([=](const RPCError &error) { }).handleFloodErrors().fail([=](const RPCError &error) {
setPasswordFail(newPasswordBytes, error); setPasswordFail(newPasswordBytes, QString(), error);
}).send(); }).send();
} }
@ -890,3 +981,81 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) {
_recoverCode->setFocus(); _recoverCode->setFocus();
return false; return false;
} }
RecoveryEmailValidation ConfirmRecoveryEmail(const QString &pattern) {
const auto errors = std::make_shared<rpl::event_stream<QString>>();
const auto resent = std::make_shared<rpl::event_stream<QString>>();
const auto requestId = std::make_shared<mtpRequestId>(0);
const auto weak = std::make_shared<QPointer<BoxContent>>();
const auto reloads = std::make_shared<rpl::event_stream<>>();
const auto cancels = std::make_shared<rpl::event_stream<>>();
const auto submit = [=](QString code) {
if (*requestId) {
return;
}
const auto done = [=](const MTPBool &result) {
*requestId = 0;
reloads->fire({});
if (*weak) {
(*weak)->getDelegate()->show(
Box<InformBox>(lang(lng_cloud_password_was_set)),
LayerOption::CloseOther);
}
};
const auto fail = [=](const RPCError &error) {
const auto skip = MTP::isDefaultHandledError(error)
&& !MTP::isFloodError(error);
if (skip) {
return false;
}
*requestId = 0;
if (MTP::isFloodError(error)) {
errors->fire(lang(lng_flood_error));
} else if (error.type() == qstr("CODE_INVALID")) {
errors->fire(lang(lng_signin_wrong_code));
} else if (error.type() == qstr("EMAIL_HASH_EXPIRED")) {
cancels->fire({});
if (*weak) {
auto box = Box<InformBox>(
Lang::Hard::EmailConfirmationExpired());
(*weak)->getDelegate()->show(
std::move(box),
LayerOption::CloseOther);
}
} else {
errors->fire(Lang::Hard::ServerError());
}
return true;
};
*requestId = MTP::send(
MTPaccount_ConfirmPasswordEmail(MTP_string(code)),
rpcDone(done),
rpcFail(fail));
};
const auto resend = [=] {
if (*requestId) {
return;
}
*requestId = MTP::send(MTPaccount_ResendPasswordEmail(
), rpcDone([=](const MTPBool &result) {
*requestId = 0;
resent->fire(lang(lng_cloud_password_resent));
}), rpcFail([=](const RPCError &error) {
*requestId = 0;
errors->fire(Lang::Hard::ServerError());
return true;
}));
};
auto box = Passport::VerifyEmailBox(
pattern,
0,
submit,
resend,
errors->events(),
resent->events());
*weak = box.data();
return { std::move(box), reloads->events(), cancels->events() };
}

View File

@ -32,6 +32,7 @@ public:
rpl::producer<QByteArray> newPasswordSet() const; rpl::producer<QByteArray> newPasswordSet() const;
rpl::producer<> passwordReloadNeeded() const; rpl::producer<> passwordReloadNeeded() const;
rpl::producer<> clearUnconfirmedPassword() const;
protected: protected:
void prepare() override; void prepare() override;
@ -59,7 +60,12 @@ private:
void setPasswordFail(const RPCError &error); void setPasswordFail(const RPCError &error);
void setPasswordFail( void setPasswordFail(
const QByteArray &newPasswordBytes, const QByteArray &newPasswordBytes,
const QString &email,
const RPCError &error); const RPCError &error);
void validateEmail(
const QString &email,
int codeLength,
const QByteArray &newPasswordBytes);
void recoverStarted(const MTPauth_PasswordRecovery &result); void recoverStarted(const MTPauth_PasswordRecovery &result);
void recoverStartFail(const RPCError &error); void recoverStartFail(const RPCError &error);
@ -90,9 +96,6 @@ private:
const Core::CloudPasswordResult &check, const Core::CloudPasswordResult &check,
const QString &newPassword, const QString &newPassword,
Fn<void()> callback); Fn<void()> callback);
void resetSecretAndChangePassword(
const bytes::vector &oldPasswordHash,
const QString &newPassword);
void sendClearCloudPassword(const QString &oldPassword); void sendClearCloudPassword(const QString &oldPassword);
void sendClearCloudPassword(const Core::CloudPasswordResult &check); void sendClearCloudPassword(const Core::CloudPasswordResult &check);
@ -134,6 +137,7 @@ private:
rpl::event_stream<QByteArray> _newPasswordSet; rpl::event_stream<QByteArray> _newPasswordSet;
rpl::event_stream<> _passwordReloadNeeded; rpl::event_stream<> _passwordReloadNeeded;
rpl::event_stream<> _clearUnconfirmedPassword;
}; };
@ -173,3 +177,10 @@ private:
rpl::event_stream<> _recoveryExpired; rpl::event_stream<> _recoveryExpired;
}; };
struct RecoveryEmailValidation {
object_ptr<BoxContent> box;
rpl::producer<> reloadRequests;
rpl::producer<> cancelRequests;
};
RecoveryEmailValidation ConfirmRecoveryEmail(const QString &pattern);

View File

@ -62,5 +62,9 @@ inline QString UnknownSecureScanError() {
return qsl("Unknown scan read error."); return qsl("Unknown scan read error.");
} }
inline QString EmailConfirmationExpired() {
return qsl("This email confirmation has expired. Please setup two-step verification once again.");
}
} // namespace Hard } // namespace Hard
} // namespace Lang } // namespace Lang

View File

@ -1016,15 +1016,7 @@ void FormController::cancelPassword() {
if (_passwordRequestId) { if (_passwordRequestId) {
return; return;
} }
_passwordRequestId = request(MTPaccount_UpdatePasswordSettings( _passwordRequestId = request(MTPaccount_CancelPasswordEmail(
MTP_inputCheckPasswordEmpty(),
MTP_account_passwordInputSettings(
MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
MTP_passwordKdfAlgoUnknown(), // new_algo
MTP_bytes(QByteArray()), // new_password_hash
MTP_string(QString()), // hint
MTP_string(QString()), // email
MTPSecureSecretSettings())
)).done([=](const MTPBool &result) { )).done([=](const MTPBool &result) {
_passwordRequestId = 0; _passwordRequestId = 0;
reloadPassword(); reloadPassword();

View File

@ -738,6 +738,11 @@ void PanelController::setupPassword() {
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_form->reloadPassword(); _form->reloadPassword();
}, box->lifetime()); }, box->lifetime());
box->clearUnconfirmedPassword(
) | rpl::start_with_next([=] {
_form->cancelPassword();
}, box->lifetime());
} }
void PanelController::cancelPasswordSubmit() { void PanelController::cancelPasswordSubmit() {
@ -748,6 +753,24 @@ void PanelController::cancelPasswordSubmit() {
[=] { if (*box) (*box)->closeBox(); _form->cancelPassword(); })); [=] { if (*box) (*box)->closeBox(); _form->cancelPassword(); }));
} }
void PanelController::validateRecoveryEmail() {
auto validation = ConfirmRecoveryEmail(unconfirmedEmailPattern());
std::move(
validation.reloadRequests
) | rpl::start_with_next([=] {
_form->reloadPassword();
}, validation.box->lifetime());
std::move(
validation.cancelRequests
) | rpl::start_with_next([=] {
_form->cancelPassword();
}, validation.box->lifetime());
show(std::move(validation.box));
}
bool PanelController::canAddScan(FileType type) const { bool PanelController::canAddScan(FileType type) const {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
@ -1335,12 +1358,15 @@ void PanelController::processVerificationNeeded(
text, text,
value->verification.codeLength, value->verification.codeLength,
[=](const QString &code) { _form->verify(value, code); }, [=](const QString &code) { _form->verify(value, code); },
nullptr, // resend
rpl::duplicate( rpl::duplicate(
update update
) | rpl::map([=](not_null<const Value*> field) { ) | rpl::map([=](not_null<const Value*> field) {
return field->verification.error; return field->verification.error;
}) | rpl::distinct_until_changed())); }) | rpl::distinct_until_changed(),
rpl::never<QString>()));
} else { } else {
Unexpected("Type in processVerificationNeeded."); Unexpected("Type in processVerificationNeeded.");
} }

View File

@ -95,6 +95,7 @@ public:
void setupPassword(); void setupPassword();
void cancelPasswordSubmit(); void cancelPasswordSubmit();
void validateRecoveryEmail();
bool canAddScan(FileType type) const; bool canAddScan(FileType type) const;
void uploadScan(FileType type, QByteArray &&content); void uploadScan(FileType type, QByteArray &&content);

View File

@ -35,8 +35,10 @@ public:
const QString &text, const QString &text,
int codeLength, int codeLength,
Fn<void(QString code)> submit, Fn<void(QString code)> submit,
Fn<void()> resend,
rpl::producer<QString> call, rpl::producer<QString> call,
rpl::producer<QString> error); rpl::producer<QString> error,
rpl::producer<QString> resent);
void setInnerFocus() override; void setInnerFocus() override;
@ -48,13 +50,15 @@ private:
const QString &text, const QString &text,
int codeLength, int codeLength,
Fn<void(QString code)> submit, Fn<void(QString code)> submit,
Fn<void()> resend,
rpl::producer<QString> call, rpl::producer<QString> call,
rpl::producer<QString> error); rpl::producer<QString> error,
rpl::producer<QString> resent);
QString _title; QString _title;
Fn<void()> _submit; Fn<void()> _submit;
QPointer<SentCodeField> _code; QPointer<SentCodeField> _code;
int _height = 0; QPointer<Ui::VerticalLayout> _content;
}; };
@ -64,44 +68,94 @@ VerifyBox::VerifyBox(
const QString &text, const QString &text,
int codeLength, int codeLength,
Fn<void(QString code)> submit, Fn<void(QString code)> submit,
Fn<void()> resend,
rpl::producer<QString> call, rpl::producer<QString> call,
rpl::producer<QString> error) rpl::producer<QString> error,
rpl::producer<QString> resent)
: _title(title) { : _title(title) {
setupControls( setupControls(
text, text,
codeLength, codeLength,
submit, submit,
resend,
std::move(call), std::move(call),
std::move(error)); std::move(error),
std::move(resent));
} }
void VerifyBox::setupControls( void VerifyBox::setupControls(
const QString &text, const QString &text,
int codeLength, int codeLength,
Fn<void(QString code)> submit, Fn<void(QString code)> submit,
Fn<void()> resend,
rpl::producer<QString> call, rpl::producer<QString> call,
rpl::producer<QString> error) { rpl::producer<QString> error,
const auto description = Ui::CreateChild<Ui::FlatLabel>( rpl::producer<QString> resent) {
this, _content = Ui::CreateChild<Ui::VerticalLayout>(this);
text,
Ui::FlatLabel::InitType::Simple,
st::boxLabel);
_code = Ui::CreateChild<SentCodeField>(
this,
st::defaultInputField,
langFactory(lng_change_phone_code_title));
const auto problem = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>( const auto small = style::margins(
this, st::boxPadding.left(),
0,
st::boxPadding.right(),
st::boxPadding.bottom());
const auto description = _content->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
this, _content,
QString(), text,
Ui::FlatLabel::InitType::Simple, Ui::FlatLabel::InitType::Simple,
st::passportVerifyErrorLabel)); st::boxLabel),
const auto waiter = Ui::CreateChild<Ui::FlatLabel>( small);
this, _code = _content->add(
std::move(call), object_ptr<SentCodeField>(
st::boxDividerLabel); _content,
st::defaultInputField,
langFactory(lng_change_phone_code_title)),
small);
const auto problem = _content->add(
object_ptr<Ui::FadeWrap<Ui::FlatLabel>>(
_content,
object_ptr<Ui::FlatLabel>(
_content,
QString(),
Ui::FlatLabel::InitType::Simple,
st::passportVerifyErrorLabel)),
small);
const auto waiter = _content->add(
object_ptr<Ui::FlatLabel>(
_content,
std::move(call),
st::boxDividerLabel),
small);
if (resend) {
auto link = TextWithEntities{ lang(lng_cloud_password_resend) };
link.entities.push_back(EntityInText(
EntityInTextCustomUrl,
0,
link.text.size(),
QString("internal:resend")));
const auto label = _content->add(
object_ptr<Ui::FlatLabel>(
_content,
rpl::single(
link
) | rpl::then(rpl::duplicate(
resent
) | rpl::map([](const QString &value) {
return TextWithEntities{ value };
})),
st::boxDividerLabel),
small);
std::move(
resent
) | rpl::start_with_next([=] {
_content->resizeToWidth(st::boxWidth);
}, _content->lifetime());
label->setClickHandlerFilter([=](auto&&...) {
resend();
return false;
});
}
std::move( std::move(
error error
) | rpl::start_with_next([=](const QString &error) { ) | rpl::start_with_next([=](const QString &error) {
@ -109,29 +163,12 @@ void VerifyBox::setupControls(
problem->hide(anim::type::normal); problem->hide(anim::type::normal);
} else { } else {
problem->entity()->setText(error); problem->entity()->setText(error);
_content->resizeToWidth(st::boxWidth);
problem->show(anim::type::normal); problem->show(anim::type::normal);
_code->showError(); _code->showError();
} }
}, lifetime()); }, lifetime());
auto y = 0;
const auto innerWidth = st::boxWidth
- st::boxPadding.left()
- st::boxPadding.right();
description->resizeToWidth(innerWidth);
description->moveToLeft(st::boxPadding.left(), y);
y += description->height() + st::boxPadding.bottom();
_code->resizeToWidth(innerWidth);
_code->moveToLeft(st::boxPadding.left(), y);
y += _code->height() + st::boxPadding.bottom();
problem->resizeToWidth(innerWidth);
problem->moveToLeft(st::boxPadding.left(), y);
y += problem->height() + st::boxPadding.top();
waiter->resizeToWidth(innerWidth);
waiter->moveToLeft(st::boxPadding.left(), y);
y += waiter->height() + st::boxPadding.bottom();
_height = y;
_submit = [=] { _submit = [=] {
submit(_code->getLastText()); submit(_code->getLastText());
}; };
@ -155,7 +192,11 @@ void VerifyBox::prepare() {
addButton(langFactory(lng_change_phone_new_submit), _submit); addButton(langFactory(lng_change_phone_new_submit), _submit);
addButton(langFactory(lng_cancel), [=] { closeBox(); }); addButton(langFactory(lng_cancel), [=] { closeBox(); });
setDimensions(st::boxWidth, _height); _content->resizeToWidth(st::boxWidth);
_content->heightValue(
) | rpl::start_with_next([=](int height) {
setDimensions(st::boxWidth, height);
}, _content->lifetime());
} }
} // namespace } // namespace
@ -364,22 +405,28 @@ object_ptr<BoxContent> VerifyPhoneBox(
lng_passport_confirm_phone(lt_phone, App::formatPhone(phone)), lng_passport_confirm_phone(lt_phone, App::formatPhone(phone)),
codeLength, codeLength,
submit, submit,
nullptr,
std::move(call), std::move(call),
std::move(error)); std::move(error),
rpl::never<QString>());
} }
object_ptr<BoxContent> VerifyEmailBox( object_ptr<BoxContent> VerifyEmailBox(
const QString &email, const QString &email,
int codeLength, int codeLength,
Fn<void(QString code)> submit, Fn<void(QString code)> submit,
rpl::producer<QString> error) { Fn<void()> resend,
rpl::producer<QString> error,
rpl::producer<QString> resent) {
return Box<VerifyBox>( return Box<VerifyBox>(
lang(lng_passport_email_title), lang(lng_passport_email_title),
lng_passport_confirm_email(lt_email, email), lng_passport_confirm_email(lt_email, email),
codeLength, codeLength,
submit, submit,
resend,
rpl::single(QString()), rpl::single(QString()),
std::move(error)); std::move(error),
std::move(resent));
} }
} // namespace Passport } // namespace Passport

View File

@ -83,6 +83,8 @@ object_ptr<BoxContent> VerifyEmailBox(
const QString &email, const QString &email,
int codeLength, int codeLength,
Fn<void(QString code)> submit, Fn<void(QString code)> submit,
rpl::producer<QString> error); Fn<void()> resend,
rpl::producer<QString> error,
rpl::producer<QString> resent);
} // namespace Passport } // namespace Passport

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/wrap/padding_wrap.h" #include "ui/wrap/padding_wrap.h"
#include "ui/special_buttons.h" #include "ui/special_buttons.h"
#include "boxes/passcode_box.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_icon.h"
#include "styles/style_passport.h" #include "styles/style_passport.h"
@ -226,29 +227,49 @@ void PanelNoPassword::refreshBottom() {
_inner, _inner,
(pattern.isEmpty() (pattern.isEmpty()
? lang(lng_passport_about_password) ? lang(lng_passport_about_password)
: lng_passport_link_sent(lt_email, pattern)), : lng_passport_code_sent(lt_email, pattern)),
Ui::FlatLabel::InitType::Simple, Ui::FlatLabel::InitType::Simple,
st::passportPasswordSetupLabel)), st::passportPasswordSetupLabel)),
st::passportFormAbout2Padding)->entity()); st::passportFormAbout2Padding)->entity());
const auto button = _inner->add(
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
_inner,
object_ptr<Ui::RoundButton>(
_inner,
langFactory(pattern.isEmpty()
? lng_passport_password_create
: lng_cancel),
st::defaultBoxButton)));
if (pattern.isEmpty()) { if (pattern.isEmpty()) {
const auto button = _inner->add(
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
_inner,
object_ptr<Ui::RoundButton>(
_inner,
langFactory(lng_passport_password_create),
st::defaultBoxButton)));
button->entity()->addClickHandler([=] { button->entity()->addClickHandler([=] {
_controller->setupPassword(); _controller->setupPassword();
}); });
} else { } else {
button->entity()->addClickHandler([=] { const auto container = _inner->add(
object_ptr<Ui::FixedHeightWidget>(
_inner,
st::defaultBoxButton.height));
const auto cancel = Ui::CreateChild<Ui::RoundButton>(
container,
langFactory(lng_cancel),
st::defaultBoxButton);
cancel->addClickHandler([=] {
_controller->cancelPasswordSubmit(); _controller->cancelPasswordSubmit();
}); });
const auto validate = Ui::CreateChild<Ui::RoundButton>(
container,
langFactory(lng_passport_email_validate),
st::defaultBoxButton);
validate->addClickHandler([=] {
_controller->validateRecoveryEmail();
});
container->widthValue(
) | rpl::start_with_next([=](int width) {
const auto both = cancel->width()
+ validate->width()
+ st::boxLittleSkip;
cancel->moveToLeft((width - both) / 2, 0, width);
validate->moveToRight((width - both) / 2, 0, width);
}, container->lifetime());
} }
_button.reset(button);
} }
} // namespace Passport } // namespace Passport

View File

@ -67,7 +67,6 @@ private:
not_null<Ui::VerticalLayout*> _inner; not_null<Ui::VerticalLayout*> _inner;
base::unique_qptr<Ui::RpWidget> _about; base::unique_qptr<Ui::RpWidget> _about;
base::unique_qptr<Ui::RpWidget> _button;
}; };

View File

@ -17,6 +17,10 @@ settingsSectionButton: InfoProfileButton(infoProfileButton) {
settingsButton: InfoProfileButton(settingsSectionButton) { settingsButton: InfoProfileButton(settingsSectionButton) {
padding: margins(22px, 10px, 22px, 8px); padding: margins(22px, 10px, 22px, 8px);
} }
settingsAttentionButton: InfoProfileButton(settingsButton) {
textFg: attentionButtonFg;
textFgOver: attentionButtonFgOver;
}
settingsSectionSkip: 9px; settingsSectionSkip: 9px;
settingsSectionIconLeft: 22px; settingsSectionIconLeft: 22px;
settingsSeparatorPadding: margins(22px, infoProfileSkip, 0px, infoProfileSkip); settingsSeparatorPadding: margins(22px, infoProfileSkip, 0px, infoProfileSkip);

View File

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/self_destruction_box.h" #include "boxes/self_destruction_box.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "auth_session.h" #include "auth_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "styles/style_boxes.h"
namespace Settings { namespace Settings {
namespace { namespace {
@ -242,12 +244,18 @@ void EditCloudPassword() {
current->notEmptyPassport, current->notEmptyPassport,
current->hint, current->hint,
current->newSecureSecret)); current->newSecureSecret));
rpl::merge( rpl::merge(
box->newPasswordSet() | rpl::map([] { return rpl::empty_value(); }), box->newPasswordSet() | rpl::map([] { return rpl::empty_value(); }),
box->passwordReloadNeeded() box->passwordReloadNeeded()
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
Auth().api().reloadPasswordState(); Auth().api().reloadPasswordState();
}, box->lifetime()); }, box->lifetime());
box->clearUnconfirmedPassword(
) | rpl::start_with_next([=] {
Auth().api().clearUnconfirmedPassword();
}, box->lifetime());
} }
void RemoveCloudPassword() { void RemoveCloudPassword() {
@ -266,6 +274,7 @@ void RemoveCloudPassword() {
current->hint, current->hint,
current->newSecureSecret, current->newSecureSecret,
true)); true));
rpl::merge( rpl::merge(
box->newPasswordSet( box->newPasswordSet(
) | rpl::map([] { return rpl::empty_value(); }), ) | rpl::map([] { return rpl::empty_value(); }),
@ -273,15 +282,21 @@ void RemoveCloudPassword() {
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
Auth().api().reloadPasswordState(); Auth().api().reloadPasswordState();
}, box->lifetime()); }, box->lifetime());
box->clearUnconfirmedPassword(
) | rpl::start_with_next([=] {
Auth().api().clearUnconfirmedPassword();
}, box->lifetime());
} }
void SetupCloudPassword(not_null<Ui::VerticalLayout*> container) { void SetupCloudPassword(not_null<Ui::VerticalLayout*> container) {
using namespace rpl::mappers;
using State = Core::CloudPasswordState;
AddDivider(container); AddDivider(container);
AddSkip(container); AddSkip(container);
AddSubsectionTitle(container, lng_settings_password_title); AddSubsectionTitle(container, lng_settings_password_title);
using State = Core::CloudPasswordState;
auto has = rpl::single( auto has = rpl::single(
false false
) | rpl::then(Auth().api().passwordState( ) | rpl::then(Auth().api().passwordState(
@ -301,7 +316,7 @@ void SetupCloudPassword(not_null<Ui::VerticalLayout*> container) {
) | rpl::filter([](const QString &pattern) { ) | rpl::filter([](const QString &pattern) {
return !pattern.isEmpty(); return !pattern.isEmpty();
}) | rpl::map([](const QString &pattern) { }) | rpl::map([](const QString &pattern) {
return lng_cloud_password_waiting(lt_email, pattern); return lng_cloud_password_waiting_code(lt_email, pattern);
})); }));
auto unconfirmed = rpl::single( auto unconfirmed = rpl::single(
true true
@ -346,7 +361,7 @@ void SetupCloudPassword(not_null<Ui::VerticalLayout*> container) {
container, container,
std::move(text), std::move(text),
st::settingsButton))); st::settingsButton)));
change->toggleOn(std::move( change->toggleOn(rpl::duplicate(
unconfirmed unconfirmed
) | rpl::map([](bool unconfirmed) { ) | rpl::map([](bool unconfirmed) {
return !unconfirmed; return !unconfirmed;
@ -357,6 +372,40 @@ void SetupCloudPassword(not_null<Ui::VerticalLayout*> container) {
} }
}); });
const auto confirm = container->add(
object_ptr<Ui::SlideWrap<Button>>(
container,
object_ptr<Button>(
container,
Lang::Viewer(lng_cloud_password_confirm),
st::settingsButton)));
confirm->toggleOn(rpl::duplicate(
unconfirmed
))->setDuration(0);
confirm->entity()->addClickHandler([] {
const auto state = Auth().api().passwordStateCurrent();
auto validation = ConfirmRecoveryEmail(state->unconfirmedPattern);
std::move(
validation.reloadRequests
) | rpl::start_with_next([] {
Auth().api().reloadPasswordState();
}, validation.box->lifetime());
std::move(
validation.cancelRequests
) | rpl::start_with_next([] {
Auth().api().clearUnconfirmedPassword();
}, validation.box->lifetime());
Ui::show(std::move(validation.box));
});
const auto remove = [] {
if (CheckEditCloudPassword()) {
RemoveCloudPassword();
}
};
const auto disable = container->add( const auto disable = container->add(
object_ptr<Ui::SlideWrap<Button>>( object_ptr<Ui::SlideWrap<Button>>(
container, container,
@ -364,12 +413,24 @@ void SetupCloudPassword(not_null<Ui::VerticalLayout*> container) {
container, container,
Lang::Viewer(lng_settings_password_disable), Lang::Viewer(lng_settings_password_disable),
st::settingsButton))); st::settingsButton)));
disable->toggleOn(base::duplicate(has)); disable->toggleOn(rpl::combine(
disable->entity()->addClickHandler([] { rpl::duplicate(has),
if (CheckEditCloudPassword()) { rpl::duplicate(unconfirmed),
RemoveCloudPassword(); _1 && !_2));
} disable->entity()->addClickHandler(remove);
});
const auto abort = container->add(
object_ptr<Ui::SlideWrap<Button>>(
container,
object_ptr<Button>(
container,
Lang::Viewer(lng_settings_password_abort),
st::settingsAttentionButton)));
abort->toggleOn(rpl::combine(
rpl::duplicate(has),
rpl::duplicate(unconfirmed),
_1 && _2));
abort->entity()->addClickHandler(remove);
const auto reloadOnActivation = [=](Qt::ApplicationState state) { const auto reloadOnActivation = [=](Qt::ApplicationState state) {
if (label->toggled() && state == Qt::ApplicationActive) { if (label->toggled() && state == Qt::ApplicationActive) {