From ddb4527159ca7205e0ddd7b8863f329daa37beca Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Sun, 18 Mar 2018 12:51:14 +0400
Subject: [PATCH] Start passport support: handle link, receive form.

---
 Telegram/Resources/scheme.tl                  |  45 +++-
 Telegram/SourceFiles/apiwrap.cpp              |   6 -
 Telegram/SourceFiles/boxes/passcode_box.cpp   |  15 +-
 Telegram/SourceFiles/messenger.cpp            |  19 ++
 Telegram/SourceFiles/mtproto/auth_key.cpp     |   2 -
 .../passport/passport_form_controller.cpp     | 216 ++++++++++++++++++
 .../passport/passport_form_controller.h       | 103 +++++++++
 .../platform/linux/specific_linux.cpp         |  60 ++---
 .../SourceFiles/platform/mac/specific_mac.mm  |   9 +-
 .../SourceFiles/platform/mac/specific_mac_p.h |   2 -
 .../platform/mac/specific_mac_p.mm            |   7 -
 .../SourceFiles/platform/platform_specific.h  |   1 +
 .../SourceFiles/platform/win/specific_win.cpp |   6 +-
 .../settings/settings_privacy_widget.cpp      |  15 +-
 .../SourceFiles/settings/settings_widget.cpp  |   6 +
 .../SourceFiles/window/window_controller.cpp  |   6 +
 .../SourceFiles/window/window_controller.h    |   9 +
 Telegram/gyp/telegram_sources.txt             |   2 +
 18 files changed, 471 insertions(+), 58 deletions(-)
 create mode 100644 Telegram/SourceFiles/passport/passport_form_controller.cpp
 create mode 100644 Telegram/SourceFiles/passport/passport_form_controller.h

diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl
index 34fa8d3f7..d71a1c1b0 100644
--- a/Telegram/Resources/scheme.tl
+++ b/Telegram/Resources/scheme.tl
@@ -191,6 +191,7 @@ inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto;
 inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation;
 inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
 inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation;
+inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
 
 inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent;
 
@@ -583,12 +584,12 @@ authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string s
 
 account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
 
-account.noPassword#96dabc18 new_salt:bytes email_unconfirmed_pattern:string = account.Password;
-account.password#7c18141c current_salt:bytes new_salt:bytes hint:string has_recovery:Bool email_unconfirmed_pattern:string = account.Password;
+account.noPassword#c86371bd new_salt:bytes secret_random:bytes email_unconfirmed_pattern:string = account.Password;
+account.password#49efccf5 current_salt:bytes new_salt:bytes secret_random:bytes hint:string has_recovery:Bool email_unconfirmed_pattern:string = account.Password;
 
-account.passwordSettings#b7b72ab3 email:string = account.PasswordSettings;
+account.passwordSettings#f8ebfc3f email:string secure_secret:bytes = account.PasswordSettings;
 
-account.passwordInputSettings#86916deb flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string = account.PasswordInputSettings;
+account.passwordInputSettings#20ce9e59 flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_secret:flags.2?bytes = account.PasswordInputSettings;
 
 auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
 
@@ -964,6 +965,37 @@ help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vect
 help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
 help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate;
 
+inputSecureFileUploaded#c53ed020 id:long parts:int md5_checksum:string file_hash:string = InputSecureFile;
+inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;
+
+secureFileEmpty#64199744 = SecureFile;
+secureFile#40ad69ba id:long access_hash:long size:int dc_id:int file_hash:string = SecureFile;
+
+secureValueEmpty#f0cec6e0 name:string = SecureValue;
+secureValueData#6c3bf275 name:string data:bytes hash:string secret:bytes = SecureValue;
+secureValueFile#a145a158 name:string file:Vector<SecureFile> hash:string secret:bytes = SecureValue;
+secureValueText#1bb37ce3 name:string text:string hash:string = SecureValue;
+
+inputSecureValueData#627bcf0d name:string data:bytes hash:string secret:bytes = InputSecureValue;
+inputSecureValueFile#e24f9466 name:string file:Vector<InputSecureFile> hash:string secret:bytes = InputSecureValue;
+inputSecureValueText#955a14bb name:string text:string hash:string = InputSecureValue;
+
+secureValueHash#e4c2677 name:string hash:string = SecureValueHash;
+
+authFieldTypeIdentity#4413b5d = AuthFieldType;
+authFieldTypeAddress#63bbccf3 = AuthFieldType;
+authFieldTypePhone#b56120ff = AuthFieldType;
+authFieldTypeEmail#4cc9fd06 = AuthFieldType;
+
+authField#ed81c3dc flags:# type:AuthFieldType data:SecureValue document:flags.0?SecureValue = AuthField;
+
+account.secureValueResultSaved#f4e0ab11 = account.SecureValueResult;
+account.secureValueVerificationNeeded#9192a1db code_hash:string = account.SecureValueResult;
+
+account.authorizationForm#40997b71 flags:# accepted:flags.0?true request_write:flags.1?true bot_id:int fields:Vector<AuthField> accepted_fields:Vector<AuthFieldType> users:Vector<User> = account.AuthorizationForm;
+
+account.authorizationResult#836727ce user_data:DataJSON accepted_fields:Vector<AuthFieldType> = account.AuthorizationResult;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1019,6 +1051,11 @@ account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPass
 account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
 account.resetWebAuthorization#2d01b9ef hash:long = Bool;
 account.resetWebAuthorizations#682d2594 = Bool;
+account.getSecureValue#d86fa683 user_id:InputUser name:Vector<string> = Vector<SecureValue>;
+account.saveSecureValue#9fc2a624 data:InputSecureValue = account.SecureValueResult;
+account.verifySecureValue#68367bed name:string code_hash:string code:string = Bool;
+account.getAuthorizationForm#2f003837 flags:# bot_id:int scope:Vector<string> origin:flags.0?string package_name:flags.1?string bundle_id:flags.2?string public_key:flags.3?string = account.AuthorizationForm;
+account.acceptAuthorization#df98a3f5 flags:# request_write:flags.4?true bot_id:int scope:Vector<string> origin:flags.0?string package_name:flags.1?string bundle_id:flags.2?string public_key:flags.3?string accepted_fields:Vector<AuthFieldType> value_hashes:Vector<secureValueHash> = account.AuthorizationResult;
 
 users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
 users.getFullUser#ca30a5b1 id:InputUser = UserFull;
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index eb6b146d7..8c1ee6db3 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -2397,12 +2397,6 @@ std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji(
 			auto &entry = _stickersByEmoji[emoji];
 			entry.list.clear();
 			entry.list.reserve(data.vstickers.v.size());
-			for (const auto &sticker : data.vstickers.v) {
-				const auto document = _session->data().document(sticker);
-				if (document->sticker()) {
-					entry.list.push_back(document);
-				}
-			}
 			entry.hash = data.vhash.v;
 			entry.received = getms(true);
 			_session->data().notifyStickersUpdated();
diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp
index f8df692a1..c3334083c 100644
--- a/Telegram/SourceFiles/boxes/passcode_box.cpp
+++ b/Telegram/SourceFiles/boxes/passcode_box.cpp
@@ -327,8 +327,19 @@ void PasscodeBox::save(bool force) {
 			if (_oldPasscode->isHidden() || _newPasscode->isHidden()) {
 				flags |= MTPDaccount_passwordInputSettings::Flag::f_email;
 			}
-			MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(flags), MTP_bytes(_newSalt), MTP_bytes(newPasswordHash), MTP_string(hint), MTP_string(email)));
-			_setRequest = MTP::send(MTPaccount_UpdatePasswordSettings(MTP_bytes(oldPasswordHash), settings), rpcDone(&PasscodeBox::setPasswordDone), rpcFail(&PasscodeBox::setPasswordFail));
+			const auto newSecureSecret = base::byte_vector();
+			_setRequest = MTP::send(
+				MTPaccount_UpdatePasswordSettings(
+					MTP_bytes(oldPasswordHash),
+					MTP_account_passwordInputSettings(
+						MTP_flags(flags),
+						MTP_bytes(_newSalt),
+						MTP_bytes(newPasswordHash),
+						MTP_string(hint),
+						MTP_string(email),
+						MTP_bytes(newSecureSecret))),
+				rpcDone(&PasscodeBox::setPasswordDone),
+				rpcFail(&PasscodeBox::setPasswordFail));
 		}
 	} else {
 		cSetPasscodeBadTries(0);
diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp
index e0d066310..079a5fc74 100644
--- a/Telegram/SourceFiles/messenger.cpp
+++ b/Telegram/SourceFiles/messenger.cpp
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_cloud_manager.h"
 #include "lang/lang_hardcoded.h"
 #include "core/update_checker.h"
+#include "passport/passport_form_controller.h"
 #include "observer_peer.h"
 #include "storage/file_upload.h"
 #include "mainwidget.h"
@@ -907,6 +908,24 @@ bool Messenger::openLocalUrl(const QString &url) {
 		auto params = url_parse_params(proxyMatch->captured(1), UrlParamNameTransform::ToLower);
 		ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Mtproto, params);
 		return true;
+	} else if (auto authMatch = regex_match(qsl("^auth/?\\?(.+)(#|$)"), command, matchOptions)) {
+		const auto params = url_parse_params(
+			authMatch->captured(1),
+			UrlParamNameTransform::ToLower);
+		if (const auto botId = params.value("bot_id", QString()).toInt()) {
+			const auto scope = params.value("scope", QString()).split(',');
+			const auto callback = params.value("callback_url", QString());
+			const auto publicKey = params.value("public_key", QString());
+			if (const auto window = App::wnd()) {
+				if (const auto controller = window->controller()) {
+					controller->showAuthForm(Passport::FormRequest(
+						botId,
+						scope,
+						callback,
+						publicKey));
+				}
+			}
+		}
 	}
 	return false;
 }
diff --git a/Telegram/SourceFiles/mtproto/auth_key.cpp b/Telegram/SourceFiles/mtproto/auth_key.cpp
index c394d51cf..85ec5d2b2 100644
--- a/Telegram/SourceFiles/mtproto/auth_key.cpp
+++ b/Telegram/SourceFiles/mtproto/auth_key.cpp
@@ -8,9 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mtproto/auth_key.h"
 
 #include <openssl/aes.h>
-extern "C" {
 #include <openssl/modes.h>
-}
 
 namespace MTP {
 
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp
new file mode 100644
index 000000000..528508b4a
--- /dev/null
+++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp
@@ -0,0 +1,216 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "passport/passport_form_controller.h"
+
+#include "boxes/confirm_box.h"
+#include "lang/lang_keys.h"
+#include "mainwindow.h"
+
+namespace Passport {
+
+FormRequest::FormRequest(
+	UserId botId,
+	const QStringList &scope,
+	const QString &callbackUrl,
+	const QString &publicKey)
+: botId(botId)
+, scope(scope)
+, callbackUrl(callbackUrl)
+, publicKey(publicKey) {
+}
+
+
+FormController::Field::Field(Type type) : type(type) {
+}
+
+FormController::FormController(
+	not_null<Window::Controller*> controller,
+	const FormRequest &request)
+: _controller(controller)
+, _request(request) {
+	const auto url = QUrl(_request.callbackUrl);
+	_origin = url.scheme() + "://" + url.host();
+}
+
+void FormController::show() {
+	requestForm();
+	requestPassword();
+}
+
+void FormController::requestForm() {
+	auto scope = QVector<MTPstring>();
+	scope.reserve(_request.scope.size());
+	for (const auto &element : _request.scope) {
+		scope.push_back(MTP_string(element));
+	}
+	auto normalizedKey = _request.publicKey;
+	normalizedKey.replace("\r\n", "\n");
+	const auto bytes = normalizedKey.toUtf8();
+	request(MTPaccount_GetAuthorizationForm(
+		MTP_flags(MTPaccount_GetAuthorizationForm::Flag::f_origin
+			| MTPaccount_GetAuthorizationForm::Flag::f_public_key),
+		MTP_int(_request.botId),
+		MTP_vector<MTPstring>(std::move(scope)),
+		MTP_string(_origin),
+		MTPstring(), // package_name
+		MTPstring(), // bundle_id
+		MTP_bytes(bytes)
+	)).done([=](const MTPaccount_AuthorizationForm &result) {
+		formDone(result);
+	}).fail([=](const RPCError &error) {
+		formFail(error);
+	}).send();
+}
+
+auto FormController::convertValue(
+		const MTPSecureValue &value) const -> Value {
+	auto result = Value();
+	switch (value.type()) {
+	case mtpc_secureValueEmpty: {
+		const auto &data = value.c_secureValueEmpty();
+		result.name = qs(data.vname);
+	} break;
+	case mtpc_secureValueData: {
+		const auto &data = value.c_secureValueData();
+		result.name = qs(data.vname);
+		result.data = data.vdata.v;
+		result.dataHash = data.vhash.v;
+		result.dataSecret = data.vsecret.v;
+	} break;
+	case mtpc_secureValueText: {
+		const auto &data = value.c_secureValueText();
+		result.name = qs(data.vname);
+		result.text = qs(data.vtext);
+		result.textHash = data.vhash.v;
+	} break;
+	case mtpc_secureValueFile: {
+		const auto &data = value.c_secureValueFile();
+		result.name = qs(data.vname);
+		result.filesHash = data.vhash.v;
+		result.filesSecret = data.vsecret.v;
+		for (const auto &file : data.vfile.v) {
+			switch (file.type()) {
+			case mtpc_secureFileEmpty: {
+				result.files.push_back(File());
+			} break;
+			case mtpc_secureFile: {
+				const auto &fields = file.c_secureFile();
+				auto normal = File();
+				normal.id = fields.vid.v;
+				normal.accessHash = fields.vaccess_hash.v;
+				normal.size = fields.vsize.v;
+				normal.dcId = fields.vdc_id.v;
+				normal.fileHash = qba(fields.vfile_hash);
+				result.files.push_back(std::move(normal));
+			} break;
+			}
+		}
+	} break;
+	}
+	return result;
+}
+
+void FormController::formDone(const MTPaccount_AuthorizationForm &result) {
+	parseForm(result);
+	showForm();
+}
+
+void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
+	Expects(result.type() == mtpc_account_authorizationForm);
+
+	const auto &data = result.c_account_authorizationForm();
+	_form.requestWrite = data.is_request_write();
+	if (_request.botId != data.vbot_id.v) {
+		LOG(("API Error: Wrong account.authorizationForm.bot_id."));
+		_request.botId = data.vbot_id.v;
+	}
+	App::feedUsers(data.vusers);
+	for (const auto &field : data.vfields.v) {
+		Assert(field.type() == mtpc_authField);
+
+		using Type = Field::Type;
+
+		const auto &data = field.c_authField();
+		const auto type = [&] {
+			switch (data.vtype.type()) {
+			case mtpc_authFieldTypeIdentity: return Type::Identity;
+			case mtpc_authFieldTypeAddress: return Type::Address;
+			case mtpc_authFieldTypeEmail: return Type::Email;
+			case mtpc_authFieldTypePhone: return Type::Phone;
+			}
+			Unexpected("Type in authField.type.");
+		}();
+		auto entry = Field(type);
+		entry.data = convertValue(data.vdata);
+		if (data.has_document()) {
+			entry.document = convertValue(data.vdocument);
+		}
+		_form.fields.push_back(std::move(entry));
+	}
+	_bot = App::userLoaded(_request.botId);
+}
+
+void FormController::showForm() {
+	if (!_bot) {
+		Ui::show(Box<InformBox>("Could not get authorization bot."));
+		return;
+	}
+	auto text = QString("Auth request:\n");
+	for (const auto &field : _form.fields) {
+		switch (field.type) {
+		case Field::Type::Identity: text += "- identity\n"; break;
+		case Field::Type::Address: text += "- address\n"; break;
+		case Field::Type::Email: text += "- email\n"; break;
+		case Field::Type::Phone: text += "- phone\n"; break;
+		}
+	}
+	if (_form.requestWrite) {
+		text += "and bot @" + _bot->username + " requests write permission.";
+	} else {
+		text += "and bot @" + _bot->username + " does not request write permission.";
+	}
+	Ui::show(Box<InformBox>(text));
+//	Ui::show(Box<FormBox>(this));
+}
+
+void FormController::formFail(const RPCError &error) {
+	// #TODO langs
+	Ui::show(Box<InformBox>("Could not get authorization form."));
+}
+
+void FormController::requestPassword() {
+	request(MTPaccount_GetPassword(
+	)).done([=](const MTPaccount_Password &result) {
+		passwordDone(result);
+	}).send();
+}
+
+void FormController::passwordDone(const MTPaccount_Password &result) {
+	switch (result.type()) {
+	case mtpc_account_noPassword:
+		createPassword(result.c_account_noPassword());
+		break;
+
+	case mtpc_account_password:
+		checkPassword(result.c_account_password());
+		break;
+	}
+}
+
+void FormController::passwordFail(const RPCError &error) {
+	Ui::show(Box<InformBox>("Could not get authorization form."));
+}
+
+void FormController::createPassword(const MTPDaccount_noPassword &result) {
+	Ui::show(Box<InformBox>("You need 2fa password!")); // #TODO
+}
+
+void FormController::checkPassword(const MTPDaccount_password &result) {
+}
+
+} // namespace Passport
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h
new file mode 100644
index 000000000..257c86699
--- /dev/null
+++ b/Telegram/SourceFiles/passport/passport_form_controller.h
@@ -0,0 +1,103 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "mtproto/sender.h"
+
+namespace Window {
+class Controller;
+} // namespace Window
+
+namespace Passport {
+
+struct FormRequest {
+	FormRequest(
+		UserId botId,
+		const QStringList &scope,
+		const QString &callbackUrl,
+		const QString &publicKey);
+
+	UserId botId;
+	QStringList scope;
+	QString callbackUrl;
+	QString publicKey;
+
+};
+
+class FormController : private MTP::Sender {
+public:
+	FormController(
+		not_null<Window::Controller*> controller,
+		const FormRequest &request);
+
+	void show();
+
+private:
+	struct File {
+		uint64 id = 0;
+		uint64 accessHash = 0;
+		int32 size = 0;
+		int32 dcId = 0;
+		QByteArray fileHash;
+	};
+	struct Value {
+		QString name;
+
+		QByteArray data;
+		QByteArray dataHash;
+		QByteArray dataSecret;
+
+		QString text;
+		QByteArray textHash;
+
+		std::vector<File> files;
+		QByteArray filesHash;
+		QByteArray filesSecret;
+	};
+	struct Field {
+		enum class Type {
+			Identity,
+			Address,
+			Phone,
+			Email,
+		};
+		explicit Field(Type type);
+
+		Type type;
+		Value data;
+		base::optional<Value> document;
+	};
+	struct Form {
+		bool requestWrite = false;
+		std::vector<Field> fields;
+	};
+	Value convertValue(const MTPSecureValue &value) const;
+
+	void requestForm();
+	void requestPassword();
+
+	void formDone(const MTPaccount_AuthorizationForm &result);
+	void formFail(const RPCError &error);
+	void parseForm(const MTPaccount_AuthorizationForm &result);
+	void showForm();
+
+	void passwordDone(const MTPaccount_Password &result);
+	void passwordFail(const RPCError &error);
+	void createPassword(const MTPDaccount_noPassword &settings);
+	void checkPassword(const MTPDaccount_password &settings);
+
+	not_null<Window::Controller*> _controller;
+	FormRequest _request;
+	UserData *_bot = nullptr;
+	QString _origin;
+
+	Form _form;
+
+};
+
+} // namespace Passport
diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
index 9046aeb35..8122b7f14 100644
--- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp
+++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
@@ -31,6 +31,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 using namespace Platform;
 using Platform::File::internal::EscapeShell;
 
+namespace {
+
+bool _psRunCommand(const QByteArray &command) {
+        auto result = system(command.constData());
+        if (result) {
+                DEBUG_LOG(("App Error: command failed, code: %1, command (in utf8): %2").arg(result).arg(command.constData()));
+                return false;
+        }
+        DEBUG_LOG(("App Info: command succeeded, command (in utf8): %1").arg(command.constData()));
+        return true;
+}
+
+} // namespace
+
 namespace Platform {
 
 QString CurrentExecutablePath(int argc, char *argv[]) {
@@ -389,35 +403,7 @@ QString SystemLanguage() {
 	return QString();
 }
 
-namespace ThirdParty {
-
-void start() {
-	Libs::start();
-	MainWindow::LibsLoaded();
-}
-
-void finish() {
-}
-
-} // namespace ThirdParty
-
-} // namespace Platform
-
-namespace {
-
-bool _psRunCommand(const QByteArray &command) {
-	auto result = system(command.constData());
-	if (result) {
-		DEBUG_LOG(("App Error: command failed, code: %1, command (in utf8): %2").arg(result).arg(command.constData()));
-		return false;
-	}
-	DEBUG_LOG(("App Info: command succeeded, command (in utf8): %1").arg(command.constData()));
-	return true;
-}
-
-} // namespace
-
-void psRegisterCustomScheme() {
+void RegisterCustomScheme() {
 #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
 	auto home = getHomeDir();
 	if (home.isEmpty() || cBetaVersion() || cExeName().isEmpty()) return; // don't update desktop file for beta version
@@ -517,8 +503,22 @@ void psRegisterCustomScheme() {
 #endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
 }
 
+namespace ThirdParty {
+
+void start() {
+	Libs::start();
+	MainWindow::LibsLoaded();
+}
+
+void finish() {
+}
+
+} // namespace ThirdParty
+
+} // namespace Platform
+
 void psNewVersion() {
-	psRegisterCustomScheme();
+	Platform::RegisterCustomScheme();
 }
 
 bool psShowOpenWithMenu(int x, int y, const QString &file) {
diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm
index a180429a5..c549f061f 100644
--- a/Telegram/SourceFiles/platform/mac/specific_mac.mm
+++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm
@@ -398,10 +398,17 @@ QString CurrentExecutablePath(int argc, char *argv[]) {
 	return NS2QString([[NSBundle mainBundle] bundlePath]);
 }
 
+void RegisterCustomScheme() {
+#ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
+	OSStatus result = LSSetDefaultHandlerForURLScheme(CFSTR("tg"), (CFStringRef)[[NSBundle mainBundle] bundleIdentifier]);
+	DEBUG_LOG(("App Info: set default handler for 'tg' scheme result: %1").arg(result));
+#endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
+}
+
 } // namespace Platform
 
 void psNewVersion() {
-	objc_registerCustomScheme();
+	Platform::RegisterCustomScheme();
 }
 
 void psAutoStart(bool start, bool silent) {
diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.h b/Telegram/SourceFiles/platform/mac/specific_mac_p.h
index afb5090f3..ccf7dc0c4 100644
--- a/Telegram/SourceFiles/platform/mac/specific_mac_p.h
+++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.h
@@ -23,8 +23,6 @@ void objc_start();
 void objc_ignoreApplicationActivationRightNow();
 void objc_finish();
 
-void objc_registerCustomScheme();
-
 void objc_activateProgram(WId winId);
 bool objc_moveFile(const QString &from, const QString &to);
 void objc_deleteDir(const QString &dir);
diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm
index f037935f5..569167088 100644
--- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm
+++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm
@@ -394,13 +394,6 @@ void objc_finish() {
 	}
 }
 
-void objc_registerCustomScheme() {
-#ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
-	OSStatus result = LSSetDefaultHandlerForURLScheme(CFSTR("tg"), (CFStringRef)[[NSBundle mainBundle] bundleIdentifier]);
-	DEBUG_LOG(("App Info: set default handler for 'tg' scheme result: %1").arg(result));
-#endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
-}
-
 void objc_activateProgram(WId winId) {
 	[NSApp activateIgnoringOtherApps:YES];
 	if (winId) {
diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h
index c2b166441..bd3b2f9a0 100644
--- a/Telegram/SourceFiles/platform/platform_specific.h
+++ b/Telegram/SourceFiles/platform/platform_specific.h
@@ -18,6 +18,7 @@ void StartTranslucentPaint(QPainter &p, QPaintEvent *e);
 void InitOnTopPanel(QWidget *panel);
 void DeInitOnTopPanel(QWidget *panel);
 void ReInitOnTopPanel(QWidget *panel);
+void RegisterCustomScheme();
 
 QString SystemLanguage();
 QString SystemCountry();
diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp
index 5c00d2d96..e49048f6e 100644
--- a/Telegram/SourceFiles/platform/win/specific_win.cpp
+++ b/Telegram/SourceFiles/platform/win/specific_win.cpp
@@ -576,6 +576,8 @@ namespace {
 	}
 }
 
+namespace Platform {
+
 void RegisterCustomScheme() {
 	if (cExeName().isEmpty()) {
 		return;
@@ -621,8 +623,10 @@ void RegisterCustomScheme() {
 #endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
 }
 
+} // namespace Platform
+
 void psNewVersion() {
-	RegisterCustomScheme();
+	Platform::RegisterCustomScheme();
 	if (Local::oldSettingsVersion() < 8051) {
 		AppUserModelId::checkPinned();
 	}
diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp
index bdbc51734..e5c052758 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp
@@ -81,9 +81,18 @@ void CloudPasswordState::onTurnOff() {
 	if (_curPasswordSalt.isEmpty()) {
 		_turnOff->hide();
 
-		auto flags = MTPDaccount_passwordInputSettings::Flag::f_email;
-		MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(flags), MTP_bytes(QByteArray()), MTP_bytes(QByteArray()), MTP_string(QString()), MTP_string(QString())));
-		MTP::send(MTPaccount_UpdatePasswordSettings(MTP_bytes(QByteArray()), settings), rpcDone(&CloudPasswordState::offPasswordDone), rpcFail(&CloudPasswordState::offPasswordFail));
+		MTP::send(
+			MTPaccount_UpdatePasswordSettings(
+				MTP_bytes(QByteArray()),
+				MTP_account_passwordInputSettings(
+			MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
+			MTP_bytes(QByteArray()),
+			MTP_bytes(QByteArray()),
+			MTP_string(QString()),
+			MTP_string(QString()),
+			MTP_bytes(QByteArray()))),
+			rpcDone(&CloudPasswordState::offPasswordDone),
+			rpcFail(&CloudPasswordState::offPasswordFail));
 	} else {
 		auto box = Ui::show(Box<PasscodeBox>(_newPasswordSalt, _curPasswordSalt, _hasPasswordRecovery, _curPasswordHint, true));
 		connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword()));
diff --git a/Telegram/SourceFiles/settings/settings_widget.cpp b/Telegram/SourceFiles/settings/settings_widget.cpp
index 77856463d..136dab07c 100644
--- a/Telegram/SourceFiles/settings/settings_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_widget.cpp
@@ -12,8 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/style_settings.h"
 #include "styles/style_window.h"
 #include "styles/style_boxes.h"
+#include "platform/platform_specific.h"
 #include "ui/widgets/scroll_area.h"
 #include "ui/widgets/buttons.h"
+#include "ui/toast/toast.h"
 #include "mainwindow.h"
 #include "mainwidget.h"
 #include "storage/localstorage.h"
@@ -114,6 +116,10 @@ void fillCodes() {
 			}
 		});
 	});
+	Codes.insert(qsl("registertg"), [] {
+		Platform::RegisterCustomScheme();
+		Ui::Toast::Show("Forced custom scheme register.");
+	});
 
 	auto audioFilters = qsl("Audio files (*.wav *.mp3);;") + FileDialog::AllFilesFilter();
 	auto audioKeys = {
diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp
index 43951c2f2..c06e5bcb9 100644
--- a/Telegram/SourceFiles/window/window_controller.cpp
+++ b/Telegram/SourceFiles/window/window_controller.cpp
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "media/player/media_player_round_controller.h"
 #include "data/data_session.h"
 #include "data/data_feed.h"
+#include "passport/passport_form_controller.h"
 #include "boxes/calendar_box.h"
 #include "mainwidget.h"
 #include "mainwindow.h"
@@ -404,6 +405,11 @@ void Controller::showJumpToDate(Dialogs::Key chat, QDate requestedDate) {
 	Ui::show(std::move(box));
 }
 
+void Controller::showAuthForm(const Passport::FormRequest &request) {
+	_authForm = std::make_unique<Passport::FormController>(this, request);
+	_authForm->show();
+}
+
 void Controller::updateColumnLayout() {
 	App::main()->updateColumnLayout();
 }
diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h
index 003361197..c33e83430 100644
--- a/Telegram/SourceFiles/window/window_controller.h
+++ b/Telegram/SourceFiles/window/window_controller.h
@@ -21,6 +21,11 @@ class RoundController;
 } // namespace Player
 } // namespace Media
 
+namespace Passport {
+struct FormRequest;
+class FormController;
+} // namespace Passport
+
 namespace Window {
 
 class LayerWidget;
@@ -200,6 +205,8 @@ public:
 		Dialogs::Key chat,
 		QDate requestedDate);
 
+	void showAuthForm(const Passport::FormRequest &request);
+
 	base::Variable<bool> &dialogsListFocused() {
 		return _dialogsListFocused;
 	}
@@ -246,6 +253,8 @@ private:
 
 	not_null<MainWindow*> _window;
 
+	std::unique_ptr<Passport::FormController> _authForm;
+
 	GifPauseReasons _gifPauseReasons = 0;
 	base::Observable<void> _gifPauseLevelChanged;
 	base::Observable<void> _floatPlayerAreaUpdated;
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index f60aba58b..9a404647d 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -454,6 +454,8 @@
 <(src_loc)/mtproto/type_utils.h
 <(src_loc)/overview/overview_layout.cpp
 <(src_loc)/overview/overview_layout.h
+<(src_loc)/passport/passport_form_controller.cpp
+<(src_loc)/passport/passport_form_controller.h
 <(src_loc)/platform/linux/linux_desktop_environment.cpp
 <(src_loc)/platform/linux/linux_desktop_environment.h
 <(src_loc)/platform/linux/linux_gdk_helper.cpp