diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 91bf1c4bf8..232bc877c5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1521,6 +1521,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_email_title" = "Email"; "lng_passport_email_description" = "Enter your email address"; "lng_passport_accept_allow" = "You accept {policy} and allow their {bot} to send messages to you."; +"lng_passport_allow" = "You allow {bot} to send messages to you."; "lng_passport_policy" = "{bot} privacy policy"; "lng_passport_authorize" = "Authorize"; "lng_passport_form_error" = "Could not get authorization form."; diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index 8d555a61bc..74ca2a6074 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -975,30 +975,29 @@ secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_ha secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData; -secureValueVerified#2c8e61e2 date:int = SecureValueVerified; +securePlainPhone#7d6099dd phone:string = SecurePlainData; +securePlainEmail#21ec5a5f email:string = SecurePlainData; -secureValueTypeIdentity#37da58ca = SecureValueType; +secureValueTypePersonalDetails#9d2a81e3 = SecureValueType; +secureValueTypePassport#3dac6a00 = SecureValueType; +secureValueTypeDriverLicense#6e425c4 = SecureValueType; +secureValueTypeIdentityCard#a0d0744b = SecureValueType; secureValueTypeAddress#cbe31e26 = SecureValueType; +secureValueTypeUtilityBill#fc36954e = SecureValueType; +secureValueTypeBankStatement#89137c0d = SecureValueType; +secureValueTypeRentalAgreement#8b883488 = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType; -secureValueIdentity#15fe72a2 flags:# data:SecureData files:Vector verified:flags.0?SecureValueVerified = SecureValue; -secureValueAddress#88109b79 flags:# data:SecureData files:Vector verified:flags.0?SecureValueVerified = SecureValue; -secureValuePhone#e39470bf flags:# phone:string verified:flags.0?SecureValueVerified = SecureValue; -secureValueEmail#35d804cd flags:# email:string verified:flags.0?SecureValueVerified = SecureValue; +secureValue#ec4134c8 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector plain_data:flags.2?SecurePlainData selfie:flags.3?SecureFile hash:bytes = SecureValue; -inputSecureValueIdentity#94fa65b data:SecureData files:Vector = InputSecureValue; -inputSecureValueAddress#96689355 data:SecureData files:Vector = InputSecureValue; -inputSecureValuePhone#9d623d96 phone:string = InputSecureValue; -inputSecureValueEmail#9e885359 email:string = InputSecureValue; +inputSecureValue#c0da30f0 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector plain_data:flags.2?SecurePlainData selfie:flags.3?InputSecureFile = InputSecureValue; secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash; -secureValueSaved#c9147049 files:Vector = SecureValueSaved; - secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted; -account.authorizationForm#4cace8c4 required_types:Vector values:Vector users:Vector = account.AuthorizationForm; +account.authorizationForm#b9d3d1f0 flags:# selfie_required:flags.1?true required_types:Vector values:Vector users:Vector privacy_policy_url:flags.0?string = account.AuthorizationForm; account.sentEmailCode#28b1633b email_pattern:string = account.SentEmailCode; @@ -1057,8 +1056,8 @@ 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#d97e77cb user_id:InputUser types:Vector = Vector; -account.saveSecureValue#78969d0b value:InputSecureValue secure_secret_id:long = SecureValueSaved; +account.getSecureValue#73665bc2 types:Vector = Vector; +account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = SecureValue; account.deleteSecureValue#b880bc4b types:Vector = Bool; account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm; account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index e14d4190ed..c2cc6fab8d 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -29,6 +29,23 @@ QImage ReadImage(bytes::const_span buffer) { buffer.size())); } +Value::Type ConvertType(const MTPSecureValueType &type) { + using Type = Value::Type; + switch (type.type()) { + case mtpc_secureValueTypePersonalDetails: return Type::PersonalDetails; + case mtpc_secureValueTypePassport: return Type::Passport; + case mtpc_secureValueTypeDriverLicense: return Type::DriverLicense; + case mtpc_secureValueTypeIdentityCard: return Type::IdentityCard; + case mtpc_secureValueTypeAddress: return Type::Address; + case mtpc_secureValueTypeUtilityBill: return Type::UtilityBill; + case mtpc_secureValueTypeBankStatement: return Type::BankStatement; + case mtpc_secureValueTypeRentalAgreement: return Type::RentalAgreement; + case mtpc_secureValueTypePhone: return Type::Phone; + case mtpc_secureValueTypeEmail: return Type::Email; + } + Unexpected("Type in secureValueType type."); +}; + } // namespace FormRequest::FormRequest( @@ -43,9 +60,11 @@ FormRequest::FormRequest( } EditFile::EditFile( + not_null value, const File &fields, std::unique_ptr &&uploadData) -: fields(std::move(fields)) +: value(value) +, fields(std::move(fields)) , uploadData(std::move(uploadData)) , guard(std::make_shared(true)) { } @@ -105,6 +124,10 @@ UserData *FormController::bot() const { return _bot; } +QString FormController::privacyPolicyUrl() const { + return _form.privacyPolicyUrl; +} + bytes::vector FormController::passwordHashForAuth( bytes::const_span password) const { return openssl::Sha256(bytes::concatenate( @@ -157,7 +180,7 @@ void FormController::validateSecureSecret( _secretId = 0; LOG(("API Error: Failed to decrypt secure secret. " "Forgetting all files and data :(")); - for (auto &value : _form.rows) { + for (auto &[type, value] : _form.values) { if (!value.data.original.isEmpty()) { resetValue(value); } @@ -176,7 +199,7 @@ void FormController::validateSecureSecret( void FormController::decryptValues() { Expects(!_secret.empty()); - for (auto &value : _form.rows) { + for (auto &[type, value] : _form.values) { if (value.data.original.isEmpty()) { continue; } @@ -234,16 +257,17 @@ QString FormController::passwordHint() const { return _password.hint; } -void FormController::uploadScan(int valueIndex, QByteArray &&content) { - Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); - - auto &value = _form.rows[valueIndex]; - auto fileIndex = int(value.filesInEdit.size()); - value.filesInEdit.emplace_back( +void FormController::uploadScan( + not_null value, + QByteArray &&content) { + const auto nonconst = findValue(value); + auto fileIndex = int(nonconst->filesInEdit.size()); + nonconst->filesInEdit.emplace_back( + nonconst, File(), nullptr); const auto fileId = rand_value(); - auto &file = value.filesInEdit.back(); + auto &file = nonconst->filesInEdit.back(); file.fields.size = content.size(); file.fields.id = fileId; file.fields.dcId = MTP::maindc(); @@ -254,19 +278,16 @@ void FormController::uploadScan(int valueIndex, QByteArray &&content) { _scanUpdated.fire(&file); - encryptScan(valueIndex, fileIndex, std::move(content)); + encryptScan(nonconst, fileIndex, std::move(content)); } void FormController::encryptScan( - int valueIndex, + not_null value, int fileIndex, QByteArray &&content) { - Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); - Expects(fileIndex >= 0 - && fileIndex < _form.rows[valueIndex].filesInEdit.size()); + Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size()); - const auto &value = _form.rows[valueIndex]; - const auto &file = value.filesInEdit[fileIndex]; + const auto &file = value->filesInEdit[fileIndex]; const auto weak = std::weak_ptr(file.guard); crl::async([ =, @@ -289,7 +310,7 @@ void FormController::encryptScan( crl::on_main([=, encrypted = std::move(result)]() mutable { if (weak.lock()) { uploadEncryptedScan( - valueIndex, + value , fileIndex, std::move(encrypted)); } @@ -297,23 +318,26 @@ void FormController::encryptScan( }); } -void FormController::deleteScan(int valueIndex, int fileIndex) { - scanDeleteRestore(valueIndex, fileIndex, true); +void FormController::deleteScan( + not_null value, + int fileIndex) { + scanDeleteRestore(value, fileIndex, true); } -void FormController::restoreScan(int valueIndex, int fileIndex) { - scanDeleteRestore(valueIndex, fileIndex, false); +void FormController::restoreScan( + not_null value, + int fileIndex) { + scanDeleteRestore(value, fileIndex, false); } void FormController::scanDeleteRestore( - int valueIndex, + not_null value, int fileIndex, bool deleted) { - Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); - Expects(fileIndex >= 0 - && fileIndex < _form.rows[valueIndex].filesInEdit.size()); + Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size()); - auto &file = _form.rows[valueIndex].filesInEdit[fileIndex]; + const auto nonconst = findValue(value); + auto &file = nonconst->filesInEdit[fileIndex]; file.deleted = deleted; _scanUpdated.fire(&file); } @@ -342,16 +366,14 @@ void FormController::subscribeToUploader() { } void FormController::uploadEncryptedScan( - int valueIndex, + not_null value, int fileIndex, UploadScanData &&data) { - Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); - Expects(fileIndex >= 0 - && fileIndex < _form.rows[valueIndex].filesInEdit.size()); + Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size()); subscribeToUploader(); - auto &file = _form.rows[valueIndex].filesInEdit[fileIndex]; + auto &file = value->filesInEdit[fileIndex]; file.uploadData = std::make_unique(std::move(data)); auto prepared = std::make_shared( @@ -424,27 +446,32 @@ QString FormController::defaultPhoneNumber() const { return QString(); } -rpl::producer> FormController::scanUpdated() const { +auto FormController::scanUpdated() const +-> rpl::producer> { return _scanUpdated.events(); } -void FormController::enumerateRows( - base::lambda callback) { - ranges::for_each(_form.rows, callback); +const Form &FormController::form() const { + return _form; } -not_null FormController::startValueEdit(int index) { - Expects(index >= 0 && index < _form.rows.size()); +not_null FormController::findValue(not_null value) { + const auto i = _form.values.find(value->type); + Assert(i != end(_form.values)); + const auto result = &i->second; - auto &value = _form.rows[index]; - loadFiles(value.files); - value.filesInEdit = ranges::view::all( - value.files - ) | ranges::view::transform([](const File &file) { - return EditFile(file, nullptr); + Ensures(result == value); + return result; +} + +void FormController::startValueEdit(not_null value) { + const auto nonconst = findValue(value); + loadFiles(nonconst->files); + nonconst->filesInEdit = ranges::view::all( + value->files + ) | ranges::view::transform([=](const File &file) { + return EditFile(value, file, nullptr); }) | ranges::to_vector; - - return &value; } void FormController::loadFiles(std::vector &files) { @@ -529,119 +556,169 @@ void FormController::fileLoadFail(FileKey key) { } } -void FormController::cancelValueEdit(int index) { - Expects(index >= 0 && index < _form.rows.size()); - - _form.rows[index].filesInEdit.clear(); +void FormController::cancelValueEdit(not_null value) { + const auto nonconst = findValue(value); + nonconst->filesInEdit.clear(); } bool FormController::isEncryptedValue(Value::Type type) const { - return (type == Value::Type::Identity || type == Value::Type::Address); + return (type != Value::Type::Phone && type != Value::Type::Email); } -void FormController::saveValueEdit(int index) { - Expects(index >= 0 && index < _form.rows.size()); +void FormController::saveValueEdit( + not_null value, + ValueMap &&data) { + const auto nonconst = findValue(value); + nonconst->data.parsed = std::move(data); - if (isEncryptedValue(_form.rows[index].type)) { - saveEncryptedValue(index); + if (isEncryptedValue(nonconst->type)) { + saveEncryptedValue(nonconst); } else { - savePlainTextValue(index); + savePlainTextValue(nonconst); } } -void FormController::saveEncryptedValue(int index) { - Expects(index >= 0 && index < _form.rows.size()); - Expects(isEncryptedValue(_form.rows[index].type)); +void FormController::saveEncryptedValue(not_null value) { + Expects(isEncryptedValue(value->type)); - auto &value = _form.rows[index]; if (_secret.empty()) { _secretCallbacks.push_back([=] { - saveEncryptedValue(index); + saveEncryptedValue(value); }); return; } - auto inputFiles = QVector(); - inputFiles.reserve(value.filesInEdit.size()); - for (const auto &file : value.filesInEdit) { - if (file.deleted) { - continue; - } else if (const auto uploadData = file.uploadData.get()) { - inputFiles.push_back(MTP_inputSecureFileUploaded( + const auto inputFile = [](const EditFile &file) { + if (const auto uploadData = file.uploadData.get()) { + return MTP_inputSecureFileUploaded( MTP_long(file.fields.id), MTP_int(uploadData->partsCount), MTP_bytes(uploadData->md5checksum), MTP_bytes(file.fields.hash), - MTP_bytes(file.fields.encryptedSecret))); - } else { - inputFiles.push_back(MTP_inputSecureFile( - MTP_long(file.fields.id), - MTP_long(file.fields.accessHash))); + MTP_bytes(file.fields.encryptedSecret)); } + return MTP_inputSecureFile( + MTP_long(file.fields.id), + MTP_long(file.fields.accessHash)); + }; + + auto inputFiles = QVector(); + inputFiles.reserve(value->filesInEdit.size()); + for (const auto &file : value->filesInEdit) { + if (file.deleted) { + continue; + } + inputFiles.push_back(inputFile(file)); } - if (value.data.secret.empty()) { - value.data.secret = GenerateSecretBytes(); + + if (value->data.secret.empty()) { + value->data.secret = GenerateSecretBytes(); } const auto encryptedData = EncryptData( - SerializeData(value.data.parsed.fields), - value.data.secret); - value.data.hash = encryptedData.hash; - value.data.encryptedSecret = EncryptValueSecret( - value.data.secret, + SerializeData(value->data.parsed.fields), + value->data.secret); + value->data.hash = encryptedData.hash; + value->data.encryptedSecret = EncryptValueSecret( + value->data.secret, _secret, - value.data.hash); + value->data.hash); - const auto wrap = [&] { - switch (value.type) { - case Value::Type::Identity: return MTP_inputSecureValueIdentity; - case Value::Type::Address: return MTP_inputSecureValueAddress; + const auto selfie = value->selfieInEdit + ? inputFile(*value->selfieInEdit) + : MTPInputSecureFile(); + + const auto type = [&] { + switch (value->type) { + case Value::Type::PersonalDetails: + return MTP_secureValueTypePersonalDetails(); + case Value::Type::Passport: + return MTP_secureValueTypePassport(); + case Value::Type::DriverLicense: + return MTP_secureValueTypeDriverLicense(); + case Value::Type::IdentityCard: + return MTP_secureValueTypeIdentityCard(); + case Value::Type::Address: + return MTP_secureValueTypeAddress(); + case Value::Type::UtilityBill: + return MTP_secureValueTypeUtilityBill(); + case Value::Type::BankStatement: + return MTP_secureValueTypeBankStatement(); + case Value::Type::RentalAgreement: + return MTP_secureValueTypeRentalAgreement(); } Unexpected("Value type in saveEncryptedValue()."); }(); - sendSaveRequest(index, wrap( - MTP_secureData( - MTP_bytes(encryptedData.bytes), - MTP_bytes(value.data.hash), - MTP_bytes(value.data.encryptedSecret)), - MTP_vector(inputFiles))); + const auto flags = ((value->filesInEdit.empty() + && value->data.parsed.fields.empty()) + ? MTPDinputSecureValue::Flag(0) + : MTPDinputSecureValue::Flag::f_data) + | (value->filesInEdit.empty() + ? MTPDinputSecureValue::Flag(0) + : MTPDinputSecureValue::Flag::f_files) + | (value->selfieInEdit + ? MTPDinputSecureValue::Flag::f_selfie + : MTPDinputSecureValue::Flag(0)); + if (!flags) { + request(MTPaccount_DeleteSecureValue(MTP_vector(1, type))).send(); + } else { + sendSaveRequest(value, MTP_inputSecureValue( + MTP_flags(flags), + type, + MTP_secureData( + MTP_bytes(encryptedData.bytes), + MTP_bytes(value->data.hash), + MTP_bytes(value->data.encryptedSecret)), + MTP_vector(inputFiles), + MTPSecurePlainData(), + selfie)); + } } -void FormController::savePlainTextValue(int index) { - Expects(index >= 0 && index < _form.rows.size()); - Expects(!isEncryptedValue(_form.rows[index].type)); +void FormController::savePlainTextValue(not_null value) { + Expects(!isEncryptedValue(value->type)); - auto &value = _form.rows[index]; - const auto text = value.data.parsed.fields[QString("value")]; - const auto wrap = [&] { - switch (value.type) { - case Value::Type::Phone: return MTP_inputSecureValuePhone; - case Value::Type::Email: return MTP_inputSecureValueEmail; + const auto text = value->data.parsed.fields[QString("value")]; + const auto type = [&] { + switch (value->type) { + case Value::Type::Phone: return MTP_secureValueTypePhone(); + case Value::Type::Email: return MTP_secureValueTypeEmail(); } Unexpected("Value type in savePlainTextValue()."); }(); - sendSaveRequest(index, wrap(MTP_string(text))); + const auto plain = [&] { + switch (value->type) { + case Value::Type::Phone: return MTP_securePlainPhone; + case Value::Type::Email: return MTP_securePlainEmail; + } + Unexpected("Value type in savePlainTextValue()."); + }(); + sendSaveRequest(value, MTP_inputSecureValue( + MTP_flags(MTPDinputSecureValue::Flag::f_plain_data), + type, + MTPSecureData(), + MTPVector(), + plain(MTP_string(text)), + MTPInputSecureFile())); } void FormController::sendSaveRequest( - int index, - const MTPInputSecureValue &value) { - Expects(index >= 0 && index < _form.rows.size()); - + not_null value, + const MTPInputSecureValue &data) { request(MTPaccount_SaveSecureValue( - value, + data, MTP_long(_secretId) - )).done([=](const MTPSecureValueSaved &result) { - Expects(result.type() == mtpc_secureValueSaved); + )).done([=](const MTPSecureValue &result) { + Expects(result.type() == mtpc_secureValue); - const auto &data = result.c_secureValueSaved(); - _form.rows[index].files = parseFiles( + const auto &data = result.c_secureValue(); + value->files = parseFiles( data.vfiles.v, - base::take(_form.rows[index].filesInEdit)); + base::take(value->filesInEdit)); Ui::show(Box("Saved"), LayerOption::KeepOther); }).fail([=](const RPCError &error) { - Ui::show(Box("Error saving value.")); + Ui::show(Box("Error saving value")); }).send(); } @@ -710,24 +787,6 @@ void FormController::requestForm() { }).send(); } -template -auto FormController::parseEncryptedValue( - Value::Type type, - const DataType &data) const -> Value { - Expects(data.vdata.type() == mtpc_secureData); - - auto result = Value(type); - if (data.has_verified()) { - result.verification = parseVerified(data.vverified); - } - const auto &fields = data.vdata.c_secureData(); - result.data.original = fields.vdata.v; - result.data.hash = bytes::make_vector(fields.vdata_hash.v); - result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v); - result.files = parseFiles(data.vfiles.v); - return result; -} - auto FormController::parseFiles( const QVector &data, const std::vector &editData) const @@ -788,60 +847,41 @@ void FormController::fillDownloadedFile( i->uploadData->bytes.size()))); } -template -auto FormController::parsePlainTextValue( - Value::Type type, - const QByteArray &text, - const DataType &data) const -> Value { +auto FormController::parseValue( + const MTPSecureValue &value) const -> Value { + Expects(value.type() == mtpc_secureValue); + + const auto &data = value.c_secureValue(); + const auto type = ConvertType(data.vtype); auto result = Value(type); - result.data.parsed.fields[QString("value")] = QString::fromUtf8(text); - if (data.has_verified()) { - result.verification = parseVerified(data.vverified); + if (data.has_data()) { + Assert(data.vdata.type() == mtpc_secureData); + const auto &fields = data.vdata.c_secureData(); + result.data.original = fields.vdata.v; + result.data.hash = bytes::make_vector(fields.vdata_hash.v); + result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v); } + if (data.has_files()) { + result.files = parseFiles(data.vfiles.v); + } + if (data.has_plain_data()) { + switch (data.vplain_data.type()) { + case mtpc_securePlainPhone: { + const auto &fields = data.vplain_data.c_securePlainPhone(); + result.data.parsed.fields["value"] = qs(fields.vphone); + } break; + case mtpc_securePlainEmail: { + const auto &fields = data.vplain_data.c_securePlainEmail(); + result.data.parsed.fields["value"] = qs(fields.vemail); + } break; + } + } + // #TODO passport selfie return result; } -auto FormController::parseValue( - const MTPSecureValue &value) const -> Value { - switch (value.type()) { - case mtpc_secureValueIdentity: { - return parseEncryptedValue( - Value::Type::Identity, - value.c_secureValueIdentity()); - } break; - case mtpc_secureValueAddress: { - return parseEncryptedValue( - Value::Type::Address, - value.c_secureValueAddress()); - } break; - case mtpc_secureValuePhone: { - const auto &data = value.c_secureValuePhone(); - return parsePlainTextValue( - Value::Type::Phone, - data.vphone.v, - data); - } break; - case mtpc_secureValueEmail: { - const auto &data = value.c_secureValueEmail(); - return parsePlainTextValue( - Value::Type::Phone, - data.vemail.v, - data); - } break; - } - Unexpected("secureValue type."); -} - -auto FormController::parseVerified(const MTPSecureValueVerified &data) const --> Verification { - Expects(data.type() == mtpc_secureValueVerified); - - const auto &fields = data.c_secureValueVerified(); - return Verification{ fields.vdate.v }; -} - auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { - for (auto &value : _form.rows) { + for (auto &[type, value] : _form.values) { for (auto &file : value.filesInEdit) { if (file.uploadData && file.uploadData->fullId == fullId) { return &file; @@ -852,7 +892,7 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { } auto FormController::findEditFile(const FileKey &key) -> EditFile* { - for (auto &value : _form.rows) { + for (auto &[type, value] : _form.values) { for (auto &file : value.filesInEdit) { if (file.fields.dcId == key.dcId && file.fields.id == key.id) { return &file; @@ -864,7 +904,7 @@ auto FormController::findEditFile(const FileKey &key) -> EditFile* { auto FormController::findFile(const FileKey &key) -> std::pair { - for (auto &value : _form.rows) { + for (auto &[type, value] : _form.values) { for (auto &file : value.files) { if (file.dcId == key.dcId && file.id == key.id) { return { &value, &file }; @@ -886,49 +926,33 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) { const auto &data = result.c_account_authorizationForm(); - auto values = std::vector(); - for (const auto &value : data.vvalues.v) { - values.push_back(parseValue(value)); - } - const auto findValue = [&](Value::Type type) -> Value* { - for (auto &value : values) { - if (value.type == type) { - return &value; - } - } - return nullptr; - }; - App::feedUsers(data.vusers); - for (const auto &required : data.vrequired_types.v) { - using Type = Value::Type; - const auto type = [&] { - switch (required.type()) { - case mtpc_secureValueTypeIdentity: return Type::Identity; - case mtpc_secureValueTypeAddress: return Type::Address; - case mtpc_secureValueTypeEmail: return Type::Email; - case mtpc_secureValueTypePhone: return Type::Phone; - } - Unexpected("Type in secureValueType type."); - }(); - - if (auto value = findValue(type)) { - _form.rows.push_back(std::move(*value)); - } else { - _form.rows.push_back(Value(type)); + for (const auto &value : data.vvalues.v) { + auto parsed = parseValue(value); + const auto type = parsed.type; + const auto alreadyIt = _form.values.find(type); + if (alreadyIt != _form.values.end()) { + LOG(("API Error: Two values for type %1 in authorization form" + "%1").arg(int(type))); + continue; } + _form.values.emplace(type, std::move(parsed)); + } + _form.identitySelfieRequired = data.is_selfie_required(); + if (data.has_privacy_policy_url()) { + _form.privacyPolicyUrl = qs(data.vprivacy_policy_url); + } + for (const auto &required : data.vrequired_types.v) { + const auto type = ConvertType(required); + _form.request.push_back(type); + _form.values.emplace(type, Value(type)); } _bot = App::userLoaded(_request.botId); } void FormController::formFail(const RPCError &error) { - // #TODO passport testing - _form.rows.push_back(Value(Value::Type::Identity)); - _form.rows.back().data.parsed.fields["first_name"] = "test1"; - _form.rows.back().data.parsed.fields["last_name"] = "test2"; - _view->editValue(0); -// Ui::show(Box(lang(lng_passport_form_error))); + Ui::show(Box(lang(lng_passport_form_error))); } void FormController::requestPassword() { diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index fd30ecd44c..6bfce51152 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -67,6 +67,8 @@ private: }; +struct Value; + struct File { uint64 id = 0; uint64 accessHash = 0; @@ -83,19 +85,17 @@ struct File { struct EditFile { EditFile( + not_null value, const File &fields, std::unique_ptr &&uploadData); + not_null value; File fields; UploadScanDataPointer uploadData; std::shared_ptr guard; bool deleted = false; }; -struct Verification { - TimeId date; -}; - struct ValueMap { std::map fields; }; @@ -110,8 +110,14 @@ struct ValueData { struct Value { enum class Type { - Identity, + PersonalDetails, + Passport, + DriverLicense, + IdentityCard, Address, + UtilityBill, + BankStatement, + RentalAgreement, Phone, Email, }; @@ -124,11 +130,15 @@ struct Value { ValueData data; std::vector files; std::vector filesInEdit; - base::optional verification; + base::optional selfie; + base::optional selfieInEdit; }; struct Form { - std::vector rows; + std::map values; + std::vector request; + bool identitySelfieRequired = false; + QString privacyPolicyUrl; }; struct PasswordSettings { @@ -174,14 +184,15 @@ public: void show(); UserData *bot() const; + QString privacyPolicyUrl() const; void submitPassword(const QString &password); rpl::producer passwordError() const; QString passwordHint() const; - void uploadScan(int valueIndex, QByteArray &&content); - void deleteScan(int valueIndex, int fileIndex); - void restoreScan(int valueIndex, int fileIndex); + void uploadScan(not_null value, QByteArray &&content); + void deleteScan(not_null value, int fileIndex); + void restoreScan(not_null value, int fileIndex); rpl::producer<> secretReadyEvents() const; @@ -190,10 +201,10 @@ public: rpl::producer> scanUpdated() const; - void enumerateRows(base::lambda callback); - not_null startValueEdit(int index); - void cancelValueEdit(int index); - void saveValueEdit(int index); + const Form &form() const; + void startValueEdit(not_null value); + void cancelValueEdit(not_null value); + void saveValueEdit(not_null value, ValueMap &&data); void cancel(); @@ -205,6 +216,7 @@ private: EditFile *findEditFile(const FullMsgId &fullId); EditFile *findEditFile(const FileKey &key); std::pair findFile(const FileKey &key); + not_null findValue(not_null value); void requestForm(); void requestPassword(); @@ -214,16 +226,6 @@ private: void parseForm(const MTPaccount_AuthorizationForm &result); void showForm(); Value parseValue(const MTPSecureValue &value) const; - template - Value parseEncryptedValue( - Value::Type type, - const DataType &data) const; - template - Value parsePlainTextValue( - Value::Type type, - const QByteArray &text, - const DataType &data) const; - Verification parseVerified(const MTPSecureValueVerified &data) const; std::vector parseFiles( const QVector &data, const std::vector &editData = {}) const; @@ -253,22 +255,24 @@ private: void subscribeToUploader(); void encryptScan( - int valueIndex, + not_null value, int fileIndex, QByteArray &&content); void uploadEncryptedScan( - int valueIndex, + not_null value, int fileIndex, UploadScanData &&data); void scanUploadDone(const Storage::UploadSecureDone &data); void scanUploadProgress(const Storage::UploadSecureProgress &data); void scanUploadFail(const FullMsgId &fullId); - void scanDeleteRestore(int valueIndex, int fileIndex, bool deleted); + void scanDeleteRestore(not_null value, int fileIndex, bool deleted); bool isEncryptedValue(Value::Type type) const; - void saveEncryptedValue(int index); - void savePlainTextValue(int index); - void sendSaveRequest(int index, const MTPInputSecureValue &value); + void saveEncryptedValue(not_null value); + void savePlainTextValue(not_null value); + void sendSaveRequest( + not_null value, + const MTPInputSecureValue &data); not_null _controller; FormRequest _request; diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp new file mode 100644 index 0000000000..cb4472ce17 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp @@ -0,0 +1,96 @@ +/* +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_view_controller.h" + +#include "passport/passport_form_controller.h" + +namespace Passport { +namespace { + +std::map ScopeTypesMap() { + return { + { Value::Type::PersonalDetails, Scope::Type::Identity }, + { Value::Type::Passport, Scope::Type::Identity }, + { Value::Type::DriverLicense, Scope::Type::Identity }, + { Value::Type::IdentityCard, Scope::Type::Identity }, + { Value::Type::Address, Scope::Type::Address }, + { Value::Type::UtilityBill, Scope::Type::Address }, + { Value::Type::BankStatement, Scope::Type::Address }, + { Value::Type::RentalAgreement, Scope::Type::Address }, + { Value::Type::Phone, Scope::Type::Phone }, + { Value::Type::Email, Scope::Type::Email }, + }; +} + +Scope::Type ScopeTypeForValueType(Value::Type type) { + static const auto map = ScopeTypesMap(); + const auto i = map.find(type); + Assert(i != map.end()); + return i->second; +} + +std::map ScopeFieldsMap() { + return { + { Scope::Type::Identity, Value::Type::PersonalDetails }, + { Scope::Type::Address, Value::Type::Address }, + { Scope::Type::Phone, Value::Type::Phone }, + { Scope::Type::Email, Value::Type::Email }, + }; +} + +Value::Type FieldsTypeForScopeType(Scope::Type type) { + static const auto map = ScopeFieldsMap(); + const auto i = map.find(type); + Assert(i != map.end()); + return i->second; +} + +} // namespace + +Scope::Scope(Type type, not_null fields) +: type(type) +, fields(fields) { +} + +std::vector ComputeScopes(not_null controller) { + auto scopes = std::map(); + const auto &form = controller->form(); + const auto findValue = [&](const Value::Type type) { + const auto i = form.values.find(type); + Assert(i != form.values.end()); + return &i->second; + }; + for (const auto type : form.request) { + const auto scopeType = ScopeTypeForValueType(type); + const auto fieldsType = FieldsTypeForScopeType(scopeType); + const auto [i, ok] = scopes.emplace( + scopeType, + Scope(scopeType, findValue(fieldsType))); + i->second.selfieRequired = (scopeType == Scope::Type::Identity) + && form.identitySelfieRequired; + const auto alreadyIt = ranges::find( + i->second.files, + type, + [](not_null value) { return value->type; }); + if (alreadyIt != end(i->second.files)) { + LOG(("API Error: Value type %1 multiple times in request." + ).arg(int(type))); + continue; + } else if (type != fieldsType) { + i->second.files.push_back(findValue(type)); + } + } + auto result = std::vector(); + result.reserve(scopes.size()); + for (auto &[type, scope] : scopes) { + result.push_back(std::move(scope)); + } + return result; +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.h b/Telegram/SourceFiles/passport/passport_form_view_controller.h index a6aab70c5f..172bd93cdb 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.h @@ -7,16 +7,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "passport/passport_form_controller.h" + namespace Passport { -struct Value; +struct Scope { + enum class Type { + Identity, + Address, + Phone, + Email, + }; + Scope(Type type, not_null fields); + + Type type; + not_null fields; + std::vector> files; + bool selfieRequired = false; +}; + +std::vector ComputeScopes(not_null controller); class ViewController { public: virtual void showAskPassword() = 0; virtual void showNoPassword() = 0; virtual void showPasswordUnconfirmed() = 0; - virtual void editValue(int index) = 0; + virtual void editScope(int index) = 0; virtual ~ViewController() { } diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 8e8ee056ad..5c8d6ae713 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -51,52 +51,61 @@ BoxContent *BoxPointer::operator->() const { } PanelController::PanelController(not_null form) -: _form(form) { +: _form(form) +, _scopes(ComputeScopes(_form)) { _form->secretReadyEvents( ) | rpl::start_with_next([=] { if (_panel) { _panel->showForm(); } }, lifetime()); + _scopes = ComputeScopes(_form); } not_null PanelController::bot() const { return _form->bot(); } +QString PanelController::privacyPolicyUrl() const { + return _form->privacyPolicyUrl(); +} + void PanelController::fillRows( base::lambda callback) { - _form->enumerateRows([&](const Value &value) { - switch (value.type) { - case Value::Type::Identity: + if (_scopes.empty()) { + _scopes = ComputeScopes(_form); + } + for (const auto &scope : _scopes) { + switch (scope.type) { + case Scope::Type::Identity: callback( lang(lng_passport_identity_title), lang(lng_passport_identity_description), false); break; - case Value::Type::Address: + case Scope::Type::Address: callback( lang(lng_passport_address_title), lang(lng_passport_address_description), false); break; - case Value::Type::Phone: + case Scope::Type::Phone: callback( lang(lng_passport_phone_title), - App::self()->phone(), - true); + lang(lng_passport_phone_description), + false); break; - case Value::Type::Email: + case Scope::Type::Email: callback( lang(lng_passport_email_title), lang(lng_passport_email_description), false); break; } - }); + } } void PanelController::submitPassword(const QString &password) { @@ -119,27 +128,40 @@ QString PanelController::defaultPhoneNumber() const { return _form->defaultPhoneNumber(); } -void PanelController::uploadScan(int valueIndex, QByteArray &&content) { - Expects(_panel != nullptr); +void PanelController::uploadScan(QByteArray &&content) { + Expects(_editScope != nullptr); + Expects(_editScopeFilesIndex >= 0); - _form->uploadScan(valueIndex, std::move(content)); + _form->uploadScan( + _editScope->files[_editScopeFilesIndex], + std::move(content)); } -void PanelController::deleteScan(int valueIndex, int fileIndex) { - Expects(_panel != nullptr); +void PanelController::deleteScan(int fileIndex) { + Expects(_editScope != nullptr); + Expects(_editScopeFilesIndex >= 0); - _form->deleteScan(valueIndex, fileIndex); + _form->deleteScan( + _editScope->files[_editScopeFilesIndex], + fileIndex); } -void PanelController::restoreScan(int valueIndex, int fileIndex) { - Expects(_panel != nullptr); +void PanelController::restoreScan(int fileIndex) { + Expects(_editScope != nullptr); + Expects(_editScopeFilesIndex >= 0); - _form->restoreScan(valueIndex, fileIndex); + _form->restoreScan( + _editScope->files[_editScopeFilesIndex], + fileIndex); } rpl::producer PanelController::scanUpdated() const { return _form->scanUpdated( - ) | rpl::map([=](not_null file) { + ) | rpl::filter([=](not_null file) { + return (_editScope != nullptr) + && (_editScopeFilesIndex >= 0) + && (file->value == _editScope->files[_editScopeFilesIndex]); + }) | rpl::map([=](not_null file) { return collectScanInfo(*file); }); } @@ -202,22 +224,29 @@ void PanelController::ensurePanelCreated() { } } -void PanelController::editValue(int index) { - ensurePanelCreated(); // #TODO passport testing +void PanelController::editScope(int index) { Expects(_panel != nullptr); + Expects(index >= 0 && index < _scopes.size()); - _editValue = _form->startValueEdit(index); - Assert(_editValue != nullptr); + _editScope = &_scopes[index]; + + // #TODO select type for files index + _editScopeFilesIndex = _scopes[index].files.empty() ? -1 : 0; + + _form->startValueEdit(_editScope->fields); + if (_editScopeFilesIndex >= 0) { + _form->startValueEdit(_editScope->files[_editScopeFilesIndex]); + } auto content = [&]() -> object_ptr { - switch (_editValue->type) { - case Value::Type::Identity: + switch (_editScope->type) { + case Scope::Type::Identity: return object_ptr( _panel.get(), this, - index, - _editValue->data.parsed, - valueFiles(*_editValue)); + _editScope->fields->data.parsed, + _editScope->files[_editScopeFilesIndex]->data.parsed, + valueFiles(*_editScope->files[_editScopeFilesIndex])); } return { nullptr }; }(); @@ -244,21 +273,29 @@ std::vector PanelController::valueFiles(const Value &value) const { } void PanelController::cancelValueEdit(int index) { - if (base::take(_editValue)) { - _form->cancelValueEdit(index); + if (const auto scope = base::take(_editScope)) { + _form->startValueEdit(scope->fields); + const auto index = std::exchange(_editScopeFilesIndex, -1); + if (index >= 0) { + _form->startValueEdit(scope->files[index]); + } } } -void PanelController::saveValue(int index, ValueMap &&data) { +void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) { Expects(_panel != nullptr); - Expects(_editValue != nullptr); + Expects(_editScope != nullptr); - _editValue->data.parsed = std::move(data); - _editValue = nullptr; + const auto scope = base::take(_editScope); + _form->saveValueEdit(scope->fields, std::move(data)); + const auto index = std::exchange(_editScopeFilesIndex, -1); + if (index >= 0) { + _form->saveValueEdit(scope->files[index], std::move(filesData)); + } else { + Assert(filesData.fields.empty()); + } _panel->showForm(); - - _form->saveValueEdit(index); } void PanelController::cancelAuth() { diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index 72ea5b6aa7..1791d5eb73 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -45,14 +45,15 @@ public: PanelController(not_null form); not_null bot() const; + QString privacyPolicyUrl() const; void submitPassword(const QString &password); rpl::producer passwordError() const; QString passwordHint() const; - void uploadScan(int valueIndex, QByteArray &&content); - void deleteScan(int valueIndex, int fileIndex); - void restoreScan(int valueIndex, int fileIndex); + void uploadScan(QByteArray &&content); + void deleteScan(int fileIndex); + void restoreScan(int fileIndex); rpl::producer scanUpdated() const; QString defaultEmail() const; @@ -68,8 +69,8 @@ public: QString description, bool ready)> callback); - void editValue(int index) override; - void saveValue(int index, ValueMap &&data); + void editScope(int index) override; + void saveScope(ValueMap &&data, ValueMap &&filesData); void cancelAuth(); @@ -84,9 +85,11 @@ private: ScanInfo collectScanInfo(const EditFile &file) const; not_null _form; + std::vector _scopes; std::unique_ptr _panel; - Value *_editValue = nullptr; + Scope *_editScope = nullptr; + int _editScopeFilesIndex = -1; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp index 16cf11c98a..b45830563f 100644 --- a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp @@ -17,7 +17,7 @@ PanelDetailsRow::PanelDetailsRow( const QString &label, const QString &value) : _label(label) -, _field(this, st::passportDetailsField) { +, _field(this, st::passportDetailsField, nullptr, value) { } QPointer PanelDetailsRow::field() const { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp index 1087a488d4..dd07fcceed 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp @@ -190,11 +190,10 @@ void ScanButton::paintEvent(QPaintEvent *e) { PanelEditIdentity::PanelEditIdentity( QWidget*, not_null controller, - int valueIndex, const ValueMap &data, + const ValueMap &scanData, std::vector &&files) : _controller(controller) -, _valueIndex(valueIndex) , _files(std::move(files)) , _scroll(this, st::passportPanelScroll) , _topShadow(this) @@ -203,11 +202,13 @@ PanelEditIdentity::PanelEditIdentity( this, langFactory(lng_passport_save_value), st::passportPanelSaveValue) { - setupControls(data); + setupControls(data, scanData); } -void PanelEditIdentity::setupControls(const ValueMap &data) { - const auto inner = setupContent(data); +void PanelEditIdentity::setupControls( + const ValueMap &data, + const ValueMap &scanData) { + const auto inner = setupContent(data, scanData); using namespace rpl::mappers; @@ -225,7 +226,8 @@ void PanelEditIdentity::setupControls(const ValueMap &data) { } not_null PanelEditIdentity::setupContent( - const ValueMap &data) { + const ValueMap &data, + const ValueMap &scanData) { const auto inner = _scroll->setOwnedWidget( object_ptr(this)); _scroll->widthValue( @@ -261,7 +263,9 @@ not_null PanelEditIdentity::setupContent( _scansUpload = inner->add( object_ptr( inner, - uploadButtonText(), + _scansUploadTexts.events_starting_with( + uploadButtonText() + ) | rpl::flatten_latest(), st::passportUploadButton), st::passportUploadButtonPadding); _scansUpload->addClickHandler([=] { @@ -315,6 +319,7 @@ void PanelEditIdentity::updateScan(ScanInfo &&info) { _scans.back()->show(anim::type::normal); _scansDivider->hide(anim::type::normal); _scansHeader->show(anim::type::normal); + _scansUploadTexts.fire(uploadButtonText()); } } @@ -335,12 +340,12 @@ void PanelEditIdentity::pushScan(const ScanInfo &info) { scan->deleteClicks( ) | rpl::start_with_next([=] { - _controller->deleteScan(_valueIndex, index); + _controller->deleteScan(index); }, scan->lifetime()); scan->restoreClicks( ) | rpl::start_with_next([=] { - _controller->restoreScan(_valueIndex, index); + _controller->restoreScan(index); }, scan->lifetime()); } @@ -394,14 +399,15 @@ void PanelEditIdentity::encryptScan(const QString &path) { } void PanelEditIdentity::encryptScanContent(QByteArray &&content) { - _controller->uploadScan(_valueIndex, std::move(content)); + _controller->uploadScan(std::move(content)); } void PanelEditIdentity::save() { auto data = ValueMap(); data.fields["first_name"] = _firstName->getLastText(); data.fields["last_name"] = _lastName->getLastText(); - _controller->saveValue(_valueIndex, std::move(data)); + auto scanData = ValueMap(); + _controller->saveScope(std::move(data), std::move(scanData)); } rpl::producer PanelEditIdentity::uploadButtonText() const { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_identity.h b/Telegram/SourceFiles/passport/passport_panel_edit_identity.h index 24a0ead0c1..47840bfb41 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_identity.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_identity.h @@ -41,8 +41,8 @@ public: PanelEditIdentity( QWidget *parent, not_null controller, - int valueIndex, const ValueMap &data, + const ValueMap &scanData, std::vector &&files); protected: @@ -50,8 +50,10 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - void setupControls(const ValueMap &data); - not_null setupContent(const ValueMap &data); + void setupControls(const ValueMap &data, const ValueMap &scanData); + not_null setupContent( + const ValueMap &data, + const ValueMap &scanData); void updateControlsGeometry(); void chooseScan(); @@ -64,8 +66,6 @@ private: void save(); not_null _controller; - int _valueIndex = -1; - std::vector _files; object_ptr _scroll; @@ -77,6 +77,7 @@ private: QPointer _scansWrap; std::vector>> _scans; QPointer _scansUpload; + rpl::event_stream> _scansUploadTexts; QPointer _firstName; QPointer _lastName; diff --git a/Telegram/SourceFiles/passport/passport_panel_form.cpp b/Telegram/SourceFiles/passport/passport_panel_form.cpp index 899c4d7850..29f8b862a1 100644 --- a/Telegram/SourceFiles/passport/passport_panel_form.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_form.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "passport/passport_panel_controller.h" #include "lang/lang_keys.h" #include "boxes/abstract_box.h" +#include "core/click_handler_types.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" @@ -221,27 +222,30 @@ not_null PanelForm::setupContent() { title, description))); _rows.back()->addClickHandler([=] { - _controller->editValue(index); + _controller->editScope(index); }); _rows.back()->setReady(ready); + ++index; }); + const auto policyUrl = _controller->privacyPolicyUrl(); const auto policy = inner->add( object_ptr( inner, - lng_passport_accept_allow( - lt_policy, - textcmdLink( - 1, - lng_passport_policy(lt_bot, App::peerName(bot))), - lt_bot, - '@' + bot->username), + (policyUrl.isEmpty() + ? lng_passport_allow(lt_bot, '@' + bot->username) + : lng_passport_accept_allow( + lt_policy, + textcmdLink( + 1, + lng_passport_policy(lt_bot, App::peerName(bot))), + lt_bot, + '@' + bot->username)), Ui::FlatLabel::InitType::Rich, st::passportFormPolicy), st::passportFormPolicyPadding); - policy->setLink(1, std::make_shared([=] { - _controller->cancelAuth(); - // #TODO passport policy - })); + if (!policyUrl.isEmpty()) { + policy->setLink(1, std::make_shared(policyUrl)); + } return inner; } diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index ad4df532d3..f47b2ebe0d 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -458,6 +458,7 @@ <(src_loc)/passport/passport_encryption.h <(src_loc)/passport/passport_form_controller.cpp <(src_loc)/passport/passport_form_controller.h +<(src_loc)/passport/passport_form_view_controller.cpp <(src_loc)/passport/passport_form_view_controller.h <(src_loc)/passport/passport_panel.cpp <(src_loc)/passport/passport_panel.h