1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "boxes/passcode_box.h"
9
10 #include "base/bytes.h"
11 #include "lang/lang_keys.h"
12 #include "ui/boxes/confirm_box.h"
13 #include "base/unixtime.h"
14 #include "mainwindow.h"
15 #include "apiwrap.h"
16 #include "api/api_cloud_password.h"
17 #include "main/main_session.h"
18 #include "main/main_domain.h"
19 #include "core/application.h"
20 #include "storage/storage_domain.h"
21 #include "ui/layers/generic_box.h"
22 #include "ui/text/text_utilities.h"
23 #include "ui/widgets/buttons.h"
24 #include "ui/widgets/input_fields.h"
25 #include "ui/widgets/labels.h"
26 #include "ui/widgets/sent_code_field.h"
27 #include "ui/wrap/vertical_layout.h"
28 #include "ui/wrap/fade_wrap.h"
29 #include "passport/passport_encryption.h"
30 #include "passport/passport_panel_edit_contact.h"
31 #include "settings/settings_privacy_security.h"
32 #include "styles/style_layers.h"
33 #include "styles/style_passport.h"
34 #include "styles/style_boxes.h"
35 #include "base/qt_adapters.h"
36
37 namespace {
38
39 enum class PasswordErrorType {
40 None,
41 NoPassword,
42 Later,
43 };
44
SetCloudPassword(not_null<Ui::GenericBox * > box,not_null<Main::Session * > session)45 void SetCloudPassword(
46 not_null<Ui::GenericBox*> box,
47 not_null<Main::Session*> session) {
48 session->api().cloudPassword().state(
49 ) | rpl::start_with_next([=] {
50 using namespace Settings;
51 const auto weak = Ui::MakeWeak(box);
52 if (CheckEditCloudPassword(session)) {
53 box->getDelegate()->show(
54 EditCloudPasswordBox(session));
55 } else {
56 box->getDelegate()->show(CloudPasswordAppOutdatedBox());
57 }
58 if (weak) {
59 weak->closeBox();
60 }
61 }, box->lifetime());
62 }
63
TransferPasswordError(not_null<Ui::GenericBox * > box,not_null<Main::Session * > session,TextWithEntities && about,PasswordErrorType error)64 void TransferPasswordError(
65 not_null<Ui::GenericBox*> box,
66 not_null<Main::Session*> session,
67 TextWithEntities &&about,
68 PasswordErrorType error) {
69 box->setTitle(tr::lng_rights_transfer_check());
70 box->setWidth(st::transferCheckWidth);
71
72 auto text = std::move(about).append('\n').append('\n').append(
73 tr::lng_rights_transfer_check_password(
74 tr::now,
75 Ui::Text::RichLangValue)
76 ).append('\n').append('\n').append(
77 tr::lng_rights_transfer_check_session(
78 tr::now,
79 Ui::Text::RichLangValue)
80 );
81 if (error == PasswordErrorType::Later) {
82 text.append('\n').append('\n').append(
83 tr::lng_rights_transfer_check_later(
84 tr::now,
85 Ui::Text::RichLangValue));
86 }
87 box->addRow(object_ptr<Ui::FlatLabel>(
88 box,
89 rpl::single(text),
90 st::boxLabel));
91 if (error == PasswordErrorType::Later) {
92 box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
93 } else {
94 box->addButton(tr::lng_rights_transfer_set_password(), [=] {
95 SetCloudPassword(box, session);
96 });
97 box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
98 }
99 }
100
StartPendingReset(not_null<Main::Session * > session,not_null<Ui::BoxContent * > context,Fn<void ()> close)101 void StartPendingReset(
102 not_null<Main::Session*> session,
103 not_null<Ui::BoxContent*> context,
104 Fn<void()> close) {
105 const auto weak = Ui::MakeWeak(context.get());
106 auto lifetime = std::make_shared<rpl::lifetime>();
107
108 auto finish = [=](const QString &message) mutable {
109 if (const auto strong = weak.data()) {
110 if (!message.isEmpty()) {
111 strong->getDelegate()->show(Box<Ui::InformBox>(message));
112 }
113 strong->closeBox();
114 }
115 close();
116 if (lifetime) {
117 base::take(lifetime)->destroy();
118 }
119 };
120
121 session->api().cloudPassword().resetPassword(
122 ) | rpl::start_with_next_error_done([=](
123 Api::CloudPassword::ResetRetryDate retryDate) {
124 constexpr auto kMinute = 60;
125 constexpr auto kHour = 3600;
126 constexpr auto kDay = 86400;
127 const auto left = std::max(
128 retryDate - base::unixtime::now(),
129 kMinute);
130 const auto days = (left / kDay);
131 const auto hours = (left / kHour);
132 const auto minutes = (left / kMinute);
133 const auto duration = days
134 ? tr::lng_group_call_duration_days(tr::now, lt_count, days)
135 : hours
136 ? tr::lng_group_call_duration_hours(tr::now, lt_count, hours)
137 : tr::lng_group_call_duration_minutes(
138 tr::now,
139 lt_count,
140 minutes);
141 if (const auto strong = weak.data()) {
142 strong->getDelegate()->show(Box<Ui::InformBox>(
143 tr::lng_cloud_password_reset_later(
144 tr::now,
145 lt_duration,
146 duration)));
147 }
148 }, [=](const QString &error) mutable {
149 finish("Error: " + error);
150 }, [=]() mutable {
151 finish({});
152 }, *lifetime);
153 }
154
155 } // namespace
156
From(const Core::CloudPasswordState & current)157 PasscodeBox::CloudFields PasscodeBox::CloudFields::From(
158 const Core::CloudPasswordState ¤t) {
159 auto result = CloudFields();
160 result.curRequest = current.request;
161 result.newAlgo = current.newPassword;
162 result.newSecureSecretAlgo = current.newSecureSecret;
163 result.hasRecovery = current.hasRecovery;
164 result.notEmptyPassport = current.notEmptyPassport;
165 result.hint = current.hint;
166 result.pendingResetDate = current.pendingResetDate;
167 return result;
168 }
169
PasscodeBox(QWidget *,not_null<Main::Session * > session,bool turningOff)170 PasscodeBox::PasscodeBox(
171 QWidget*,
172 not_null<Main::Session*> session,
173 bool turningOff)
174 : _session(session)
175 , _api(&_session->mtp())
176 , _turningOff(turningOff)
177 , _about(st::boxWidth - st::boxPadding.left() * 1.5)
178 , _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old())
179 , _newPasscode(
180 this,
181 st::defaultInputField,
182 session->domain().local().hasLocalPasscode()
183 ? tr::lng_passcode_enter_new()
184 : tr::lng_passcode_enter_first())
185 , _reenterPasscode(this, st::defaultInputField, tr::lng_passcode_confirm_new())
186 , _passwordHint(this, st::defaultInputField, tr::lng_cloud_password_hint())
187 , _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
188 , _recover(this, tr::lng_signin_recover(tr::now)) {
189 }
190
PasscodeBox(QWidget *,not_null<MTP::Instance * > mtp,Main::Session * session,const CloudFields & fields)191 PasscodeBox::PasscodeBox(
192 QWidget*,
193 not_null<MTP::Instance*> mtp,
194 Main::Session *session,
195 const CloudFields &fields)
196 : _session(session)
197 , _api(mtp)
198 , _turningOff(fields.turningOff)
199 , _cloudPwd(true)
200 , _cloudFields(fields)
201 , _about(st::boxWidth - st::boxPadding.left() * 1.5)
202 , _oldPasscode(this, st::defaultInputField, tr::lng_cloud_password_enter_old())
203 , _newPasscode(
204 this,
205 st::defaultInputField,
206 (fields.curRequest
207 ? tr::lng_cloud_password_enter_new()
208 : tr::lng_cloud_password_enter_first()))
209 , _reenterPasscode(this, st::defaultInputField, tr::lng_cloud_password_confirm_new())
210 , _passwordHint(
211 this,
212 st::defaultInputField,
213 (fields.curRequest
214 ? tr::lng_cloud_password_change_hint()
215 : tr::lng_cloud_password_hint()))
216 , _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
217 , _recover(this, tr::lng_signin_recover(tr::now))
218 , _showRecoverLink(_cloudFields.hasRecovery || !_cloudFields.pendingResetDate) {
219 Expects(session != nullptr || !fields.fromRecoveryCode.isEmpty());
220 Expects(!_turningOff || _cloudFields.curRequest);
221
222 if (!_cloudFields.hint.isEmpty()) {
223 _hintText.setText(
224 st::passcodeTextStyle,
225 tr::lng_signin_hint(tr::now, lt_password_hint, _cloudFields.hint));
226 }
227 }
228
PasscodeBox(QWidget *,not_null<Main::Session * > session,const CloudFields & fields)229 PasscodeBox::PasscodeBox(
230 QWidget*,
231 not_null<Main::Session*> session,
232 const CloudFields &fields)
233 : PasscodeBox(nullptr, &session->mtp(), session, fields) {
234 }
235
newPasswordSet() const236 rpl::producer<QByteArray> PasscodeBox::newPasswordSet() const {
237 return _newPasswordSet.events();
238 }
239
passwordReloadNeeded() const240 rpl::producer<> PasscodeBox::passwordReloadNeeded() const {
241 return _passwordReloadNeeded.events();
242 }
243
clearUnconfirmedPassword() const244 rpl::producer<> PasscodeBox::clearUnconfirmedPassword() const {
245 return _clearUnconfirmedPassword.events();
246 }
247
newAuthorization() const248 rpl::producer<MTPauth_Authorization> PasscodeBox::newAuthorization() const {
249 return _newAuthorization.events();
250 }
251
currentlyHave() const252 bool PasscodeBox::currentlyHave() const {
253 return _cloudPwd
254 ? (!!_cloudFields.curRequest)
255 : _session->domain().local().hasLocalPasscode();
256 }
257
onlyCheckCurrent() const258 bool PasscodeBox::onlyCheckCurrent() const {
259 return _turningOff || _cloudFields.customCheckCallback;
260 }
261
prepare()262 void PasscodeBox::prepare() {
263 addButton(
264 (_cloudFields.customSubmitButton
265 ? std::move(_cloudFields.customSubmitButton)
266 : _turningOff
267 ? tr::lng_passcode_remove_button()
268 : tr::lng_settings_save()),
269 [=] { save(); });
270 addButton(tr::lng_cancel(), [=] { closeBox(); });
271
272 _about.setText(
273 st::passcodeTextStyle,
274 (_cloudFields.customDescription
275 ? *_cloudFields.customDescription
276 : _cloudPwd
277 ? tr::lng_cloud_password_about(tr::now)
278 : tr::lng_passcode_about(tr::now)));
279 _aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5);
280 const auto onlyCheck = onlyCheckCurrent();
281 if (onlyCheck) {
282 _oldPasscode->show();
283 setTitle(_cloudFields.customTitle
284 ? std::move(_cloudFields.customTitle)
285 : _cloudPwd
286 ? tr::lng_cloud_password_remove()
287 : tr::lng_passcode_remove());
288 setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
289 } else {
290 if (currentlyHave()) {
291 _oldPasscode->show();
292 setTitle(_cloudPwd
293 ? tr::lng_cloud_password_change()
294 : tr::lng_passcode_change());
295 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());
296 } else {
297 _oldPasscode->hide();
298 setTitle(_cloudPwd
299 ? (_cloudFields.fromRecoveryCode.isEmpty()
300 ? tr::lng_cloud_password_create()
301 : tr::lng_cloud_password_change())
302 : tr::lng_passcode_create());
303 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()));
304 }
305 }
306
307 connect(_oldPasscode, &Ui::MaskedInputField::changed, [=] { oldChanged(); });
308 connect(_newPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); });
309 connect(_reenterPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); });
310 connect(_passwordHint, &Ui::InputField::changed, [=] { newChanged(); });
311 connect(_recoverEmail, &Ui::InputField::changed, [=] { emailChanged(); });
312
313 const auto fieldSubmit = [=] { submit(); };
314 connect(_oldPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
315 connect(_newPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
316 connect(_reenterPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
317 connect(_passwordHint, &Ui::InputField::submitted, fieldSubmit);
318 connect(_recoverEmail, &Ui::InputField::submitted, fieldSubmit);
319
320 _recover->addClickHandler([=] { recoverByEmail(); });
321
322 const auto has = currentlyHave();
323 _oldPasscode->setVisible(onlyCheck || has);
324 _recover->setVisible((onlyCheck || has)
325 && _cloudPwd
326 && _showRecoverLink);
327 _newPasscode->setVisible(!onlyCheck);
328 _reenterPasscode->setVisible(!onlyCheck);
329 _passwordHint->setVisible(!onlyCheck && _cloudPwd);
330 _recoverEmail->setVisible(!onlyCheck
331 && _cloudPwd
332 && !has
333 && _cloudFields.fromRecoveryCode.isEmpty());
334 }
335
submit()336 void PasscodeBox::submit() {
337 const auto has = currentlyHave();
338 if (_oldPasscode->hasFocus()) {
339 if (onlyCheckCurrent()) {
340 save();
341 } else {
342 _newPasscode->setFocus();
343 }
344 } else if (_newPasscode->hasFocus()) {
345 _reenterPasscode->setFocus();
346 } else if (_reenterPasscode->hasFocus()) {
347 if (has && _oldPasscode->text().isEmpty()) {
348 _oldPasscode->setFocus();
349 _oldPasscode->showError();
350 } else if (_newPasscode->text().isEmpty()) {
351 _newPasscode->setFocus();
352 _newPasscode->showError();
353 } else if (_reenterPasscode->text().isEmpty()) {
354 _reenterPasscode->showError();
355 } else if (!_passwordHint->isHidden()) {
356 _passwordHint->setFocus();
357 } else {
358 save();
359 }
360 } else if (_passwordHint->hasFocus()) {
361 if (_recoverEmail->isHidden()) {
362 save();
363 } else {
364 _recoverEmail->setFocus();
365 }
366 } else if (_recoverEmail->hasFocus()) {
367 save();
368 }
369 }
370
paintEvent(QPaintEvent * e)371 void PasscodeBox::paintEvent(QPaintEvent *e) {
372 BoxContent::paintEvent(e);
373
374 Painter p(this);
375
376 int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
377 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;
378 p.setPen(st::boxTextFg);
379 _about.drawLeft(p, st::boxPadding.left(), abouty, w, width());
380
381 if (!_hintText.isEmpty() && _oldError.isEmpty()) {
382 _hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft);
383 }
384
385 if (!_oldError.isEmpty()) {
386 p.setPen(st::boxTextFgError);
387 p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left);
388 }
389
390 if (!_newError.isEmpty()) {
391 p.setPen(st::boxTextFgError);
392 p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left);
393 }
394
395 if (!_emailError.isEmpty()) {
396 p.setPen(st::boxTextFgError);
397 p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left);
398 }
399 }
400
resizeEvent(QResizeEvent * e)401 void PasscodeBox::resizeEvent(QResizeEvent *e) {
402 BoxContent::resizeEvent(e);
403
404 const auto has = currentlyHave();
405 int32 w = st::boxWidth - st::boxPadding.left() - st::boxPadding.right();
406 _oldPasscode->resize(w, _oldPasscode->height());
407 _oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top());
408 _newPasscode->resize(w, _newPasscode->height());
409 _newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0));
410 _reenterPasscode->resize(w, _reenterPasscode->height());
411 _reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::passcodeLittleSkip);
412 _passwordHint->resize(w, _passwordHint->height());
413 _passwordHint->moveToLeft(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height() + st::passcodeSkip);
414 _recoverEmail->resize(w, _passwordHint->height());
415 _recoverEmail->moveToLeft(st::boxPadding.left(), _passwordHint->y() + _passwordHint->height() + st::passcodeLittleSkip + _aboutHeight + st::passcodeLittleSkip);
416
417 if (!_recover->isHidden()) {
418 _recover->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + (_hintText.isEmpty() ? ((st::passcodeTextLine - _recover->height()) / 2) : st::passcodeTextLine));
419 }
420 }
421
setInnerFocus()422 void PasscodeBox::setInnerFocus() {
423 if (_skipEmailWarning && !_recoverEmail->isHidden()) {
424 _recoverEmail->setFocusFast();
425 } else if (_oldPasscode->isHidden()) {
426 _newPasscode->setFocusFast();
427 } else {
428 _oldPasscode->setFocusFast();
429 }
430 }
431
recoverPasswordDone(const QByteArray & newPasswordBytes,const MTPauth_Authorization & result)432 void PasscodeBox::recoverPasswordDone(
433 const QByteArray &newPasswordBytes,
434 const MTPauth_Authorization &result) {
435 if (_replacedBy) {
436 _replacedBy->closeBox();
437 }
438 _setRequest = 0;
439 const auto weak = Ui::MakeWeak(this);
440 _newAuthorization.fire_copy(result);
441 if (weak) {
442 _newPasswordSet.fire_copy(newPasswordBytes);
443 if (weak) {
444 getDelegate()->show(Box<Ui::InformBox>(
445 tr::lng_cloud_password_updated(tr::now)));
446 if (weak) {
447 closeBox();
448 }
449 }
450 }
451 }
452
setPasswordDone(const QByteArray & newPasswordBytes)453 void PasscodeBox::setPasswordDone(const QByteArray &newPasswordBytes) {
454 if (_replacedBy) {
455 _replacedBy->closeBox();
456 }
457 _setRequest = 0;
458 const auto weak = Ui::MakeWeak(this);
459 _newPasswordSet.fire_copy(newPasswordBytes);
460 if (weak) {
461 const auto text = _reenterPasscode->isHidden()
462 ? tr::lng_cloud_password_removed(tr::now)
463 : _oldPasscode->isHidden()
464 ? tr::lng_cloud_password_was_set(tr::now)
465 : tr::lng_cloud_password_updated(tr::now);
466 getDelegate()->show(Box<Ui::InformBox>(text));
467 if (weak) {
468 closeBox();
469 }
470 }
471 }
472
closeReplacedBy()473 void PasscodeBox::closeReplacedBy() {
474 if (isHidden()) {
475 if (_replacedBy && !_replacedBy->isHidden()) {
476 _replacedBy->closeBox();
477 }
478 }
479 }
480
setPasswordFail(const MTP::Error & error)481 void PasscodeBox::setPasswordFail(const MTP::Error &error) {
482 setPasswordFail(error.type());
483 }
484
setPasswordFail(const QString & type)485 void PasscodeBox::setPasswordFail(const QString &type) {
486 if (MTP::IsFloodError(type)) {
487 closeReplacedBy();
488 _setRequest = 0;
489
490 _oldPasscode->selectAll();
491 _oldPasscode->setFocus();
492 _oldPasscode->showError();
493 _oldError = tr::lng_flood_error(tr::now);
494 if (_showRecoverLink && _hintText.isEmpty()) {
495 _recover->hide();
496 }
497 update();
498 return;
499 }
500
501 closeReplacedBy();
502 _setRequest = 0;
503 if (type == qstr("PASSWORD_HASH_INVALID")
504 || type == qstr("SRP_PASSWORD_CHANGED")) {
505 if (_oldPasscode->isHidden()) {
506 _passwordReloadNeeded.fire({});
507 closeBox();
508 } else {
509 badOldPasscode();
510 }
511 } else if (type == qstr("SRP_ID_INVALID")) {
512 handleSrpIdInvalid();
513 //} else if (type == qstr("NEW_PASSWORD_BAD")) {
514 //} else if (type == qstr("NEW_SALT_INVALID")) {
515 } else if (type == qstr("EMAIL_INVALID")) {
516 _emailError = tr::lng_cloud_password_bad_email(tr::now);
517 _recoverEmail->setFocus();
518 _recoverEmail->showError();
519 update();
520 }
521 }
522
setPasswordFail(const QByteArray & newPasswordBytes,const QString & email,const MTP::Error & error)523 void PasscodeBox::setPasswordFail(
524 const QByteArray &newPasswordBytes,
525 const QString &email,
526 const MTP::Error &error) {
527 const auto prefix = qstr("EMAIL_UNCONFIRMED_");
528 if (error.type().startsWith(prefix)) {
529 const auto codeLength = base::StringViewMid(error.type(), prefix.size()).toInt();
530
531 closeReplacedBy();
532 _setRequest = 0;
533
534 validateEmail(email, codeLength, newPasswordBytes);
535 } else {
536 setPasswordFail(error);
537 }
538 }
539
validateEmail(const QString & email,int codeLength,const QByteArray & newPasswordBytes)540 void PasscodeBox::validateEmail(
541 const QString &email,
542 int codeLength,
543 const QByteArray &newPasswordBytes) {
544 const auto errors = std::make_shared<rpl::event_stream<QString>>();
545 const auto resent = std::make_shared<rpl::event_stream<QString>>();
546 const auto set = std::make_shared<bool>(false);
547 const auto submit = crl::guard(this, [=](QString code) {
548 if (_setRequest) {
549 return;
550 }
551 _setRequest = _api.request(MTPaccount_ConfirmPasswordEmail(
552 MTP_string(code)
553 )).done([=](const MTPBool &result) {
554 *set = true;
555 setPasswordDone(newPasswordBytes);
556 }).fail([=](const MTP::Error &error) {
557 _setRequest = 0;
558 if (MTP::IsFloodError(error)) {
559 errors->fire(tr::lng_flood_error(tr::now));
560 } else if (error.type() == qstr("CODE_INVALID")) {
561 errors->fire(tr::lng_signin_wrong_code(tr::now));
562 } else if (error.type() == qstr("EMAIL_HASH_EXPIRED")) {
563 const auto weak = Ui::MakeWeak(this);
564 _clearUnconfirmedPassword.fire({});
565 if (weak) {
566 auto box = Box<Ui::InformBox>(
567 Lang::Hard::EmailConfirmationExpired());
568 weak->getDelegate()->show(
569 std::move(box),
570 Ui::LayerOption::CloseOther);
571 }
572 } else {
573 errors->fire(Lang::Hard::ServerError());
574 }
575 }).handleFloodErrors().send();
576 });
577 const auto resend = crl::guard(this, [=] {
578 if (_setRequest) {
579 return;
580 }
581 _setRequest = _api.request(MTPaccount_ResendPasswordEmail(
582 )).done([=](const MTPBool &result) {
583 _setRequest = 0;
584 resent->fire(tr::lng_cloud_password_resent(tr::now));
585 }).fail([=](const MTP::Error &error) {
586 _setRequest = 0;
587 errors->fire(Lang::Hard::ServerError());
588 }).send();
589 });
590 const auto box = _replacedBy = getDelegate()->show(
591 Passport::VerifyEmailBox(
592 email,
593 codeLength,
594 submit,
595 resend,
596 errors->events(),
597 resent->events()));
598
599 box->setCloseByOutsideClick(false);
600 box->setCloseByEscape(false);
601 box->boxClosing(
602 ) | rpl::filter([=] {
603 return !*set;
604 }) | start_with_next([=, weak = Ui::MakeWeak(this)] {
605 if (weak) {
606 weak->_clearUnconfirmedPassword.fire({});
607 }
608 if (weak) {
609 weak->closeBox();
610 }
611 }, box->lifetime());
612 }
613
handleSrpIdInvalid()614 void PasscodeBox::handleSrpIdInvalid() {
615 const auto now = crl::now();
616 if (_lastSrpIdInvalidTime > 0
617 && now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) {
618 _cloudFields.curRequest.id = 0;
619 _oldError = Lang::Hard::ServerError();
620 update();
621 } else {
622 _lastSrpIdInvalidTime = now;
623 requestPasswordData();
624 }
625 }
626
save(bool force)627 void PasscodeBox::save(bool force) {
628 if (_setRequest) return;
629
630 QString old = _oldPasscode->text(), pwd = _newPasscode->text(), conf = _reenterPasscode->text();
631 const auto has = currentlyHave();
632 if (!_cloudPwd && (_turningOff || has)) {
633 if (!passcodeCanTry()) {
634 _oldError = tr::lng_flood_error(tr::now);
635 _oldPasscode->setFocus();
636 _oldPasscode->showError();
637 update();
638 return;
639 }
640
641 if (_session->domain().local().checkPasscode(old.toUtf8())) {
642 cSetPasscodeBadTries(0);
643 if (_turningOff) pwd = conf = QString();
644 } else {
645 cSetPasscodeBadTries(cPasscodeBadTries() + 1);
646 cSetPasscodeLastTry(crl::now());
647 badOldPasscode();
648 return;
649 }
650 }
651 const auto onlyCheck = onlyCheckCurrent();
652 if (!onlyCheck && pwd.isEmpty()) {
653 _newPasscode->setFocus();
654 _newPasscode->showError();
655 closeReplacedBy();
656 return;
657 }
658 if (!onlyCheck && pwd != conf) {
659 _reenterPasscode->selectAll();
660 _reenterPasscode->setFocus();
661 _reenterPasscode->showError();
662 if (!conf.isEmpty()) {
663 _newError = _cloudPwd
664 ? tr::lng_cloud_password_differ(tr::now)
665 : tr::lng_passcode_differ(tr::now);
666 update();
667 }
668 closeReplacedBy();
669 } else if (!onlyCheck && has && old == pwd) {
670 _newPasscode->setFocus();
671 _newPasscode->showError();
672 _newError = _cloudPwd
673 ? tr::lng_cloud_password_is_same(tr::now)
674 : tr::lng_passcode_is_same(tr::now);
675 update();
676 closeReplacedBy();
677 } else if (_cloudPwd) {
678 QString hint = _passwordHint->getLastText(), email = _recoverEmail->getLastText().trimmed();
679 if (!onlyCheck
680 && !_passwordHint->isHidden()
681 && !_newPasscode->isHidden()
682 && pwd == hint) {
683 _newPasscode->setFocus();
684 _newPasscode->showError();
685 _newError = tr::lng_cloud_password_bad(tr::now);
686 update();
687 closeReplacedBy();
688 return;
689 }
690 if (!onlyCheck && !_recoverEmail->isHidden() && email.isEmpty() && !force) {
691 _skipEmailWarning = true;
692 _replacedBy = getDelegate()->show(
693 Box<Ui::ConfirmBox>(
694 tr::lng_cloud_password_about_recover(tr::now),
695 tr::lng_cloud_password_skip_email(tr::now),
696 st::attentionBoxButton,
697 crl::guard(this, [this] { save(true); })));
698 } else if (onlyCheck) {
699 submitOnlyCheckCloudPassword(old);
700 } else if (_oldPasscode->isHidden()) {
701 setNewCloudPassword(pwd);
702 } else {
703 changeCloudPassword(old, pwd);
704 }
705 } else {
706 closeReplacedBy();
707 const auto weak = Ui::MakeWeak(this);
708 cSetPasscodeBadTries(0);
709 _session->domain().local().setPasscode(pwd.toUtf8());
710 Core::App().localPasscodeChanged();
711 if (weak) {
712 closeBox();
713 }
714 }
715 }
716
submitOnlyCheckCloudPassword(const QString & oldPassword)717 void PasscodeBox::submitOnlyCheckCloudPassword(const QString &oldPassword) {
718 Expects(!_oldPasscode->isHidden());
719
720 const auto send = [=] {
721 sendOnlyCheckCloudPassword(oldPassword);
722 };
723 if (_cloudFields.turningOff && _cloudFields.notEmptyPassport) {
724 Assert(!_cloudFields.customCheckCallback);
725
726 const auto confirmed = [=](Fn<void()> &&close) {
727 send();
728 close();
729 };
730 getDelegate()->show(Box<Ui::ConfirmBox>(
731 tr::lng_cloud_password_passport_losing(tr::now),
732 tr::lng_continue(tr::now),
733 confirmed));
734 } else {
735 send();
736 }
737 }
738
sendOnlyCheckCloudPassword(const QString & oldPassword)739 void PasscodeBox::sendOnlyCheckCloudPassword(const QString &oldPassword) {
740 checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) {
741 if (const auto onstack = _cloudFields.customCheckCallback) {
742 onstack(check);
743 } else {
744 Assert(_cloudFields.turningOff);
745 sendClearCloudPassword(check);
746 }
747 });
748 }
749
checkPassword(const QString & oldPassword,CheckPasswordCallback callback)750 void PasscodeBox::checkPassword(
751 const QString &oldPassword,
752 CheckPasswordCallback callback) {
753 const auto passwordUtf = oldPassword.toUtf8();
754 _checkPasswordHash = Core::ComputeCloudPasswordHash(
755 _cloudFields.curRequest.algo,
756 bytes::make_span(passwordUtf));
757 checkPasswordHash(std::move(callback));
758 }
759
checkPasswordHash(CheckPasswordCallback callback)760 void PasscodeBox::checkPasswordHash(CheckPasswordCallback callback) {
761 _checkPasswordCallback = std::move(callback);
762 if (_cloudFields.curRequest.id) {
763 passwordChecked();
764 } else {
765 requestPasswordData();
766 }
767 }
768
passwordChecked()769 void PasscodeBox::passwordChecked() {
770 if (!_cloudFields.curRequest || !_cloudFields.curRequest.id || !_checkPasswordCallback) {
771 return serverError();
772 }
773 const auto check = Core::ComputeCloudPasswordCheck(
774 _cloudFields.curRequest,
775 _checkPasswordHash);
776 if (!check) {
777 return serverError();
778 }
779 _cloudFields.curRequest.id = 0;
780 _checkPasswordCallback(check);
781 }
782
requestPasswordData()783 void PasscodeBox::requestPasswordData() {
784 if (!_checkPasswordCallback) {
785 return serverError();
786 }
787
788 _api.request(base::take(_setRequest)).cancel();
789 _setRequest = _api.request(
790 MTPaccount_GetPassword()
791 ).done([=](const MTPaccount_Password &result) {
792 _setRequest = 0;
793 result.match([&](const MTPDaccount_password &data) {
794 _cloudFields.curRequest = Core::ParseCloudPasswordCheckRequest(data);
795 passwordChecked();
796 });
797 }).send();
798 }
799
serverError()800 void PasscodeBox::serverError() {
801 getDelegate()->show(Box<Ui::InformBox>(Lang::Hard::ServerError()));
802 closeBox();
803 }
804
handleCustomCheckError(const MTP::Error & error)805 bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) {
806 return handleCustomCheckError(error.type());
807 }
808
handleCustomCheckError(const QString & type)809 bool PasscodeBox::handleCustomCheckError(const QString &type) {
810 if (MTP::IsFloodError(type)
811 || type == qstr("PASSWORD_HASH_INVALID")
812 || type == qstr("SRP_PASSWORD_CHANGED")
813 || type == qstr("SRP_ID_INVALID")) {
814 setPasswordFail(type);
815 return true;
816 }
817 return false;
818 }
819
sendClearCloudPassword(const Core::CloudPasswordResult & check)820 void PasscodeBox::sendClearCloudPassword(
821 const Core::CloudPasswordResult &check) {
822 const auto hint = QString();
823 const auto email = QString();
824 const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
825 | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
826 | MTPDaccount_passwordInputSettings::Flag::f_hint
827 | MTPDaccount_passwordInputSettings::Flag::f_email;
828 _setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
829 check.result,
830 MTP_account_passwordInputSettings(
831 MTP_flags(flags),
832 Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
833 MTP_bytes(), // new_password_hash
834 MTP_string(hint),
835 MTP_string(email),
836 MTPSecureSecretSettings())
837 )).done([=](const MTPBool &result) {
838 setPasswordDone({});
839 }).fail([=](const MTP::Error &error) mutable {
840 setPasswordFail({}, QString(), error);
841 }).handleFloodErrors().send();
842 }
843
setNewCloudPassword(const QString & newPassword)844 void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
845 const auto newPasswordBytes = newPassword.toUtf8();
846 const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
847 _cloudFields.newAlgo,
848 bytes::make_span(newPasswordBytes));
849 if (newPasswordHash.modpow.empty()) {
850 return serverError();
851 }
852 const auto hint = _passwordHint->getLastText();
853 const auto email = _recoverEmail->getLastText().trimmed();
854 using Flag = MTPDaccount_passwordInputSettings::Flag;
855 const auto flags = Flag::f_new_algo
856 | Flag::f_new_password_hash
857 | Flag::f_hint
858 | (_cloudFields.fromRecoveryCode.isEmpty() ? Flag::f_email : Flag(0));
859 _checkPasswordCallback = nullptr;
860
861 const auto settings = MTP_account_passwordInputSettings(
862 MTP_flags(flags),
863 Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
864 MTP_bytes(newPasswordHash.modpow),
865 MTP_string(hint),
866 MTP_string(email),
867 MTPSecureSecretSettings());
868 if (_cloudFields.fromRecoveryCode.isEmpty()) {
869 _setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
870 MTP_inputCheckPasswordEmpty(),
871 settings
872 )).done([=](const MTPBool &result) {
873 setPasswordDone(newPasswordBytes);
874 }).fail([=](const MTP::Error &error) {
875 setPasswordFail(newPasswordBytes, email, error);
876 }).handleFloodErrors().send();
877 } else {
878 _setRequest = _api.request(MTPauth_RecoverPassword(
879 MTP_flags(MTPauth_RecoverPassword::Flag::f_new_settings),
880 MTP_string(_cloudFields.fromRecoveryCode),
881 settings
882 )).done([=](const MTPauth_Authorization &result) {
883 recoverPasswordDone(newPasswordBytes, result);
884 }).fail([=](const MTP::Error &error) {
885 if (MTP::IsFloodError(error)) {
886 _newError = tr::lng_flood_error(tr::now);
887 update();
888 }
889 setPasswordFail(newPasswordBytes, email, error);
890 }).handleFloodErrors().send();
891 }
892 }
893
changeCloudPassword(const QString & oldPassword,const QString & newPassword)894 void PasscodeBox::changeCloudPassword(
895 const QString &oldPassword,
896 const QString &newPassword) {
897 checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) {
898 changeCloudPassword(oldPassword, check, newPassword);
899 });
900 }
901
changeCloudPassword(const QString & oldPassword,const Core::CloudPasswordResult & check,const QString & newPassword)902 void PasscodeBox::changeCloudPassword(
903 const QString &oldPassword,
904 const Core::CloudPasswordResult &check,
905 const QString &newPassword) {
906 _setRequest = _api.request(MTPaccount_GetPasswordSettings(
907 check.result
908 )).done([=](const MTPaccount_PasswordSettings &result) {
909 _setRequest = 0;
910
911 Expects(result.type() == mtpc_account_passwordSettings);
912 const auto &data = result.c_account_passwordSettings();
913
914 const auto wrapped = data.vsecure_settings();
915 if (!wrapped) {
916 checkPasswordHash([=](const Core::CloudPasswordResult &check) {
917 const auto empty = QByteArray();
918 sendChangeCloudPassword(check, newPassword, empty);
919 });
920 return;
921 }
922 const auto &settings = wrapped->c_secureSecretSettings();
923 const auto passwordUtf = oldPassword.toUtf8();
924 const auto secret = Passport::DecryptSecureSecret(
925 bytes::make_span(settings.vsecure_secret().v),
926 Core::ComputeSecureSecretHash(
927 Core::ParseSecureSecretAlgo(settings.vsecure_algo()),
928 bytes::make_span(passwordUtf)));
929 if (secret.empty()) {
930 LOG(("API Error: Failed to decrypt secure secret."));
931 suggestSecretReset(newPassword);
932 } else if (Passport::CountSecureSecretId(secret)
933 != settings.vsecure_secret_id().v) {
934 LOG(("API Error: Wrong secure secret id."));
935 suggestSecretReset(newPassword);
936 } else {
937 const auto secureSecret = QByteArray(
938 reinterpret_cast<const char*>(secret.data()),
939 secret.size());
940 checkPasswordHash([=](const Core::CloudPasswordResult &check) {
941 sendChangeCloudPassword(check, newPassword, secureSecret);
942 });
943 }
944 }).fail([=](const MTP::Error &error) {
945 setPasswordFail(error);
946 }).handleFloodErrors().send();
947 }
948
suggestSecretReset(const QString & newPassword)949 void PasscodeBox::suggestSecretReset(const QString &newPassword) {
950 auto resetSecretAndSave = [=](Fn<void()> &&close) {
951 checkPasswordHash([=, close = std::move(close)](
952 const Core::CloudPasswordResult &check) {
953 resetSecret(check, newPassword, std::move(close));
954 });
955 };
956 getDelegate()->show(Box<Ui::ConfirmBox>(
957 Lang::Hard::PassportCorruptedChange(),
958 Lang::Hard::PassportCorruptedReset(),
959 std::move(resetSecretAndSave)));
960 }
961
resetSecret(const Core::CloudPasswordResult & check,const QString & newPassword,Fn<void ()> callback)962 void PasscodeBox::resetSecret(
963 const Core::CloudPasswordResult &check,
964 const QString &newPassword,
965 Fn<void()> callback) {
966 using Flag = MTPDaccount_passwordInputSettings::Flag;
967 _setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
968 check.result,
969 MTP_account_passwordInputSettings(
970 MTP_flags(Flag::f_new_secure_settings),
971 MTPPasswordKdfAlgo(), // new_algo
972 MTPbytes(), // new_password_hash
973 MTPstring(), // hint
974 MTPstring(), // email
975 MTP_secureSecretSettings(
976 MTP_securePasswordKdfAlgoUnknown(), // secure_algo
977 MTP_bytes(), // secure_secret
978 MTP_long(0))) // secure_secret_id
979 )).done([=](const MTPBool &result) {
980 _setRequest = 0;
981 callback();
982 checkPasswordHash([=](const Core::CloudPasswordResult &check) {
983 const auto empty = QByteArray();
984 sendChangeCloudPassword(check, newPassword, empty);
985 });
986 }).fail([=](const MTP::Error &error) {
987 _setRequest = 0;
988 if (error.type() == qstr("SRP_ID_INVALID")) {
989 handleSrpIdInvalid();
990 }
991 }).send();
992 }
993
sendChangeCloudPassword(const Core::CloudPasswordResult & check,const QString & newPassword,const QByteArray & secureSecret)994 void PasscodeBox::sendChangeCloudPassword(
995 const Core::CloudPasswordResult &check,
996 const QString &newPassword,
997 const QByteArray &secureSecret) {
998 const auto newPasswordBytes = newPassword.toUtf8();
999 const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
1000 _cloudFields.newAlgo,
1001 bytes::make_span(newPasswordBytes));
1002 if (newPasswordHash.modpow.empty()) {
1003 return serverError();
1004 }
1005 const auto hint = _passwordHint->getLastText();
1006 auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo
1007 | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash
1008 | MTPDaccount_passwordInputSettings::Flag::f_hint;
1009 auto newSecureSecret = bytes::vector();
1010 auto newSecureSecretId = 0ULL;
1011 if (!secureSecret.isEmpty()) {
1012 flags |= MTPDaccount_passwordInputSettings::Flag::f_new_secure_settings;
1013 newSecureSecretId = Passport::CountSecureSecretId(
1014 bytes::make_span(secureSecret));
1015 newSecureSecret = Passport::EncryptSecureSecret(
1016 bytes::make_span(secureSecret),
1017 Core::ComputeSecureSecretHash(
1018 _cloudFields.newSecureSecretAlgo,
1019 bytes::make_span(newPasswordBytes)));
1020 }
1021 _setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
1022 check.result,
1023 MTP_account_passwordInputSettings(
1024 MTP_flags(flags),
1025 Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
1026 MTP_bytes(newPasswordHash.modpow),
1027 MTP_string(hint),
1028 MTPstring(), // email is not changing
1029 MTP_secureSecretSettings(
1030 Core::PrepareSecureSecretAlgo(_cloudFields.newSecureSecretAlgo),
1031 MTP_bytes(newSecureSecret),
1032 MTP_long(newSecureSecretId)))
1033 )).done([=](const MTPBool &result) {
1034 setPasswordDone(newPasswordBytes);
1035 }).fail([=](const MTP::Error &error) {
1036 setPasswordFail(newPasswordBytes, QString(), error);
1037 }).handleFloodErrors().send();
1038 }
1039
badOldPasscode()1040 void PasscodeBox::badOldPasscode() {
1041 _oldPasscode->selectAll();
1042 _oldPasscode->setFocus();
1043 _oldPasscode->showError();
1044 _oldError = _cloudPwd
1045 ? tr::lng_cloud_password_wrong(tr::now)
1046 : tr::lng_passcode_wrong(tr::now);
1047 if (_showRecoverLink && _hintText.isEmpty()) {
1048 _recover->hide();
1049 }
1050 update();
1051 }
1052
oldChanged()1053 void PasscodeBox::oldChanged() {
1054 if (!_oldError.isEmpty()) {
1055 _oldError = QString();
1056 if (_showRecoverLink && _hintText.isEmpty()) {
1057 _recover->show();
1058 }
1059 update();
1060 }
1061 }
1062
newChanged()1063 void PasscodeBox::newChanged() {
1064 if (!_newError.isEmpty()) {
1065 _newError = QString();
1066 update();
1067 }
1068 }
1069
emailChanged()1070 void PasscodeBox::emailChanged() {
1071 if (!_emailError.isEmpty()) {
1072 _emailError = QString();
1073 update();
1074 }
1075 }
1076
recoverByEmail()1077 void PasscodeBox::recoverByEmail() {
1078 if (!_cloudFields.hasRecovery) {
1079 Assert(_session != nullptr);
1080 const auto session = _session;
1081 const auto confirmBox = std::make_shared<QPointer<BoxContent>>();
1082 const auto reset = crl::guard(this, [=] {
1083 StartPendingReset(session, this, [=] {
1084 if (const auto box = *confirmBox) {
1085 box->closeBox();
1086 }
1087 });
1088 });
1089 *confirmBox = getDelegate()->show(Box<Ui::ConfirmBox>(
1090 tr::lng_cloud_password_reset_no_email(tr::now),
1091 tr::lng_cloud_password_reset_ok(tr::now),
1092 reset));
1093 } else if (_pattern.isEmpty()) {
1094 _pattern = "-";
1095 _api.request(MTPauth_RequestPasswordRecovery(
1096 )).done([=](const MTPauth_PasswordRecovery &result) {
1097 recoverStarted(result);
1098 }).fail([=](const MTP::Error &error) {
1099 recoverStartFail(error);
1100 }).send();
1101 } else {
1102 recover();
1103 }
1104 }
1105
recoverExpired()1106 void PasscodeBox::recoverExpired() {
1107 _pattern = QString();
1108 }
1109
recover()1110 void PasscodeBox::recover() {
1111 if (_pattern == "-" || !_session) {
1112 return;
1113 }
1114
1115 const auto weak = Ui::MakeWeak(this);
1116 const auto box = getDelegate()->show(Box<RecoverBox>(
1117 &_api.instance(),
1118 _session,
1119 _pattern,
1120 _cloudFields,
1121 [weak] { if (weak) { weak->closeBox(); } }));
1122
1123 box->newPasswordSet(
1124 ) | rpl::start_to_stream(_newPasswordSet, lifetime());
1125
1126 box->recoveryExpired(
1127 ) | rpl::start_with_next([=] {
1128 recoverExpired();
1129 }, lifetime());
1130
1131 _replacedBy = box;
1132 }
1133
recoverStarted(const MTPauth_PasswordRecovery & result)1134 void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) {
1135 _pattern = qs(result.c_auth_passwordRecovery().vemail_pattern());
1136 recover();
1137 }
1138
recoverStartFail(const MTP::Error & error)1139 void PasscodeBox::recoverStartFail(const MTP::Error &error) {
1140 _pattern = QString();
1141 closeBox();
1142 }
1143
RecoverBox(QWidget *,not_null<MTP::Instance * > mtp,Main::Session * session,const QString & pattern,const PasscodeBox::CloudFields & fields,Fn<void ()> closeParent)1144 RecoverBox::RecoverBox(
1145 QWidget*,
1146 not_null<MTP::Instance*> mtp,
1147 Main::Session *session,
1148 const QString &pattern,
1149 const PasscodeBox::CloudFields &fields,
1150 Fn<void()> closeParent)
1151 : _session(session)
1152 , _api(mtp)
1153 , _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5))
1154 , _cloudFields(fields)
1155 , _recoverCode(this, st::defaultInputField, tr::lng_signin_code())
1156 , _noEmailAccess(this, tr::lng_signin_try_password(tr::now))
1157 , _closeParent(std::move(closeParent)) {
1158 if (_cloudFields.pendingResetDate != 0 || !session) {
1159 _noEmailAccess.destroy();
1160 } else {
1161 _noEmailAccess->setClickedCallback([=] {
1162 const auto confirmBox = std::make_shared<QPointer<BoxContent>>();
1163 const auto reset = crl::guard(this, [=] {
1164 const auto closeParent = _closeParent;
1165 StartPendingReset(session, this, [=] {
1166 if (closeParent) {
1167 closeParent();
1168 }
1169 if (const auto box = *confirmBox) {
1170 box->closeBox();
1171 }
1172 });
1173 });
1174 *confirmBox = getDelegate()->show(Box<Ui::ConfirmBox>(
1175 tr::lng_cloud_password_reset_with_email(tr::now),
1176 tr::lng_cloud_password_reset_ok(tr::now),
1177 reset));
1178 });
1179 }
1180 }
1181
newPasswordSet() const1182 rpl::producer<QByteArray> RecoverBox::newPasswordSet() const {
1183 return _newPasswordSet.events();
1184 }
1185
recoveryExpired() const1186 rpl::producer<> RecoverBox::recoveryExpired() const {
1187 return _recoveryExpired.events();
1188 }
1189
prepare()1190 void RecoverBox::prepare() {
1191 setTitle(tr::lng_signin_recover_title());
1192
1193 addButton(tr::lng_passcode_submit(), [=] { submit(); });
1194 addButton(tr::lng_cancel(), [=] { closeBox(); });
1195
1196 setDimensions(
1197 st::boxWidth,
1198 (st::passcodePadding.top()
1199 + st::passcodePadding.bottom()
1200 + st::passcodeTextLine
1201 + _recoverCode->height()
1202 + st::passcodeTextLine));
1203
1204 connect(_recoverCode, &Ui::InputField::changed, [=] { codeChanged(); });
1205 connect(_recoverCode, &Ui::InputField::submitted, [=] { submit(); });
1206 }
1207
paintEvent(QPaintEvent * e)1208 void RecoverBox::paintEvent(QPaintEvent *e) {
1209 BoxContent::paintEvent(e);
1210
1211 Painter p(this);
1212
1213 p.setFont(st::normalFont);
1214 p.setPen(st::boxTextFg);
1215 int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
1216 p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left);
1217
1218 if (!_error.isEmpty()) {
1219 p.setPen(st::boxTextFgError);
1220 p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left);
1221 }
1222 }
1223
resizeEvent(QResizeEvent * e)1224 void RecoverBox::resizeEvent(QResizeEvent *e) {
1225 BoxContent::resizeEvent(e);
1226
1227 _recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height());
1228 _recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine);
1229 if (_noEmailAccess) {
1230 _noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2);
1231 }
1232 }
1233
setInnerFocus()1234 void RecoverBox::setInnerFocus() {
1235 _recoverCode->setFocusFast();
1236 }
1237
submit()1238 void RecoverBox::submit() {
1239 if (_submitRequest) return;
1240
1241 QString code = _recoverCode->getLastText().trimmed();
1242 if (code.isEmpty()) {
1243 _recoverCode->setFocus();
1244 _recoverCode->showError();
1245 return;
1246 }
1247
1248 const auto send = crl::guard(this, [=] {
1249 if (_cloudFields.turningOff) {
1250 // From "Disable cloud password".
1251 _submitRequest = _api.request(MTPauth_RecoverPassword(
1252 MTP_flags(0),
1253 MTP_string(code),
1254 MTPaccount_PasswordInputSettings()
1255 )).done([=](const MTPauth_Authorization &result) {
1256 proceedToClear();
1257 }).fail([=](const MTP::Error &error) {
1258 checkSubmitFail(error);
1259 }).handleFloodErrors().send();
1260 } else {
1261 // From "Change cloud password".
1262 _submitRequest = _api.request(MTPauth_CheckRecoveryPassword(
1263 MTP_string(code)
1264 )).done([=](const MTPBool &result) {
1265 proceedToChange(code);
1266 }).fail([=](const MTP::Error &error) {
1267 checkSubmitFail(error);
1268 }).handleFloodErrors().send();
1269 }
1270 });
1271 if (_cloudFields.notEmptyPassport) {
1272 const auto confirmed = [=](Fn<void()> &&close) {
1273 send();
1274 close();
1275 };
1276 getDelegate()->show(Box<Ui::ConfirmBox>(
1277 tr::lng_cloud_password_passport_losing(tr::now),
1278 tr::lng_continue(tr::now),
1279 confirmed));
1280 } else {
1281 send();
1282 }
1283 }
1284
setError(const QString & error)1285 void RecoverBox::setError(const QString &error) {
1286 _error = error;
1287 if (_noEmailAccess) {
1288 _noEmailAccess->setVisible(error.isEmpty());
1289 }
1290 update();
1291 }
1292
codeChanged()1293 void RecoverBox::codeChanged() {
1294 setError(QString());
1295 }
1296
proceedToClear()1297 void RecoverBox::proceedToClear() {
1298 _submitRequest = 0;
1299 _newPasswordSet.fire({});
1300 getDelegate()->show(
1301 Box<Ui::InformBox>(tr::lng_cloud_password_removed(tr::now)),
1302 Ui::LayerOption::CloseOther);
1303 }
1304
proceedToChange(const QString & code)1305 void RecoverBox::proceedToChange(const QString &code) {
1306 Expects(!_cloudFields.turningOff);
1307 _submitRequest = 0;
1308
1309 auto fields = _cloudFields;
1310 fields.fromRecoveryCode = code;
1311 fields.hasRecovery = false;
1312 // we could've been turning off, no need to force new password then
1313 // like if (_cloudFields.turningOff) { just RecoverPassword else Check }
1314 fields.curRequest = {};
1315 auto box = Box<PasscodeBox>(_session, fields);
1316
1317 box->boxClosing(
1318 ) | rpl::start_with_next([=] {
1319 const auto weak = Ui::MakeWeak(this);
1320 if (const auto onstack = _closeParent) {
1321 onstack();
1322 }
1323 if (weak) {
1324 weak->closeBox();
1325 }
1326 }, lifetime());
1327
1328 box->newPasswordSet(
1329 ) | rpl::start_with_next([=](QByteArray &&password) {
1330 _newPasswordSet.fire(std::move(password));
1331 }, lifetime());
1332
1333 getDelegate()->show(std::move(box));
1334 }
1335
checkSubmitFail(const MTP::Error & error)1336 void RecoverBox::checkSubmitFail(const MTP::Error &error) {
1337 if (MTP::IsFloodError(error)) {
1338 _submitRequest = 0;
1339 setError(tr::lng_flood_error(tr::now));
1340 _recoverCode->showError();
1341 return;
1342 }
1343 _submitRequest = 0;
1344
1345 const QString &err = error.type();
1346 if (err == qstr("PASSWORD_EMPTY")) {
1347 _newPasswordSet.fire(QByteArray());
1348 getDelegate()->show(
1349 Box<Ui::InformBox>(tr::lng_cloud_password_removed(tr::now)),
1350 Ui::LayerOption::CloseOther);
1351 } else if (err == qstr("PASSWORD_RECOVERY_NA")) {
1352 closeBox();
1353 } else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) {
1354 _recoveryExpired.fire({});
1355 closeBox();
1356 } else if (err == qstr("CODE_INVALID")) {
1357 setError(tr::lng_signin_wrong_code(tr::now));
1358 _recoverCode->selectAll();
1359 _recoverCode->setFocus();
1360 _recoverCode->showError();
1361 } else {
1362 setError(Logs::DebugEnabled() // internal server error
1363 ? (err + ": " + error.description())
1364 : Lang::Hard::ServerError());
1365 _recoverCode->setFocus();
1366 }
1367 }
1368
ConfirmRecoveryEmail(not_null<Main::Session * > session,const QString & pattern)1369 RecoveryEmailValidation ConfirmRecoveryEmail(
1370 not_null<Main::Session*> session,
1371 const QString &pattern) {
1372 const auto errors = std::make_shared<rpl::event_stream<QString>>();
1373 const auto resent = std::make_shared<rpl::event_stream<QString>>();
1374 const auto requestId = std::make_shared<mtpRequestId>(0);
1375 const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
1376 const auto reloads = std::make_shared<rpl::event_stream<>>();
1377 const auto cancels = std::make_shared<rpl::event_stream<>>();
1378
1379 const auto submit = [=](QString code) {
1380 if (*requestId) {
1381 return;
1382 }
1383 *requestId = session->api().request(MTPaccount_ConfirmPasswordEmail(
1384 MTP_string(code)
1385 )).done([=](const MTPBool &result) {
1386 *requestId = 0;
1387 reloads->fire({});
1388 if (*weak) {
1389 (*weak)->getDelegate()->show(
1390 Box<Ui::InformBox>(
1391 tr::lng_cloud_password_was_set(tr::now)),
1392 Ui::LayerOption::CloseOther);
1393 }
1394 }).fail([=](const MTP::Error &error) {
1395 *requestId = 0;
1396 if (MTP::IsFloodError(error)) {
1397 errors->fire(tr::lng_flood_error(tr::now));
1398 } else if (error.type() == qstr("CODE_INVALID")) {
1399 errors->fire(tr::lng_signin_wrong_code(tr::now));
1400 } else if (error.type() == qstr("EMAIL_HASH_EXPIRED")) {
1401 cancels->fire({});
1402 if (*weak) {
1403 auto box = Box<Ui::InformBox>(
1404 Lang::Hard::EmailConfirmationExpired());
1405 (*weak)->getDelegate()->show(
1406 std::move(box),
1407 Ui::LayerOption::CloseOther);
1408 }
1409 } else {
1410 errors->fire(Lang::Hard::ServerError());
1411 }
1412 }).handleFloodErrors().send();
1413 };
1414 const auto resend = [=] {
1415 if (*requestId) {
1416 return;
1417 }
1418 *requestId = session->api().request(MTPaccount_ResendPasswordEmail(
1419 )).done([=](const MTPBool &result) {
1420 *requestId = 0;
1421 resent->fire(tr::lng_cloud_password_resent(tr::now));
1422 }).fail([=](const MTP::Error &error) {
1423 *requestId = 0;
1424 errors->fire(Lang::Hard::ServerError());
1425 }).send();
1426 };
1427
1428 auto box = Passport::VerifyEmailBox(
1429 pattern,
1430 0,
1431 submit,
1432 resend,
1433 errors->events(),
1434 resent->events());
1435
1436 *weak = box.data();
1437 return { std::move(box), reloads->events(), cancels->events() };
1438 }
1439
PrePasswordErrorBox(const MTP::Error & error,not_null<Main::Session * > session,TextWithEntities && about)1440 [[nodiscard]] object_ptr<Ui::GenericBox> PrePasswordErrorBox(
1441 const MTP::Error &error,
1442 not_null<Main::Session*> session,
1443 TextWithEntities &&about) {
1444 const auto type = [&] {
1445 const auto &type = error.type();
1446 if (type == qstr("PASSWORD_MISSING")) {
1447 return PasswordErrorType::NoPassword;
1448 } else if (type.startsWith(qstr("PASSWORD_TOO_FRESH_"))
1449 || type.startsWith(qstr("SESSION_TOO_FRESH_"))) {
1450 return PasswordErrorType::Later;
1451 }
1452 return PasswordErrorType::None;
1453 }();
1454 if (type == PasswordErrorType::None) {
1455 return nullptr;
1456 }
1457
1458 return Box(
1459 TransferPasswordError,
1460 session,
1461 std::move(about),
1462 type);
1463 }
1464