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 &current) {
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