1456 lines
43 KiB
C++
1456 lines
43 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "boxes/passcode_box.h"
|
|
|
|
#include "base/bytes.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "ui/boxes/confirm_box.h"
|
|
#include "base/unixtime.h"
|
|
#include "mainwindow.h"
|
|
#include "apiwrap.h"
|
|
#include "api/api_cloud_password.h"
|
|
#include "main/main_session.h"
|
|
#include "main/main_domain.h"
|
|
#include "core/application.h"
|
|
#include "storage/storage_domain.h"
|
|
#include "ui/layers/generic_box.h"
|
|
#include "ui/text/text_utilities.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/fields/input_field.h"
|
|
#include "ui/widgets/fields/password_input.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/wrap/fade_wrap.h"
|
|
#include "ui/painter.h"
|
|
#include "passport/passport_encryption.h"
|
|
#include "passport/passport_panel_edit_contact.h"
|
|
#include "settings/settings_privacy_security.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_passport.h"
|
|
#include "styles/style_boxes.h"
|
|
#include "base/qt/qt_common_adapters.h"
|
|
|
|
namespace {
|
|
|
|
enum class PasswordErrorType {
|
|
None,
|
|
NoPassword,
|
|
Later,
|
|
};
|
|
|
|
void SetCloudPassword(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Main::Session*> session) {
|
|
session->api().cloudPassword().state(
|
|
) | rpl::start_with_next([=] {
|
|
using namespace Settings;
|
|
const auto weak = Ui::MakeWeak(box);
|
|
if (CheckEditCloudPassword(session)) {
|
|
box->getDelegate()->show(
|
|
EditCloudPasswordBox(session));
|
|
} else {
|
|
box->getDelegate()->show(CloudPasswordAppOutdatedBox());
|
|
}
|
|
if (weak) {
|
|
weak->closeBox();
|
|
}
|
|
}, box->lifetime());
|
|
}
|
|
|
|
void TransferPasswordError(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Main::Session*> session,
|
|
TextWithEntities &&about,
|
|
PasswordErrorType error) {
|
|
box->setTitle(tr::lng_rights_transfer_check());
|
|
box->setWidth(st::transferCheckWidth);
|
|
|
|
auto text = std::move(about).append('\n').append('\n').append(
|
|
tr::lng_rights_transfer_check_password(
|
|
tr::now,
|
|
Ui::Text::RichLangValue)
|
|
).append('\n').append('\n').append(
|
|
tr::lng_rights_transfer_check_session(
|
|
tr::now,
|
|
Ui::Text::RichLangValue)
|
|
);
|
|
if (error == PasswordErrorType::Later) {
|
|
text.append('\n').append('\n').append(
|
|
tr::lng_rights_transfer_check_later(
|
|
tr::now,
|
|
Ui::Text::RichLangValue));
|
|
}
|
|
box->addRow(object_ptr<Ui::FlatLabel>(
|
|
box,
|
|
rpl::single(text),
|
|
st::boxLabel));
|
|
if (error == PasswordErrorType::Later) {
|
|
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
|
} else {
|
|
box->addButton(tr::lng_rights_transfer_set_password(), [=] {
|
|
SetCloudPassword(box, session);
|
|
});
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
}
|
|
}
|
|
|
|
void StartPendingReset(
|
|
not_null<Main::Session*> session,
|
|
not_null<Ui::BoxContent*> context,
|
|
Fn<void()> close) {
|
|
const auto weak = Ui::MakeWeak(context.get());
|
|
auto lifetime = std::make_shared<rpl::lifetime>();
|
|
|
|
auto finish = [=](const QString &message) mutable {
|
|
if (const auto strong = weak.data()) {
|
|
if (!message.isEmpty()) {
|
|
strong->getDelegate()->show(Ui::MakeInformBox(message));
|
|
}
|
|
strong->closeBox();
|
|
}
|
|
close();
|
|
if (lifetime) {
|
|
base::take(lifetime)->destroy();
|
|
}
|
|
};
|
|
|
|
session->api().cloudPassword().resetPassword(
|
|
) | rpl::start_with_next_error_done([=](
|
|
Api::CloudPassword::ResetRetryDate retryDate) {
|
|
constexpr auto kMinute = 60;
|
|
constexpr auto kHour = 3600;
|
|
constexpr auto kDay = 86400;
|
|
const auto left = std::max(
|
|
retryDate - base::unixtime::now(),
|
|
kMinute);
|
|
const auto days = (left / kDay);
|
|
const auto hours = (left / kHour);
|
|
const auto minutes = (left / kMinute);
|
|
const auto duration = days
|
|
? tr::lng_days(tr::now, lt_count, days)
|
|
: hours
|
|
? tr::lng_hours(tr::now, lt_count, hours)
|
|
: tr::lng_minutes(tr::now, lt_count, minutes);
|
|
if (const auto strong = weak.data()) {
|
|
strong->getDelegate()->show(Ui::MakeInformBox(
|
|
tr::lng_cloud_password_reset_later(
|
|
tr::now,
|
|
lt_duration,
|
|
duration)));
|
|
}
|
|
}, [=](const QString &error) mutable {
|
|
finish("Error: " + error);
|
|
}, [=]() mutable {
|
|
finish({});
|
|
}, *lifetime);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
PasscodeBox::CloudFields PasscodeBox::CloudFields::From(
|
|
const Core::CloudPasswordState ¤t) {
|
|
auto result = CloudFields();
|
|
result.hasPassword = current.hasPassword;
|
|
result.mtp.curRequest = current.mtp.request;
|
|
result.mtp.newAlgo = current.mtp.newPassword;
|
|
result.mtp.newSecureSecretAlgo = current.mtp.newSecureSecret;
|
|
result.hasRecovery = current.hasRecovery;
|
|
result.notEmptyPassport = current.notEmptyPassport;
|
|
result.hint = current.hint;
|
|
result.pendingResetDate = current.pendingResetDate;
|
|
return result;
|
|
}
|
|
|
|
PasscodeBox::PasscodeBox(
|
|
QWidget*,
|
|
not_null<Main::Session*> session,
|
|
bool turningOff)
|
|
: _session(session)
|
|
, _api(&_session->mtp())
|
|
, _turningOff(turningOff)
|
|
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
|
, _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old())
|
|
, _newPasscode(
|
|
this,
|
|
st::defaultInputField,
|
|
session->domain().local().hasLocalPasscode()
|
|
? tr::lng_passcode_enter_new()
|
|
: tr::lng_passcode_enter_first())
|
|
, _reenterPasscode(this, st::defaultInputField, tr::lng_passcode_confirm_new())
|
|
, _passwordHint(this, st::defaultInputField, tr::lng_cloud_password_hint())
|
|
, _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
|
|
, _recover(this, tr::lng_signin_recover(tr::now)) {
|
|
}
|
|
|
|
PasscodeBox::PasscodeBox(
|
|
QWidget*,
|
|
not_null<MTP::Instance*> mtp,
|
|
Main::Session *session,
|
|
const CloudFields &fields)
|
|
: _session(session)
|
|
, _api(mtp)
|
|
, _turningOff(fields.turningOff)
|
|
, _cloudPwd(true)
|
|
, _cloudFields(fields)
|
|
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
|
, _oldPasscode(this, st::defaultInputField, tr::lng_cloud_password_enter_old())
|
|
, _newPasscode(
|
|
this,
|
|
st::defaultInputField,
|
|
(fields.hasPassword
|
|
? tr::lng_cloud_password_enter_new()
|
|
: tr::lng_cloud_password_enter_first()))
|
|
, _reenterPasscode(this, st::defaultInputField, tr::lng_cloud_password_confirm_new())
|
|
, _passwordHint(
|
|
this,
|
|
st::defaultInputField,
|
|
(fields.hasPassword
|
|
? tr::lng_cloud_password_change_hint()
|
|
: tr::lng_cloud_password_hint()))
|
|
, _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
|
|
, _recover(this, tr::lng_signin_recover(tr::now))
|
|
, _showRecoverLink(_cloudFields.hasRecovery || !_cloudFields.pendingResetDate) {
|
|
Expects(session != nullptr || !fields.fromRecoveryCode.isEmpty());
|
|
Expects(!_turningOff || _cloudFields.hasPassword);
|
|
|
|
if (!_cloudFields.hint.isEmpty()) {
|
|
_hintText.setText(
|
|
st::passcodeTextStyle,
|
|
tr::lng_signin_hint(tr::now, lt_password_hint, _cloudFields.hint));
|
|
}
|
|
}
|
|
|
|
PasscodeBox::PasscodeBox(
|
|
QWidget*,
|
|
not_null<Main::Session*> session,
|
|
const CloudFields &fields)
|
|
: PasscodeBox(nullptr, &session->mtp(), session, fields) {
|
|
}
|
|
|
|
rpl::producer<QByteArray> PasscodeBox::newPasswordSet() const {
|
|
return _newPasswordSet.events();
|
|
}
|
|
|
|
rpl::producer<> PasscodeBox::passwordReloadNeeded() const {
|
|
return _passwordReloadNeeded.events();
|
|
}
|
|
|
|
rpl::producer<> PasscodeBox::clearUnconfirmedPassword() const {
|
|
return _clearUnconfirmedPassword.events();
|
|
}
|
|
|
|
rpl::producer<MTPauth_Authorization> PasscodeBox::newAuthorization() const {
|
|
return _newAuthorization.events();
|
|
}
|
|
|
|
bool PasscodeBox::currentlyHave() const {
|
|
return _cloudPwd
|
|
? _cloudFields.hasPassword
|
|
: _session->domain().local().hasLocalPasscode();
|
|
}
|
|
|
|
bool PasscodeBox::onlyCheckCurrent() const {
|
|
return _turningOff || _cloudFields.customCheckCallback;
|
|
}
|
|
|
|
void PasscodeBox::prepare() {
|
|
addButton(
|
|
(_cloudFields.customSubmitButton
|
|
? std::move(_cloudFields.customSubmitButton)
|
|
: _turningOff
|
|
? tr::lng_passcode_remove_button()
|
|
: tr::lng_settings_save()),
|
|
[=] { save(); });
|
|
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
|
|
|
_about.setText(
|
|
st::passcodeTextStyle,
|
|
(_cloudFields.customDescription
|
|
? *_cloudFields.customDescription
|
|
: _cloudPwd
|
|
? tr::lng_cloud_password_about(tr::now)
|
|
: tr::lng_passcode_about(tr::now)));
|
|
_aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5);
|
|
const auto onlyCheck = onlyCheckCurrent();
|
|
if (onlyCheck) {
|
|
_oldPasscode->show();
|
|
setTitle(_cloudFields.customTitle
|
|
? std::move(_cloudFields.customTitle)
|
|
: _cloudPwd
|
|
? tr::lng_cloud_password_remove()
|
|
: tr::lng_passcode_remove());
|
|
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
|
|
} else {
|
|
if (currentlyHave()) {
|
|
_oldPasscode->show();
|
|
setTitle(_cloudPwd
|
|
? tr::lng_cloud_password_change()
|
|
: tr::lng_passcode_change());
|
|
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
|
|
} else {
|
|
_oldPasscode->hide();
|
|
setTitle(_cloudPwd
|
|
? (_cloudFields.fromRecoveryCode.isEmpty()
|
|
? tr::lng_cloud_password_create()
|
|
: tr::lng_cloud_password_change())
|
|
: tr::lng_passcode_create());
|
|
setDimensions(st::boxWidth, st::passcodePadding.top() + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + ((_cloudPwd && _cloudFields.fromRecoveryCode.isEmpty()) ? (st::passcodeLittleSkip + _recoverEmail->height() + st::passcodeSkip) : st::passcodePadding.bottom()));
|
|
}
|
|
}
|
|
|
|
connect(_oldPasscode, &Ui::MaskedInputField::changed, [=] { oldChanged(); });
|
|
connect(_newPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); });
|
|
connect(_reenterPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); });
|
|
_passwordHint->changes(
|
|
) | rpl::start_with_next([=] {
|
|
newChanged();
|
|
}, _passwordHint->lifetime());
|
|
_recoverEmail->changes(
|
|
) | rpl::start_with_next([=] {
|
|
if (!_emailError.isEmpty()) {
|
|
_emailError = QString();
|
|
update();
|
|
}
|
|
}, _recoverEmail->lifetime());
|
|
|
|
const auto fieldSubmit = [=] { submit(); };
|
|
connect(_oldPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
|
|
connect(_newPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
|
|
connect(_reenterPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
|
|
_passwordHint->submits(
|
|
) | rpl::start_with_next(fieldSubmit, _passwordHint->lifetime());
|
|
_recoverEmail->submits(
|
|
) | rpl::start_with_next(fieldSubmit, _recoverEmail->lifetime());
|
|
|
|
_recover->addClickHandler([=] { recoverByEmail(); });
|
|
|
|
const auto has = currentlyHave();
|
|
_oldPasscode->setVisible(onlyCheck || has);
|
|
_recover->setVisible((onlyCheck || has)
|
|
&& _cloudPwd
|
|
&& _showRecoverLink);
|
|
_newPasscode->setVisible(!onlyCheck);
|
|
_reenterPasscode->setVisible(!onlyCheck);
|
|
_passwordHint->setVisible(!onlyCheck && _cloudPwd);
|
|
_recoverEmail->setVisible(!onlyCheck
|
|
&& _cloudPwd
|
|
&& !has
|
|
&& _cloudFields.fromRecoveryCode.isEmpty());
|
|
}
|
|
|
|
void PasscodeBox::submit() {
|
|
const auto has = currentlyHave();
|
|
if (_oldPasscode->hasFocus()) {
|
|
if (onlyCheckCurrent()) {
|
|
save();
|
|
} else {
|
|
_newPasscode->setFocus();
|
|
}
|
|
} else if (_newPasscode->hasFocus()) {
|
|
_reenterPasscode->setFocus();
|
|
} else if (_reenterPasscode->hasFocus()) {
|
|
if (has && _oldPasscode->text().isEmpty()) {
|
|
_oldPasscode->setFocus();
|
|
_oldPasscode->showError();
|
|
} else if (_newPasscode->text().isEmpty()) {
|
|
_newPasscode->setFocus();
|
|
_newPasscode->showError();
|
|
} else if (_reenterPasscode->text().isEmpty()) {
|
|
_reenterPasscode->showError();
|
|
} else if (!_passwordHint->isHidden()) {
|
|
_passwordHint->setFocus();
|
|
} else {
|
|
save();
|
|
}
|
|
} else if (_passwordHint->hasFocus()) {
|
|
if (_recoverEmail->isHidden()) {
|
|
save();
|
|
} else {
|
|
_recoverEmail->setFocus();
|
|
}
|
|
} else if (_recoverEmail->hasFocus()) {
|
|
save();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::paintEvent(QPaintEvent *e) {
|
|
BoxContent::paintEvent(e);
|
|
|
|
Painter p(this);
|
|
|
|
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
|
|
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_showRecoverLink && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
|
|
p.setPen(st::boxTextFg);
|
|
_about.drawLeft(p, st::boxPadding.left(), abouty, w, width());
|
|
|
|
if (!_hintText.isEmpty() && _oldError.isEmpty()) {
|
|
_hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft);
|
|
}
|
|
|
|
if (!_oldError.isEmpty()) {
|
|
p.setPen(st::boxTextFgError);
|
|
p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left);
|
|
}
|
|
|
|
if (!_newError.isEmpty()) {
|
|
p.setPen(st::boxTextFgError);
|
|
p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left);
|
|
}
|
|
|
|
if (!_emailError.isEmpty()) {
|
|
p.setPen(st::boxTextFgError);
|
|
p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left);
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::resizeEvent(QResizeEvent *e) {
|
|
BoxContent::resizeEvent(e);
|
|
|
|
const auto has = currentlyHave();
|
|
int32 w = st::boxWidth - st::boxPadding.left() - st::boxPadding.right();
|
|
_oldPasscode->resize(w, _oldPasscode->height());
|
|
_oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top());
|
|
_newPasscode->resize(w, _newPasscode->height());
|
|
_newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0));
|
|
_reenterPasscode->resize(w, _reenterPasscode->height());
|
|
_reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::passcodeLittleSkip);
|
|
_passwordHint->resize(w, _passwordHint->height());
|
|
_passwordHint->moveToLeft(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height() + st::passcodeSkip);
|
|
_recoverEmail->resize(w, _passwordHint->height());
|
|
_recoverEmail->moveToLeft(st::boxPadding.left(), _passwordHint->y() + _passwordHint->height() + st::passcodeLittleSkip + _aboutHeight + st::passcodeLittleSkip);
|
|
|
|
if (!_recover->isHidden()) {
|
|
_recover->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + (_hintText.isEmpty() ? ((st::passcodeTextLine - _recover->height()) / 2) : st::passcodeTextLine));
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::setInnerFocus() {
|
|
if (_skipEmailWarning && !_recoverEmail->isHidden()) {
|
|
_recoverEmail->setFocusFast();
|
|
} else if (_oldPasscode->isHidden()) {
|
|
_newPasscode->setFocusFast();
|
|
} else {
|
|
_oldPasscode->setFocusFast();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::recoverPasswordDone(
|
|
const QByteArray &newPasswordBytes,
|
|
const MTPauth_Authorization &result) {
|
|
if (_replacedBy) {
|
|
_replacedBy->closeBox();
|
|
}
|
|
_setRequest = 0;
|
|
const auto weak = Ui::MakeWeak(this);
|
|
_newAuthorization.fire_copy(result);
|
|
if (weak) {
|
|
_newPasswordSet.fire_copy(newPasswordBytes);
|
|
if (weak) {
|
|
getDelegate()->show(Ui::MakeInformBox(
|
|
tr::lng_cloud_password_updated()));
|
|
if (weak) {
|
|
closeBox();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::setPasswordDone(const QByteArray &newPasswordBytes) {
|
|
if (_replacedBy) {
|
|
_replacedBy->closeBox();
|
|
}
|
|
_setRequest = 0;
|
|
const auto weak = Ui::MakeWeak(this);
|
|
_newPasswordSet.fire_copy(newPasswordBytes);
|
|
if (weak) {
|
|
auto text = _reenterPasscode->isHidden()
|
|
? tr::lng_cloud_password_removed()
|
|
: _oldPasscode->isHidden()
|
|
? tr::lng_cloud_password_was_set()
|
|
: tr::lng_cloud_password_updated();
|
|
getDelegate()->show(Ui::MakeInformBox(std::move(text)));
|
|
if (weak) {
|
|
closeBox();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::closeReplacedBy() {
|
|
if (isHidden()) {
|
|
if (_replacedBy && !_replacedBy->isHidden()) {
|
|
_replacedBy->closeBox();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::setPasswordFail(const QString &type) {
|
|
if (MTP::IsFloodError(type)) {
|
|
closeReplacedBy();
|
|
_setRequest = 0;
|
|
|
|
_oldPasscode->selectAll();
|
|
_oldPasscode->setFocus();
|
|
_oldPasscode->showError();
|
|
_oldError = tr::lng_flood_error(tr::now);
|
|
if (_showRecoverLink && _hintText.isEmpty()) {
|
|
_recover->hide();
|
|
}
|
|
update();
|
|
return;
|
|
}
|
|
|
|
closeReplacedBy();
|
|
_setRequest = 0;
|
|
if (type == u"PASSWORD_HASH_INVALID"_q
|
|
|| type == u"SRP_PASSWORD_CHANGED"_q) {
|
|
if (_oldPasscode->isHidden()) {
|
|
_passwordReloadNeeded.fire({});
|
|
closeBox();
|
|
} else {
|
|
badOldPasscode();
|
|
}
|
|
} else if (type == u"SRP_ID_INVALID"_q) {
|
|
handleSrpIdInvalid();
|
|
//} else if (type == u"NEW_PASSWORD_BAD"_q) {
|
|
//} else if (type == u"NEW_SALT_INVALID"_q) {
|
|
} else if (type == u"EMAIL_INVALID"_q) {
|
|
_emailError = tr::lng_cloud_password_bad_email(tr::now);
|
|
_recoverEmail->setFocus();
|
|
_recoverEmail->showError();
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::setPasswordFail(
|
|
const QByteArray &newPasswordBytes,
|
|
const QString &email,
|
|
const MTP::Error &error) {
|
|
const auto prefix = u"EMAIL_UNCONFIRMED_"_q;
|
|
if (error.type().startsWith(prefix)) {
|
|
const auto codeLength = base::StringViewMid(error.type(), prefix.size()).toInt();
|
|
|
|
closeReplacedBy();
|
|
_setRequest = 0;
|
|
|
|
validateEmail(email, codeLength, newPasswordBytes);
|
|
} else {
|
|
setPasswordFail(error.type());
|
|
}
|
|
}
|
|
|
|
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 = crl::guard(this, [=](QString code) {
|
|
if (_setRequest) {
|
|
return;
|
|
}
|
|
_setRequest = _api.request(MTPaccount_ConfirmPasswordEmail(
|
|
MTP_string(code)
|
|
)).done([=] {
|
|
*set = true;
|
|
setPasswordDone(newPasswordBytes);
|
|
}).fail([=](const MTP::Error &error) {
|
|
_setRequest = 0;
|
|
if (MTP::IsFloodError(error)) {
|
|
errors->fire(tr::lng_flood_error(tr::now));
|
|
} else if (error.type() == u"CODE_INVALID"_q) {
|
|
errors->fire(tr::lng_signin_wrong_code(tr::now));
|
|
} else if (error.type() == u"EMAIL_HASH_EXPIRED"_q) {
|
|
const auto weak = Ui::MakeWeak(this);
|
|
_clearUnconfirmedPassword.fire({});
|
|
if (weak) {
|
|
auto box = Ui::MakeInformBox({
|
|
Lang::Hard::EmailConfirmationExpired()
|
|
});
|
|
weak->getDelegate()->show(
|
|
std::move(box),
|
|
Ui::LayerOption::CloseOther);
|
|
}
|
|
} else {
|
|
errors->fire(Lang::Hard::ServerError());
|
|
}
|
|
}).handleFloodErrors().send();
|
|
});
|
|
const auto resend = crl::guard(this, [=] {
|
|
if (_setRequest) {
|
|
return;
|
|
}
|
|
_setRequest = _api.request(MTPaccount_ResendPasswordEmail(
|
|
)).done([=] {
|
|
_setRequest = 0;
|
|
resent->fire(tr::lng_cloud_password_resent(tr::now));
|
|
}).fail([=] {
|
|
_setRequest = 0;
|
|
errors->fire(Lang::Hard::ServerError());
|
|
}).send();
|
|
});
|
|
const auto box = _replacedBy = getDelegate()->show(
|
|
Passport::VerifyEmailBox(
|
|
email,
|
|
codeLength,
|
|
submit,
|
|
resend,
|
|
errors->events(),
|
|
resent->events()));
|
|
|
|
box->setCloseByOutsideClick(false);
|
|
box->setCloseByEscape(false);
|
|
box->boxClosing(
|
|
) | rpl::filter([=] {
|
|
return !*set;
|
|
}) | start_with_next([=, weak = Ui::MakeWeak(this)] {
|
|
if (weak) {
|
|
weak->_clearUnconfirmedPassword.fire({});
|
|
}
|
|
if (weak) {
|
|
weak->closeBox();
|
|
}
|
|
}, box->lifetime());
|
|
}
|
|
|
|
void PasscodeBox::handleSrpIdInvalid() {
|
|
const auto now = crl::now();
|
|
if (_lastSrpIdInvalidTime > 0
|
|
&& now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) {
|
|
_cloudFields.mtp.curRequest.id = 0;
|
|
_oldError = Lang::Hard::ServerError();
|
|
update();
|
|
} else {
|
|
_lastSrpIdInvalidTime = now;
|
|
requestPasswordData();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::save(bool force) {
|
|
if (_setRequest) return;
|
|
|
|
QString old = _oldPasscode->text(), pwd = _newPasscode->text(), conf = _reenterPasscode->text();
|
|
const auto has = currentlyHave();
|
|
if (!_cloudPwd && (_turningOff || has)) {
|
|
if (!passcodeCanTry()) {
|
|
_oldError = tr::lng_flood_error(tr::now);
|
|
_oldPasscode->setFocus();
|
|
_oldPasscode->showError();
|
|
update();
|
|
return;
|
|
}
|
|
|
|
if (_session->domain().local().checkPasscode(old.toUtf8())) {
|
|
cSetPasscodeBadTries(0);
|
|
if (_turningOff) pwd = conf = QString();
|
|
} else {
|
|
cSetPasscodeBadTries(cPasscodeBadTries() + 1);
|
|
cSetPasscodeLastTry(crl::now());
|
|
badOldPasscode();
|
|
return;
|
|
}
|
|
}
|
|
const auto onlyCheck = onlyCheckCurrent();
|
|
if (!onlyCheck && pwd.isEmpty()) {
|
|
_newPasscode->setFocus();
|
|
_newPasscode->showError();
|
|
closeReplacedBy();
|
|
return;
|
|
}
|
|
if (!onlyCheck && pwd != conf) {
|
|
_reenterPasscode->selectAll();
|
|
_reenterPasscode->setFocus();
|
|
_reenterPasscode->showError();
|
|
if (!conf.isEmpty()) {
|
|
_newError = _cloudPwd
|
|
? tr::lng_cloud_password_differ(tr::now)
|
|
: tr::lng_passcode_differ(tr::now);
|
|
update();
|
|
}
|
|
closeReplacedBy();
|
|
} else if (!onlyCheck && has && old == pwd) {
|
|
_newPasscode->setFocus();
|
|
_newPasscode->showError();
|
|
_newError = _cloudPwd
|
|
? tr::lng_cloud_password_is_same(tr::now)
|
|
: tr::lng_passcode_is_same(tr::now);
|
|
update();
|
|
closeReplacedBy();
|
|
} else if (_cloudPwd) {
|
|
QString hint = _passwordHint->getLastText(), email = _recoverEmail->getLastText().trimmed();
|
|
if (!onlyCheck
|
|
&& !_passwordHint->isHidden()
|
|
&& !_newPasscode->isHidden()
|
|
&& pwd == hint) {
|
|
_newPasscode->setFocus();
|
|
_newPasscode->showError();
|
|
_newError = tr::lng_cloud_password_bad(tr::now);
|
|
update();
|
|
closeReplacedBy();
|
|
return;
|
|
}
|
|
if (!onlyCheck && !_recoverEmail->isHidden() && email.isEmpty() && !force) {
|
|
_skipEmailWarning = true;
|
|
_replacedBy = getDelegate()->show(Ui::MakeConfirmBox({
|
|
.text = { tr::lng_cloud_password_about_recover() },
|
|
.confirmed = crl::guard(this, [this] { save(true); }),
|
|
.confirmText = tr::lng_cloud_password_skip_email(),
|
|
.confirmStyle = &st::attentionBoxButton,
|
|
}));
|
|
} else if (onlyCheck) {
|
|
submitOnlyCheckCloudPassword(old);
|
|
} else if (_oldPasscode->isHidden()) {
|
|
setNewCloudPassword(pwd);
|
|
} else {
|
|
changeCloudPassword(old, pwd);
|
|
}
|
|
} else {
|
|
closeReplacedBy();
|
|
const auto weak = Ui::MakeWeak(this);
|
|
cSetPasscodeBadTries(0);
|
|
_session->domain().local().setPasscode(pwd.toUtf8());
|
|
Core::App().localPasscodeChanged();
|
|
if (weak) {
|
|
closeBox();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::submitOnlyCheckCloudPassword(const QString &oldPassword) {
|
|
Expects(!_oldPasscode->isHidden());
|
|
|
|
const auto send = [=] {
|
|
sendOnlyCheckCloudPassword(oldPassword);
|
|
};
|
|
if (_cloudFields.turningOff && _cloudFields.notEmptyPassport) {
|
|
Assert(!_cloudFields.customCheckCallback);
|
|
|
|
getDelegate()->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_cloud_password_passport_losing(),
|
|
.confirmed = [=](Fn<void()> &&close) { send(); close(); },
|
|
.confirmText = tr::lng_continue(),
|
|
}));
|
|
} else {
|
|
send();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::sendOnlyCheckCloudPassword(const QString &oldPassword) {
|
|
checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) {
|
|
if (const auto onstack = _cloudFields.customCheckCallback) {
|
|
onstack(check);
|
|
} else {
|
|
Assert(_cloudFields.turningOff);
|
|
sendClearCloudPassword(check);
|
|
}
|
|
});
|
|
}
|
|
|
|
void PasscodeBox::checkPassword(
|
|
const QString &oldPassword,
|
|
CheckPasswordCallback callback) {
|
|
const auto passwordUtf = oldPassword.toUtf8();
|
|
_checkPasswordHash = Core::ComputeCloudPasswordHash(
|
|
_cloudFields.mtp.curRequest.algo,
|
|
bytes::make_span(passwordUtf));
|
|
checkPasswordHash(std::move(callback));
|
|
}
|
|
|
|
void PasscodeBox::checkPasswordHash(CheckPasswordCallback callback) {
|
|
_checkPasswordCallback = std::move(callback);
|
|
if (_cloudFields.mtp.curRequest.id) {
|
|
passwordChecked();
|
|
} else {
|
|
requestPasswordData();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::passwordChecked() {
|
|
if (!_cloudFields.mtp.curRequest || !_cloudFields.mtp.curRequest.id || !_checkPasswordCallback) {
|
|
return serverError();
|
|
}
|
|
const auto check = Core::ComputeCloudPasswordCheck(
|
|
_cloudFields.mtp.curRequest,
|
|
_checkPasswordHash);
|
|
if (!check) {
|
|
return serverError();
|
|
}
|
|
_cloudFields.mtp.curRequest.id = 0;
|
|
_checkPasswordCallback(check);
|
|
}
|
|
|
|
void PasscodeBox::requestPasswordData() {
|
|
if (!_checkPasswordCallback) {
|
|
return serverError();
|
|
}
|
|
|
|
_api.request(base::take(_setRequest)).cancel();
|
|
_setRequest = _api.request(
|
|
MTPaccount_GetPassword()
|
|
).done([=](const MTPaccount_Password &result) {
|
|
_setRequest = 0;
|
|
result.match([&](const MTPDaccount_password &data) {
|
|
_cloudFields.mtp.curRequest = Core::ParseCloudPasswordCheckRequest(data);
|
|
passwordChecked();
|
|
});
|
|
}).send();
|
|
}
|
|
|
|
void PasscodeBox::serverError() {
|
|
getDelegate()->show(Ui::MakeInformBox(Lang::Hard::ServerError()));
|
|
closeBox();
|
|
}
|
|
|
|
bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) {
|
|
return handleCustomCheckError(error.type());
|
|
}
|
|
|
|
bool PasscodeBox::handleCustomCheckError(const QString &type) {
|
|
if (MTP::IsFloodError(type)
|
|
|| type == u"PASSWORD_HASH_INVALID"_q
|
|
|| type == u"SRP_PASSWORD_CHANGED"_q
|
|
|| type == u"SRP_ID_INVALID"_q) {
|
|
setPasswordFail(type);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PasscodeBox::sendClearCloudPassword(
|
|
const Core::CloudPasswordResult &check) {
|
|
const auto hint = QString();
|
|
const auto email = QString();
|
|
const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
|
|
| MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
|
|
| MTPDaccount_passwordInputSettings::Flag::f_hint
|
|
| MTPDaccount_passwordInputSettings::Flag::f_email;
|
|
_setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
|
|
check.result,
|
|
MTP_account_passwordInputSettings(
|
|
MTP_flags(flags),
|
|
Core::PrepareCloudPasswordAlgo(_cloudFields.mtp.newAlgo),
|
|
MTP_bytes(), // new_password_hash
|
|
MTP_string(hint),
|
|
MTP_string(email),
|
|
MTPSecureSecretSettings())
|
|
)).done([=] {
|
|
setPasswordDone({});
|
|
}).fail([=](const MTP::Error &error) mutable {
|
|
setPasswordFail({}, QString(), error);
|
|
}).handleFloodErrors().send();
|
|
}
|
|
|
|
void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
|
|
const auto newPasswordBytes = newPassword.toUtf8();
|
|
const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
|
|
_cloudFields.mtp.newAlgo,
|
|
bytes::make_span(newPasswordBytes));
|
|
if (newPasswordHash.modpow.empty()) {
|
|
return serverError();
|
|
}
|
|
const auto hint = _passwordHint->getLastText();
|
|
const auto email = _recoverEmail->getLastText().trimmed();
|
|
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
|
const auto flags = Flag::f_new_algo
|
|
| Flag::f_new_password_hash
|
|
| Flag::f_hint
|
|
| (_cloudFields.fromRecoveryCode.isEmpty() ? Flag::f_email : Flag(0));
|
|
_checkPasswordCallback = nullptr;
|
|
|
|
const auto settings = MTP_account_passwordInputSettings(
|
|
MTP_flags(flags),
|
|
Core::PrepareCloudPasswordAlgo(_cloudFields.mtp.newAlgo),
|
|
MTP_bytes(newPasswordHash.modpow),
|
|
MTP_string(hint),
|
|
MTP_string(email),
|
|
MTPSecureSecretSettings());
|
|
if (_cloudFields.fromRecoveryCode.isEmpty()) {
|
|
_setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
|
|
MTP_inputCheckPasswordEmpty(),
|
|
settings
|
|
)).done([=] {
|
|
setPasswordDone(newPasswordBytes);
|
|
}).fail([=](const MTP::Error &error) {
|
|
setPasswordFail(newPasswordBytes, email, error);
|
|
}).handleFloodErrors().send();
|
|
} else {
|
|
_setRequest = _api.request(MTPauth_RecoverPassword(
|
|
MTP_flags(MTPauth_RecoverPassword::Flag::f_new_settings),
|
|
MTP_string(_cloudFields.fromRecoveryCode),
|
|
settings
|
|
)).done([=](const MTPauth_Authorization &result) {
|
|
recoverPasswordDone(newPasswordBytes, result);
|
|
}).fail([=](const MTP::Error &error) {
|
|
if (MTP::IsFloodError(error)) {
|
|
_newError = tr::lng_flood_error(tr::now);
|
|
update();
|
|
}
|
|
setPasswordFail(newPasswordBytes, email, error);
|
|
}).handleFloodErrors().send();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::changeCloudPassword(
|
|
const QString &oldPassword,
|
|
const QString &newPassword) {
|
|
checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) {
|
|
changeCloudPassword(oldPassword, check, newPassword);
|
|
});
|
|
}
|
|
|
|
void PasscodeBox::changeCloudPassword(
|
|
const QString &oldPassword,
|
|
const Core::CloudPasswordResult &check,
|
|
const QString &newPassword) {
|
|
_setRequest = _api.request(MTPaccount_GetPasswordSettings(
|
|
check.result
|
|
)).done([=](const MTPaccount_PasswordSettings &result) {
|
|
_setRequest = 0;
|
|
|
|
Expects(result.type() == mtpc_account_passwordSettings);
|
|
const auto &data = result.c_account_passwordSettings();
|
|
|
|
const auto wrapped = data.vsecure_settings();
|
|
if (!wrapped) {
|
|
checkPasswordHash([=](const Core::CloudPasswordResult &check) {
|
|
const auto empty = QByteArray();
|
|
sendChangeCloudPassword(check, newPassword, empty);
|
|
});
|
|
return;
|
|
}
|
|
const auto &settings = wrapped->c_secureSecretSettings();
|
|
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."));
|
|
suggestSecretReset(newPassword);
|
|
} else if (Passport::CountSecureSecretId(secret)
|
|
!= settings.vsecure_secret_id().v) {
|
|
LOG(("API Error: Wrong secure secret id."));
|
|
suggestSecretReset(newPassword);
|
|
} else {
|
|
const auto secureSecret = QByteArray(
|
|
reinterpret_cast<const char*>(secret.data()),
|
|
secret.size());
|
|
checkPasswordHash([=](const Core::CloudPasswordResult &check) {
|
|
sendChangeCloudPassword(check, newPassword, secureSecret);
|
|
});
|
|
}
|
|
}).fail([=](const MTP::Error &error) {
|
|
setPasswordFail(error.type());
|
|
}).handleFloodErrors().send();
|
|
}
|
|
|
|
void PasscodeBox::suggestSecretReset(const QString &newPassword) {
|
|
auto resetSecretAndSave = [=](Fn<void()> &&close) {
|
|
checkPasswordHash([=, close = std::move(close)](
|
|
const Core::CloudPasswordResult &check) {
|
|
resetSecret(check, newPassword, std::move(close));
|
|
});
|
|
};
|
|
getDelegate()->show(Ui::MakeConfirmBox({
|
|
.text = { Lang::Hard::PassportCorruptedChange() },
|
|
.confirmed = std::move(resetSecretAndSave),
|
|
.confirmText = Lang::Hard::PassportCorruptedReset(),
|
|
}));
|
|
}
|
|
|
|
void PasscodeBox::resetSecret(
|
|
const Core::CloudPasswordResult &check,
|
|
const QString &newPassword,
|
|
Fn<void()> callback) {
|
|
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
|
_setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
|
|
check.result,
|
|
MTP_account_passwordInputSettings(
|
|
MTP_flags(Flag::f_new_secure_settings),
|
|
MTPPasswordKdfAlgo(), // new_algo
|
|
MTPbytes(), // new_password_hash
|
|
MTPstring(), // hint
|
|
MTPstring(), // email
|
|
MTP_secureSecretSettings(
|
|
MTP_securePasswordKdfAlgoUnknown(), // secure_algo
|
|
MTP_bytes(), // secure_secret
|
|
MTP_long(0))) // secure_secret_id
|
|
)).done([=] {
|
|
_setRequest = 0;
|
|
callback();
|
|
checkPasswordHash([=](const Core::CloudPasswordResult &check) {
|
|
const auto empty = QByteArray();
|
|
sendChangeCloudPassword(check, newPassword, empty);
|
|
});
|
|
}).fail([=](const MTP::Error &error) {
|
|
_setRequest = 0;
|
|
if (error.type() == u"SRP_ID_INVALID"_q) {
|
|
handleSrpIdInvalid();
|
|
}
|
|
}).send();
|
|
}
|
|
|
|
void PasscodeBox::sendChangeCloudPassword(
|
|
const Core::CloudPasswordResult &check,
|
|
const QString &newPassword,
|
|
const QByteArray &secureSecret) {
|
|
const auto newPasswordBytes = newPassword.toUtf8();
|
|
const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
|
|
_cloudFields.mtp.newAlgo,
|
|
bytes::make_span(newPasswordBytes));
|
|
if (newPasswordHash.modpow.empty()) {
|
|
return serverError();
|
|
}
|
|
const auto hint = _passwordHint->getLastText();
|
|
auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
|
|
| MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
|
|
| MTPDaccount_passwordInputSettings::Flag::f_hint;
|
|
auto newSecureSecret = bytes::vector();
|
|
auto newSecureSecretId = 0ULL;
|
|
if (!secureSecret.isEmpty()) {
|
|
flags |= MTPDaccount_passwordInputSettings::Flag::f_new_secure_settings;
|
|
newSecureSecretId = Passport::CountSecureSecretId(
|
|
bytes::make_span(secureSecret));
|
|
newSecureSecret = Passport::EncryptSecureSecret(
|
|
bytes::make_span(secureSecret),
|
|
Core::ComputeSecureSecretHash(
|
|
_cloudFields.mtp.newSecureSecretAlgo,
|
|
bytes::make_span(newPasswordBytes)));
|
|
}
|
|
_setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
|
|
check.result,
|
|
MTP_account_passwordInputSettings(
|
|
MTP_flags(flags),
|
|
Core::PrepareCloudPasswordAlgo(_cloudFields.mtp.newAlgo),
|
|
MTP_bytes(newPasswordHash.modpow),
|
|
MTP_string(hint),
|
|
MTPstring(), // email is not changing
|
|
MTP_secureSecretSettings(
|
|
Core::PrepareSecureSecretAlgo(_cloudFields.mtp.newSecureSecretAlgo),
|
|
MTP_bytes(newSecureSecret),
|
|
MTP_long(newSecureSecretId)))
|
|
)).done([=] {
|
|
setPasswordDone(newPasswordBytes);
|
|
}).fail([=](const MTP::Error &error) {
|
|
setPasswordFail(newPasswordBytes, QString(), error);
|
|
}).handleFloodErrors().send();
|
|
}
|
|
|
|
void PasscodeBox::badOldPasscode() {
|
|
_oldPasscode->selectAll();
|
|
_oldPasscode->setFocus();
|
|
_oldPasscode->showError();
|
|
_oldError = _cloudPwd
|
|
? tr::lng_cloud_password_wrong(tr::now)
|
|
: tr::lng_passcode_wrong(tr::now);
|
|
if (_showRecoverLink && _hintText.isEmpty()) {
|
|
_recover->hide();
|
|
}
|
|
update();
|
|
}
|
|
|
|
void PasscodeBox::oldChanged() {
|
|
if (!_oldError.isEmpty()) {
|
|
_oldError = QString();
|
|
if (_showRecoverLink && _hintText.isEmpty()) {
|
|
_recover->show();
|
|
}
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::newChanged() {
|
|
if (!_newError.isEmpty()) {
|
|
_newError = QString();
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::recoverByEmail() {
|
|
if (!_cloudFields.hasRecovery) {
|
|
Assert(_session != nullptr);
|
|
const auto session = _session;
|
|
const auto reset = crl::guard(this, [=](Fn<void()> &&close) {
|
|
StartPendingReset(session, this, std::move(close));
|
|
});
|
|
getDelegate()->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_cloud_password_reset_no_email(tr::now),
|
|
.confirmed = reset,
|
|
.confirmText = tr::lng_cloud_password_reset_ok(tr::now),
|
|
}));
|
|
} else if (_pattern.isEmpty()) {
|
|
_pattern = "-";
|
|
_api.request(MTPauth_RequestPasswordRecovery(
|
|
)).done([=](const MTPauth_PasswordRecovery &result) {
|
|
recoverStarted(result);
|
|
}).fail([=](const MTP::Error &error) {
|
|
recoverStartFail(error);
|
|
}).send();
|
|
} else {
|
|
recover();
|
|
}
|
|
}
|
|
|
|
void PasscodeBox::recoverExpired() {
|
|
_pattern = QString();
|
|
}
|
|
|
|
void PasscodeBox::recover() {
|
|
if (_pattern == "-" || !_session) {
|
|
return;
|
|
}
|
|
|
|
const auto weak = Ui::MakeWeak(this);
|
|
const auto box = getDelegate()->show(Box<RecoverBox>(
|
|
&_api.instance(),
|
|
_session,
|
|
_pattern,
|
|
_cloudFields,
|
|
[weak] { if (weak) { weak->closeBox(); } }));
|
|
|
|
box->newPasswordSet(
|
|
) | rpl::start_to_stream(_newPasswordSet, lifetime());
|
|
|
|
box->recoveryExpired(
|
|
) | rpl::start_with_next([=] {
|
|
recoverExpired();
|
|
}, lifetime());
|
|
|
|
_replacedBy = box;
|
|
}
|
|
|
|
void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) {
|
|
_pattern = qs(result.c_auth_passwordRecovery().vemail_pattern());
|
|
recover();
|
|
}
|
|
|
|
void PasscodeBox::recoverStartFail(const MTP::Error &error) {
|
|
_pattern = QString();
|
|
closeBox();
|
|
}
|
|
|
|
RecoverBox::RecoverBox(
|
|
QWidget*,
|
|
not_null<MTP::Instance*> mtp,
|
|
Main::Session *session,
|
|
const QString &pattern,
|
|
const PasscodeBox::CloudFields &fields,
|
|
Fn<void()> closeParent)
|
|
: _session(session)
|
|
, _api(mtp)
|
|
, _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5))
|
|
, _cloudFields(fields)
|
|
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code())
|
|
, _noEmailAccess(this, tr::lng_signin_try_password(tr::now))
|
|
, _closeParent(std::move(closeParent)) {
|
|
if (_cloudFields.pendingResetDate != 0 || !session) {
|
|
_noEmailAccess.destroy();
|
|
} else {
|
|
_noEmailAccess->setClickedCallback([=] {
|
|
const auto reset = crl::guard(this, [=](Fn<void()> &&close) {
|
|
const auto closeParent = _closeParent;
|
|
StartPendingReset(session, this, [=, c = std::move(close)] {
|
|
if (closeParent) {
|
|
closeParent();
|
|
}
|
|
c();
|
|
});
|
|
});
|
|
getDelegate()->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_cloud_password_reset_with_email(),
|
|
.confirmed = reset,
|
|
.confirmText = tr::lng_cloud_password_reset_ok(),
|
|
}));
|
|
});
|
|
}
|
|
}
|
|
|
|
rpl::producer<QByteArray> RecoverBox::newPasswordSet() const {
|
|
return _newPasswordSet.events();
|
|
}
|
|
|
|
rpl::producer<> RecoverBox::recoveryExpired() const {
|
|
return _recoveryExpired.events();
|
|
}
|
|
|
|
void RecoverBox::prepare() {
|
|
setTitle(tr::lng_signin_recover_title());
|
|
|
|
addButton(tr::lng_passcode_submit(), [=] { submit(); });
|
|
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
|
|
|
setDimensions(
|
|
st::boxWidth,
|
|
(st::passcodePadding.top()
|
|
+ st::passcodePadding.bottom()
|
|
+ st::passcodeTextLine
|
|
+ _recoverCode->height()
|
|
+ st::passcodeTextLine));
|
|
|
|
_recoverCode->changes(
|
|
) | rpl::start_with_next([=] {
|
|
codeChanged();
|
|
}, _recoverCode->lifetime());
|
|
_recoverCode->submits(
|
|
) | rpl::start_with_next([=] { submit(); }, _recoverCode->lifetime());
|
|
}
|
|
|
|
void RecoverBox::paintEvent(QPaintEvent *e) {
|
|
BoxContent::paintEvent(e);
|
|
|
|
Painter p(this);
|
|
|
|
p.setFont(st::normalFont);
|
|
p.setPen(st::boxTextFg);
|
|
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
|
|
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left);
|
|
|
|
if (!_error.isEmpty()) {
|
|
p.setPen(st::boxTextFgError);
|
|
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left);
|
|
}
|
|
}
|
|
|
|
void RecoverBox::resizeEvent(QResizeEvent *e) {
|
|
BoxContent::resizeEvent(e);
|
|
|
|
_recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height());
|
|
_recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine);
|
|
if (_noEmailAccess) {
|
|
_noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2);
|
|
}
|
|
}
|
|
|
|
void RecoverBox::setInnerFocus() {
|
|
_recoverCode->setFocusFast();
|
|
}
|
|
|
|
void RecoverBox::submit() {
|
|
if (_submitRequest) return;
|
|
|
|
QString code = _recoverCode->getLastText().trimmed();
|
|
if (code.isEmpty()) {
|
|
_recoverCode->setFocus();
|
|
_recoverCode->showError();
|
|
return;
|
|
}
|
|
|
|
const auto send = crl::guard(this, [=] {
|
|
if (_cloudFields.turningOff) {
|
|
// From "Disable cloud password".
|
|
_submitRequest = _api.request(MTPauth_RecoverPassword(
|
|
MTP_flags(0),
|
|
MTP_string(code),
|
|
MTPaccount_PasswordInputSettings()
|
|
)).done([=](const MTPauth_Authorization &result) {
|
|
proceedToClear();
|
|
}).fail([=](const MTP::Error &error) {
|
|
checkSubmitFail(error);
|
|
}).handleFloodErrors().send();
|
|
} else {
|
|
// From "Change cloud password".
|
|
_submitRequest = _api.request(MTPauth_CheckRecoveryPassword(
|
|
MTP_string(code)
|
|
)).done([=] {
|
|
proceedToChange(code);
|
|
}).fail([=](const MTP::Error &error) {
|
|
checkSubmitFail(error);
|
|
}).handleFloodErrors().send();
|
|
}
|
|
});
|
|
if (_cloudFields.notEmptyPassport) {
|
|
getDelegate()->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_cloud_password_passport_losing(),
|
|
.confirmed = [=](Fn<void()> &&close) { send(); close(); },
|
|
.confirmText = tr::lng_continue(),
|
|
}));
|
|
} else {
|
|
send();
|
|
}
|
|
}
|
|
|
|
void RecoverBox::setError(const QString &error) {
|
|
_error = error;
|
|
if (_noEmailAccess) {
|
|
_noEmailAccess->setVisible(error.isEmpty());
|
|
}
|
|
update();
|
|
}
|
|
|
|
void RecoverBox::codeChanged() {
|
|
setError(QString());
|
|
}
|
|
|
|
void RecoverBox::proceedToClear() {
|
|
_submitRequest = 0;
|
|
_newPasswordSet.fire({});
|
|
getDelegate()->show(
|
|
Ui::MakeInformBox(tr::lng_cloud_password_removed()),
|
|
Ui::LayerOption::CloseOther);
|
|
}
|
|
|
|
void RecoverBox::proceedToChange(const QString &code) {
|
|
Expects(!_cloudFields.turningOff);
|
|
_submitRequest = 0;
|
|
|
|
auto fields = _cloudFields;
|
|
fields.fromRecoveryCode = code;
|
|
fields.hasRecovery = false;
|
|
// we could've been turning off, no need to force new password then
|
|
// like if (_cloudFields.turningOff) { just RecoverPassword else Check }
|
|
fields.mtp.curRequest = {};
|
|
fields.hasPassword = false;
|
|
fields.customCheckCallback = nullptr;
|
|
auto box = Box<PasscodeBox>(_session, fields);
|
|
|
|
box->boxClosing(
|
|
) | rpl::start_with_next([=] {
|
|
const auto weak = Ui::MakeWeak(this);
|
|
if (const auto onstack = _closeParent) {
|
|
onstack();
|
|
}
|
|
if (weak) {
|
|
weak->closeBox();
|
|
}
|
|
}, lifetime());
|
|
|
|
box->newPasswordSet(
|
|
) | rpl::start_with_next([=](QByteArray &&password) {
|
|
_newPasswordSet.fire(std::move(password));
|
|
}, lifetime());
|
|
|
|
getDelegate()->show(std::move(box));
|
|
}
|
|
|
|
void RecoverBox::checkSubmitFail(const MTP::Error &error) {
|
|
if (MTP::IsFloodError(error)) {
|
|
_submitRequest = 0;
|
|
setError(tr::lng_flood_error(tr::now));
|
|
_recoverCode->showError();
|
|
return;
|
|
}
|
|
_submitRequest = 0;
|
|
|
|
const QString &err = error.type();
|
|
if (err == u"PASSWORD_EMPTY"_q) {
|
|
_newPasswordSet.fire(QByteArray());
|
|
getDelegate()->show(
|
|
Ui::MakeInformBox(tr::lng_cloud_password_removed()),
|
|
Ui::LayerOption::CloseOther);
|
|
} else if (err == u"PASSWORD_RECOVERY_NA"_q) {
|
|
closeBox();
|
|
} else if (err == u"PASSWORD_RECOVERY_EXPIRED"_q) {
|
|
_recoveryExpired.fire({});
|
|
closeBox();
|
|
} else if (err == u"CODE_INVALID"_q) {
|
|
setError(tr::lng_signin_wrong_code(tr::now));
|
|
_recoverCode->selectAll();
|
|
_recoverCode->setFocus();
|
|
_recoverCode->showError();
|
|
} else {
|
|
setError(Logs::DebugEnabled() // internal server error
|
|
? (err + ": " + error.description())
|
|
: Lang::Hard::ServerError());
|
|
_recoverCode->setFocus();
|
|
}
|
|
}
|
|
|
|
RecoveryEmailValidation ConfirmRecoveryEmail(
|
|
not_null<Main::Session*> session,
|
|
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<Ui::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;
|
|
}
|
|
*requestId = session->api().request(MTPaccount_ConfirmPasswordEmail(
|
|
MTP_string(code)
|
|
)).done([=] {
|
|
*requestId = 0;
|
|
reloads->fire({});
|
|
if (*weak) {
|
|
(*weak)->getDelegate()->show(
|
|
Ui::MakeInformBox(tr::lng_cloud_password_was_set()),
|
|
Ui::LayerOption::CloseOther);
|
|
}
|
|
}).fail([=](const MTP::Error &error) {
|
|
*requestId = 0;
|
|
if (MTP::IsFloodError(error)) {
|
|
errors->fire(tr::lng_flood_error(tr::now));
|
|
} else if (error.type() == u"CODE_INVALID"_q) {
|
|
errors->fire(tr::lng_signin_wrong_code(tr::now));
|
|
} else if (error.type() == u"EMAIL_HASH_EXPIRED"_q) {
|
|
cancels->fire({});
|
|
if (*weak) {
|
|
auto box = Ui::MakeInformBox(
|
|
Lang::Hard::EmailConfirmationExpired());
|
|
(*weak)->getDelegate()->show(
|
|
std::move(box),
|
|
Ui::LayerOption::CloseOther);
|
|
}
|
|
} else {
|
|
errors->fire(Lang::Hard::ServerError());
|
|
}
|
|
}).handleFloodErrors().send();
|
|
};
|
|
const auto resend = [=] {
|
|
if (*requestId) {
|
|
return;
|
|
}
|
|
*requestId = session->api().request(MTPaccount_ResendPasswordEmail(
|
|
)).done([=] {
|
|
*requestId = 0;
|
|
resent->fire(tr::lng_cloud_password_resent(tr::now));
|
|
}).fail([=] {
|
|
*requestId = 0;
|
|
errors->fire(Lang::Hard::ServerError());
|
|
}).send();
|
|
};
|
|
|
|
auto box = Passport::VerifyEmailBox(
|
|
pattern,
|
|
0,
|
|
submit,
|
|
resend,
|
|
errors->events(),
|
|
resent->events());
|
|
|
|
*weak = box.data();
|
|
return { std::move(box), reloads->events(), cancels->events() };
|
|
}
|
|
|
|
[[nodiscard]] object_ptr<Ui::GenericBox> PrePasswordErrorBox(
|
|
const QString &error,
|
|
not_null<Main::Session*> session,
|
|
TextWithEntities &&about) {
|
|
const auto type = [&] {
|
|
if (error == u"PASSWORD_MISSING"_q) {
|
|
return PasswordErrorType::NoPassword;
|
|
} else if (error.startsWith(u"PASSWORD_TOO_FRESH_"_q)
|
|
|| error.startsWith(u"SESSION_TOO_FRESH_"_q)) {
|
|
return PasswordErrorType::Later;
|
|
}
|
|
return PasswordErrorType::None;
|
|
}();
|
|
if (type == PasswordErrorType::None) {
|
|
return nullptr;
|
|
}
|
|
|
|
return Box(
|
|
TransferPasswordError,
|
|
session,
|
|
std::move(about),
|
|
type);
|
|
}
|