mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-28 11:30:54 +00:00
Remember passport credentials for 30 minutes.
This commit is contained in:
parent
9929bfb281
commit
e3e8d083ea
@ -54,6 +54,14 @@ PasscodeBox::PasscodeBox(
|
||||
if (!hint.isEmpty()) _hintText.setText(st::passcodeTextStyle, lng_signin_hint(lt_password_hint, hint));
|
||||
}
|
||||
|
||||
rpl::producer<QByteArray> PasscodeBox::newPasswordSet() const {
|
||||
return _newPasswordSet.events();
|
||||
}
|
||||
|
||||
rpl::producer<> PasscodeBox::passwordReloadNeeded() const {
|
||||
return _passwordReloadNeeded.events();
|
||||
}
|
||||
|
||||
void PasscodeBox::prepare() {
|
||||
addButton(langFactory(_turningOff ? lng_passcode_remove_button : lng_settings_save), [=] { save(); });
|
||||
addButton(langFactory(lng_cancel), [=] { closeBox(); });
|
||||
@ -197,9 +205,9 @@ void PasscodeBox::setInnerFocus() {
|
||||
}
|
||||
}
|
||||
|
||||
void PasscodeBox::setPasswordDone() {
|
||||
void PasscodeBox::setPasswordDone(const QByteArray &newPasswordBytes) {
|
||||
_setRequest = 0;
|
||||
emit reloadPassword();
|
||||
_newPasswordSet.fire_copy(newPasswordBytes);
|
||||
auto text = lang(_reenterPasscode->isHidden() ? lng_cloud_password_removed : (_oldPasscode->isHidden() ? lng_cloud_password_was_set : lng_cloud_password_updated));
|
||||
getDelegate()->show(Box<InformBox>(text), LayerOption::CloseOther);
|
||||
}
|
||||
@ -236,7 +244,7 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) {
|
||||
QString err = error.type();
|
||||
if (err == qstr("PASSWORD_HASH_INVALID")) {
|
||||
if (_oldPasscode->isHidden()) {
|
||||
emit reloadPassword();
|
||||
_passwordReloadNeeded.fire({});
|
||||
closeBox();
|
||||
} else {
|
||||
badOldPasscode();
|
||||
@ -247,20 +255,36 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) {
|
||||
_newError = lang(lng_cloud_password_bad);
|
||||
update();
|
||||
} else if (err == qstr("NEW_SALT_INVALID")) {
|
||||
emit reloadPassword();
|
||||
_passwordReloadNeeded.fire({});
|
||||
closeBox();
|
||||
} else if (err == qstr("EMAIL_INVALID")) {
|
||||
_emailError = lang(lng_cloud_password_bad_email);
|
||||
_recoverEmail->setFocus();
|
||||
_recoverEmail->showError();
|
||||
update();
|
||||
} else if (err == qstr("EMAIL_UNCONFIRMED")) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PasscodeBox::setPasswordFail(
|
||||
const QByteArray &newPasswordBytes,
|
||||
const RPCError &error) {
|
||||
if (MTP::isFloodError(error)) {
|
||||
return setPasswordFail(error);
|
||||
} else if (MTP::isDefaultHandledError(error)) {
|
||||
return setPasswordFail(error);
|
||||
} else if (error.type() == qstr("EMAIL_UNCONFIRMED")) {
|
||||
closeReplacedBy();
|
||||
_setRequest = 0;
|
||||
|
||||
_newPasswordSet.fire_copy(newPasswordBytes);
|
||||
getDelegate()->show(
|
||||
Box<InformBox>(lang(lng_cloud_password_almost)),
|
||||
LayerOption::CloseOther);
|
||||
emit reloadPassword();
|
||||
return true;
|
||||
} else {
|
||||
return setPasswordFail(error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PasscodeBox::save(bool force) {
|
||||
@ -386,14 +410,15 @@ void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) {
|
||||
MTPbytes(), // new_secure_secret
|
||||
MTPlong()) // new_secure_secret_id
|
||||
)).done([=](const MTPBool &result) {
|
||||
setPasswordDone();
|
||||
setPasswordDone({});
|
||||
}).fail([=](const RPCError &error) {
|
||||
setPasswordFail(error);
|
||||
setPasswordFail({}, error);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
|
||||
const auto newPasswordData = (_newSalt + newPassword.toUtf8() + _newSalt);
|
||||
const auto newPasswordBytes = newPassword.toUtf8();
|
||||
const auto newPasswordData = (_newSalt + newPasswordBytes + _newSalt);
|
||||
auto newPasswordHash = QByteArray(32, Qt::Uninitialized);
|
||||
hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data());
|
||||
const auto oldPasswordData = QByteArray();
|
||||
@ -416,9 +441,9 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
|
||||
MTPbytes(), // new_secure_secret
|
||||
MTPlong()) // new_secure_secret_id
|
||||
)).done([=](const MTPBool &result) {
|
||||
setPasswordDone();
|
||||
setPasswordDone(newPasswordBytes);
|
||||
}).fail([=](const RPCError &error) {
|
||||
setPasswordFail(error);
|
||||
setPasswordFail(newPasswordBytes, error);
|
||||
}).send();
|
||||
}
|
||||
|
||||
@ -443,9 +468,10 @@ void PasscodeBox::changeCloudPassword(
|
||||
return;
|
||||
}
|
||||
const auto secret = Passport::DecryptSecureSecret(
|
||||
bytes::make_span(data.vsecure_salt.v),
|
||||
bytes::make_span(data.vsecure_secret.v),
|
||||
bytes::make_span(passwordUtf));
|
||||
Passport::CountPasswordHashForSecret(
|
||||
bytes::make_span(data.vsecure_salt.v),
|
||||
bytes::make_span(passwordUtf)));
|
||||
if (secret.empty()) {
|
||||
LOG(("API Error: Failed to decrypt secure secret."));
|
||||
suggestSecretReset(oldPasswordHash, newPassword);
|
||||
@ -505,8 +531,8 @@ void PasscodeBox::sendChangeCloudPassword(
|
||||
const QByteArray &oldPasswordHash,
|
||||
const QString &newPassword,
|
||||
const QByteArray &secureSecret) {
|
||||
const auto passwordUtf = newPassword.toUtf8();
|
||||
const auto newPasswordData = (_newSalt + passwordUtf + _newSalt);
|
||||
const auto newPasswordBytes = newPassword.toUtf8();
|
||||
const auto newPasswordData = (_newSalt + newPasswordBytes + _newSalt);
|
||||
auto newPasswordHash = QByteArray(32, Qt::Uninitialized);
|
||||
hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data());
|
||||
const auto hint = _passwordHint->getLastText();
|
||||
@ -522,9 +548,10 @@ void PasscodeBox::sendChangeCloudPassword(
|
||||
newSecureSecretId = Passport::CountSecureSecretId(
|
||||
bytes::make_span(secureSecret));
|
||||
newSecureSecret = Passport::EncryptSecureSecret(
|
||||
bytes::make_span(_newSecureSecretSalt),
|
||||
bytes::make_span(secureSecret),
|
||||
bytes::make_span(passwordUtf));
|
||||
Passport::CountPasswordHashForSecret(
|
||||
bytes::make_span(_newSecureSecretSalt),
|
||||
bytes::make_span(newPasswordBytes)));
|
||||
}
|
||||
_setRequest = request(MTPaccount_UpdatePasswordSettings(
|
||||
MTP_bytes(oldPasswordHash),
|
||||
@ -538,9 +565,9 @@ void PasscodeBox::sendChangeCloudPassword(
|
||||
MTP_bytes(newSecureSecret),
|
||||
MTP_long(newSecureSecretId))
|
||||
)).done([=](const MTPBool &result) {
|
||||
setPasswordDone();
|
||||
setPasswordDone(newPasswordBytes);
|
||||
}).fail([=](const RPCError &error) {
|
||||
setPasswordFail(error);
|
||||
setPasswordFail(newPasswordBytes, error);
|
||||
}).send();
|
||||
}
|
||||
|
||||
@ -603,8 +630,17 @@ void PasscodeBox::recover() {
|
||||
const auto box = getDelegate()->show(Box<RecoverBox>(
|
||||
_pattern,
|
||||
_notEmptyPassport));
|
||||
connect(box, &RecoverBox::reloadPassword, this, &PasscodeBox::reloadPassword);
|
||||
connect(box, &RecoverBox::recoveryExpired, this, &PasscodeBox::recoverExpired);
|
||||
|
||||
box->passwordCleared(
|
||||
) | rpl::map([] {
|
||||
return QByteArray();
|
||||
}) | rpl::start_to_stream(_newPasswordSet, lifetime());
|
||||
|
||||
box->recoveryExpired(
|
||||
) | rpl::start_with_next([=] {
|
||||
recoverExpired();
|
||||
}, lifetime());
|
||||
|
||||
_replacedBy = box;
|
||||
}
|
||||
|
||||
@ -630,6 +666,14 @@ RecoverBox::RecoverBox(
|
||||
, _recoverCode(this, st::defaultInputField, langFactory(lng_signin_code)) {
|
||||
}
|
||||
|
||||
rpl::producer<> RecoverBox::passwordCleared() const {
|
||||
return _passwordCleared.events();
|
||||
}
|
||||
|
||||
rpl::producer<> RecoverBox::recoveryExpired() const {
|
||||
return _recoveryExpired.events();
|
||||
}
|
||||
|
||||
void RecoverBox::prepare() {
|
||||
setTitle(langFactory(lng_signin_recover_title));
|
||||
|
||||
@ -707,10 +751,12 @@ void RecoverBox::codeChanged() {
|
||||
update();
|
||||
}
|
||||
|
||||
void RecoverBox::codeSubmitDone(bool recover, const MTPauth_Authorization &result) {
|
||||
void RecoverBox::codeSubmitDone(
|
||||
bool recover,
|
||||
const MTPauth_Authorization &result) {
|
||||
_submitRequest = 0;
|
||||
|
||||
emit reloadPassword();
|
||||
_passwordCleared.fire({});
|
||||
getDelegate()->show(
|
||||
Box<InformBox>(lang(lng_cloud_password_removed)),
|
||||
LayerOption::CloseOther);
|
||||
@ -730,7 +776,7 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) {
|
||||
|
||||
const QString &err = error.type();
|
||||
if (err == qstr("PASSWORD_EMPTY")) {
|
||||
emit reloadPassword();
|
||||
_passwordCleared.fire({});
|
||||
getDelegate()->show(
|
||||
Box<InformBox>(lang(lng_cloud_password_removed)),
|
||||
LayerOption::CloseOther);
|
||||
@ -739,7 +785,7 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) {
|
||||
closeBox();
|
||||
return true;
|
||||
} else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) {
|
||||
emit recoveryExpired();
|
||||
_recoveryExpired.fire({});
|
||||
closeBox();
|
||||
return true;
|
||||
} else if (err == qstr("CODE_INVALID")) {
|
||||
|
@ -17,8 +17,6 @@ class LinkButton;
|
||||
} // namespace Ui
|
||||
|
||||
class PasscodeBox : public BoxContent, private MTP::Sender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PasscodeBox(QWidget*, bool turningOff);
|
||||
PasscodeBox(
|
||||
@ -31,8 +29,8 @@ public:
|
||||
const QByteArray &newSecureSecretSalt,
|
||||
bool turningOff = false);
|
||||
|
||||
signals:
|
||||
void reloadPassword();
|
||||
rpl::producer<QByteArray> newPasswordSet() const;
|
||||
rpl::producer<> passwordReloadNeeded() const;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@ -52,8 +50,11 @@ private:
|
||||
void recoverByEmail();
|
||||
void recoverExpired();
|
||||
|
||||
void setPasswordDone();
|
||||
void setPasswordDone(const QByteArray &newPasswordBytes);
|
||||
bool setPasswordFail(const RPCError &error);
|
||||
bool setPasswordFail(
|
||||
const QByteArray &newPasswordBytes,
|
||||
const RPCError &error);
|
||||
|
||||
void recoverStarted(const MTPauth_PasswordRecovery &result);
|
||||
bool recoverStartFail(const RPCError &error);
|
||||
@ -101,17 +102,20 @@ private:
|
||||
|
||||
QString _oldError, _newError, _emailError;
|
||||
|
||||
rpl::event_stream<QByteArray> _newPasswordSet;
|
||||
rpl::event_stream<> _passwordReloadNeeded;
|
||||
|
||||
};
|
||||
|
||||
class RecoverBox : public BoxContent, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RecoverBox(QWidget*, const QString &pattern, bool notEmptyPassport);
|
||||
|
||||
signals:
|
||||
void reloadPassword();
|
||||
void recoveryExpired();
|
||||
rpl::producer<> passwordCleared() const;
|
||||
rpl::producer<> recoveryExpired() const;
|
||||
|
||||
//void reloadPassword();
|
||||
//void recoveryExpired();
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@ -135,4 +139,7 @@ private:
|
||||
|
||||
QString _error;
|
||||
|
||||
rpl::event_stream<> _passwordCleared;
|
||||
rpl::event_stream<> _recoveryExpired;
|
||||
|
||||
};
|
||||
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "passport/passport_form_controller.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_feed.h"
|
||||
#include "data/data_photo.h"
|
||||
@ -156,6 +157,30 @@ void Session::stopExport() {
|
||||
_export = nullptr;
|
||||
}
|
||||
|
||||
const Passport::SavedCredentials *Session::passportCredentials() const {
|
||||
return _passportCredentials ? &_passportCredentials->first : nullptr;
|
||||
}
|
||||
|
||||
void Session::rememberPassportCredentials(
|
||||
Passport::SavedCredentials data,
|
||||
TimeMs rememberFor) {
|
||||
Expects(rememberFor > 0);
|
||||
|
||||
static auto generation = 0;
|
||||
_passportCredentials = std::make_unique<CredentialsWithGeneration>(
|
||||
std::move(data),
|
||||
++generation);
|
||||
App::CallDelayed(rememberFor, _session, [=, check = generation] {
|
||||
if (_passportCredentials && _passportCredentials->second == check) {
|
||||
forgetPassportCredentials();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Session::forgetPassportCredentials() {
|
||||
_passportCredentials = nullptr;
|
||||
}
|
||||
|
||||
void Session::setupContactViewsViewer() {
|
||||
Notify::PeerUpdateViewer(
|
||||
Notify::PeerUpdate::Flag::UserIsContact
|
||||
|
@ -35,6 +35,10 @@ class PanelController;
|
||||
} // namespace View
|
||||
} // namespace Export
|
||||
|
||||
namespace Passport {
|
||||
struct SavedCredentials;
|
||||
} // namespace Passport
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Feed;
|
||||
@ -60,6 +64,12 @@ public:
|
||||
void stopExportWithConfirmation(FnMut<void()> callback);
|
||||
void stopExport();
|
||||
|
||||
const Passport::SavedCredentials *passportCredentials() const;
|
||||
void rememberPassportCredentials(
|
||||
Passport::SavedCredentials data,
|
||||
TimeMs rememberFor);
|
||||
void forgetPassportCredentials();
|
||||
|
||||
[[nodiscard]] base::Variable<bool> &contactsLoaded() {
|
||||
return _contactsLoaded;
|
||||
}
|
||||
@ -610,6 +620,11 @@ private:
|
||||
|
||||
MessageIdsList _mimeForwardIds;
|
||||
|
||||
using CredentialsWithGeneration = std::pair<
|
||||
const Passport::SavedCredentials,
|
||||
int>;
|
||||
std::unique_ptr<CredentialsWithGeneration> _passportCredentials;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
@ -1096,11 +1096,6 @@ void ApiWrap::appendChatsSlice(
|
||||
auto filtered = ranges::view::all(
|
||||
info.list
|
||||
) | ranges::view::filter([&](const Data::DialogInfo &info) {
|
||||
#ifdef _DEBUG
|
||||
return (info.name == "Anta");
|
||||
#else
|
||||
#error "test"
|
||||
#endif
|
||||
return (types & SettingsFromDialogsType(info.type)) != 0;
|
||||
});
|
||||
auto &list = to.info.list;
|
||||
|
@ -19,10 +19,7 @@ namespace Export {
|
||||
namespace Output {
|
||||
namespace {
|
||||
|
||||
#ifndef _DEBUG
|
||||
#error test
|
||||
#endif
|
||||
constexpr auto kMessagesInFile = 50;
|
||||
constexpr auto kMessagesInFile = 1000;
|
||||
constexpr auto kPersonalUserpicSize = 90;
|
||||
constexpr auto kEntryUserpicSize = 48;
|
||||
constexpr auto kServiceMessagePhotoSize = 60;
|
||||
|
@ -90,15 +90,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_history.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "export/output/export_output_html.h"
|
||||
#include "export/output/export_output_stats.h"
|
||||
#include "export/view/export_view_panel_controller.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#else
|
||||
#error "test"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) {
|
||||
@ -261,10 +252,6 @@ MainWidget::MainWidget(
|
||||
Messenger::Instance().mtp()->setUpdatesHandler(rpcDone(&MainWidget::updateReceived));
|
||||
Messenger::Instance().mtp()->setGlobalFailHandler(rpcFail(&MainWidget::updateFail));
|
||||
|
||||
//Export::Output::HtmlWriter writer;
|
||||
//writer.produceTestExample(psDownloadPath(), Export::View::PrepareEnvironment());
|
||||
//crl::on_main([] { App::quit(); });
|
||||
|
||||
_ptsWaiter.setRequesting(true);
|
||||
updateScrollColors();
|
||||
setupConnectingWidget();
|
||||
|
@ -16,6 +16,7 @@ namespace {
|
||||
constexpr auto kAesKeyLength = 32;
|
||||
constexpr auto kAesIvLength = 16;
|
||||
constexpr auto kSecretSize = 32;
|
||||
constexpr auto kAesParamsHashSize = 64;
|
||||
constexpr auto kMinPadding = 32;
|
||||
constexpr auto kMaxPadding = 255;
|
||||
constexpr auto kAlignTo = 16;
|
||||
@ -27,18 +28,21 @@ struct AesParams {
|
||||
bytes::vector iv;
|
||||
};
|
||||
|
||||
AesParams PrepareAesParams(bytes::const_span bytesForEncryptionKey) {
|
||||
const auto hash = openssl::Sha512(bytesForEncryptionKey);
|
||||
const auto view = gsl::make_span(hash);
|
||||
AesParams PrepareAesParamsWithHash(bytes::const_span hashForEncryptionKey) {
|
||||
Expects(hashForEncryptionKey.size() == kAesParamsHashSize);
|
||||
|
||||
auto result = AesParams();
|
||||
result.key = bytes::make_vector(
|
||||
view.subspan(0, kAesKeyLength));
|
||||
hashForEncryptionKey.subspan(0, kAesKeyLength));
|
||||
result.iv = bytes::make_vector(
|
||||
view.subspan(kAesKeyLength, kAesIvLength));
|
||||
hashForEncryptionKey.subspan(kAesKeyLength, kAesIvLength));
|
||||
return result;
|
||||
}
|
||||
|
||||
AesParams PrepareAesParams(bytes::const_span bytesForEncryptionKey) {
|
||||
return PrepareAesParamsWithHash(openssl::Sha512(bytesForEncryptionKey));
|
||||
}
|
||||
|
||||
bytes::vector EncryptOrDecrypt(
|
||||
bytes::const_span initial,
|
||||
AesParams &¶ms,
|
||||
@ -112,9 +116,9 @@ bytes::vector GenerateSecretBytes() {
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes::vector DecryptSecretBytes(
|
||||
bytes::vector DecryptSecretBytesWithHash(
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span bytesForEncryptionKey) {
|
||||
bytes::const_span hashForEncryptionKey) {
|
||||
if (encryptedSecret.empty()) {
|
||||
return {};
|
||||
} else if (encryptedSecret.size() != kSecretSize) {
|
||||
@ -122,7 +126,7 @@ bytes::vector DecryptSecretBytes(
|
||||
).arg(encryptedSecret.size()));
|
||||
return {};
|
||||
}
|
||||
auto params = PrepareAesParams(bytesForEncryptionKey);
|
||||
auto params = PrepareAesParamsWithHash(hashForEncryptionKey);
|
||||
auto result = Decrypt(encryptedSecret, std::move(params));
|
||||
if (!CheckSecretBytes(result)) {
|
||||
LOG(("API Error: Bad secret bytes."));
|
||||
@ -131,6 +135,24 @@ bytes::vector DecryptSecretBytes(
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes::vector DecryptSecretBytes(
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span bytesForEncryptionKey) {
|
||||
return DecryptSecretBytesWithHash(
|
||||
encryptedSecret,
|
||||
openssl::Sha512(bytesForEncryptionKey));
|
||||
}
|
||||
|
||||
bytes::vector EncryptSecretBytesWithHash(
|
||||
bytes::const_span secret,
|
||||
bytes::const_span hashForEncryptionKey) {
|
||||
Expects(secret.size() == kSecretSize);
|
||||
Expects(CheckSecretBytes(secret) == true);
|
||||
|
||||
auto params = PrepareAesParamsWithHash(hashForEncryptionKey);
|
||||
return Encrypt(secret, std::move(params));
|
||||
}
|
||||
|
||||
bytes::vector EncryptSecretBytes(
|
||||
bytes::const_span secret,
|
||||
bytes::const_span bytesForEncryptionKey) {
|
||||
@ -141,34 +163,31 @@ bytes::vector EncryptSecretBytes(
|
||||
return Encrypt(secret, std::move(params));
|
||||
}
|
||||
|
||||
bytes::vector DecryptSecureSecret(
|
||||
bytes::vector CountPasswordHashForSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password) {
|
||||
Expects(!salt.empty());
|
||||
Expects(!encryptedSecret.empty());
|
||||
Expects(!password.empty());
|
||||
|
||||
const auto bytesForEncryptionKey = bytes::concatenate(
|
||||
return openssl::Sha512(bytes::concatenate(
|
||||
salt,
|
||||
password,
|
||||
salt);
|
||||
return DecryptSecretBytes(encryptedSecret, bytesForEncryptionKey);
|
||||
salt));
|
||||
}
|
||||
|
||||
bytes::vector DecryptSecureSecret(
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span passwordHashForSecret) {
|
||||
Expects(!encryptedSecret.empty());
|
||||
|
||||
return DecryptSecretBytesWithHash(
|
||||
encryptedSecret,
|
||||
passwordHashForSecret);
|
||||
}
|
||||
|
||||
bytes::vector EncryptSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span secret,
|
||||
bytes::const_span password) {
|
||||
Expects(!salt.empty());
|
||||
bytes::const_span passwordHashForSecret) {
|
||||
Expects(secret.size() == kSecretSize);
|
||||
Expects(!password.empty());
|
||||
|
||||
const auto bytesForEncryptionKey = bytes::concatenate(
|
||||
salt,
|
||||
password,
|
||||
salt);
|
||||
return EncryptSecretBytes(secret, bytesForEncryptionKey);
|
||||
return EncryptSecretBytesWithHash(secret, passwordHashForSecret);
|
||||
}
|
||||
|
||||
bytes::vector SerializeData(const std::map<QString, QString> &data) {
|
||||
|
@ -11,14 +11,15 @@ namespace Passport {
|
||||
|
||||
bytes::vector GenerateSecretBytes();
|
||||
|
||||
bytes::vector CountPasswordHashForSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span password);
|
||||
bytes::vector EncryptSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span secret,
|
||||
bytes::const_span password);
|
||||
bytes::const_span passwordHashForSecret);
|
||||
bytes::vector DecryptSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password);
|
||||
bytes::const_span passwordHashForSecret);
|
||||
|
||||
bytes::vector SerializeData(const std::map<QString, QString> &data);
|
||||
std::map<QString, QString> DeserializeData(bytes::const_span bytes);
|
||||
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_hardcoded.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "data/data_session.h"
|
||||
#include "mainwindow.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/click_handler_types.h"
|
||||
@ -30,6 +31,7 @@ namespace {
|
||||
|
||||
constexpr auto kDocumentScansLimit = 20;
|
||||
constexpr auto kShortPollTimeout = TimeMs(3000);
|
||||
constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000);
|
||||
|
||||
bool ForwardServiceErrorRequired(const QString &error) {
|
||||
return (error == qstr("BOT_INVALID"))
|
||||
@ -436,32 +438,54 @@ std::vector<not_null<const Value*>> FormController::submitGetErrors() {
|
||||
return {};
|
||||
}
|
||||
|
||||
void FormController::submitPassword(const QString &password) {
|
||||
void FormController::submitPassword(const QByteArray &password) {
|
||||
Expects(!_password.salt.empty());
|
||||
|
||||
const auto submitSaved = !base::take(_savedPasswordValue).isEmpty();
|
||||
if (_passwordCheckRequestId) {
|
||||
return;
|
||||
} else if (password.isEmpty()) {
|
||||
_passwordError.fire(QString());
|
||||
return;
|
||||
}
|
||||
const auto passwordBytes = password.toUtf8();
|
||||
_passwordCheckRequestId = request(MTPaccount_GetPasswordSettings(
|
||||
MTP_bytes(passwordHashForAuth(bytes::make_span(passwordBytes)))
|
||||
MTP_bytes(passwordHashForAuth(bytes::make_span(password)))
|
||||
)).handleFloodErrors(
|
||||
).done([=](const MTPaccount_PasswordSettings &result) {
|
||||
Expects(result.type() == mtpc_account_passwordSettings);
|
||||
|
||||
_passwordCheckRequestId = 0;
|
||||
_savedPasswordValue = QByteArray();
|
||||
const auto &data = result.c_account_passwordSettings();
|
||||
const auto hashForAuth = passwordHashForAuth(
|
||||
bytes::make_span(password));
|
||||
const auto hashForSecret = (data.vsecure_salt.v.isEmpty()
|
||||
? bytes::vector()
|
||||
: CountPasswordHashForSecret(
|
||||
bytes::make_span(data.vsecure_salt.v),
|
||||
bytes::make_span(password)));
|
||||
_password.confirmedEmail = qs(data.vemail);
|
||||
validateSecureSecret(
|
||||
bytes::make_span(data.vsecure_salt.v),
|
||||
bytes::make_span(data.vsecure_secret.v),
|
||||
bytes::make_span(passwordBytes),
|
||||
hashForSecret,
|
||||
bytes::make_span(password),
|
||||
data.vsecure_secret_id.v);
|
||||
if (!_secret.empty()) {
|
||||
auto saved = SavedCredentials();
|
||||
saved.hashForAuth = hashForAuth;
|
||||
saved.hashForSecret = hashForSecret;
|
||||
saved.secretId = _secretId;
|
||||
Auth().data().rememberPassportCredentials(
|
||||
std::move(saved),
|
||||
kRememberCredentialsDelay);
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_passwordCheckRequestId = 0;
|
||||
if (MTP::isFloodError(error)) {
|
||||
if (submitSaved) {
|
||||
// Force reload and show form.
|
||||
_password = PasswordSettings();
|
||||
reloadPassword();
|
||||
} else if (MTP::isFloodError(error)) {
|
||||
_passwordError.fire(lang(lng_flood_error));
|
||||
} else if (error.type() == qstr("PASSWORD_HASH_INVALID")) {
|
||||
_passwordError.fire(lang(lng_passport_password_wrong));
|
||||
@ -471,6 +495,35 @@ void FormController::submitPassword(const QString &password) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void FormController::checkSavedPasswordSettings(
|
||||
const SavedCredentials &credentials) {
|
||||
_passwordCheckRequestId = request(MTPaccount_GetPasswordSettings(
|
||||
MTP_bytes(credentials.hashForAuth)
|
||||
)).done([=](const MTPaccount_PasswordSettings &result) {
|
||||
Expects(result.type() == mtpc_account_passwordSettings);
|
||||
|
||||
_passwordCheckRequestId = 0;
|
||||
const auto &data = result.c_account_passwordSettings();
|
||||
if (!data.vsecure_secret.v.isEmpty()
|
||||
&& data.vsecure_secret_id.v == credentials.secretId) {
|
||||
_password.confirmedEmail = qs(data.vemail);
|
||||
validateSecureSecret(
|
||||
bytes::make_span(data.vsecure_secret.v),
|
||||
credentials.hashForSecret,
|
||||
{},
|
||||
data.vsecure_secret_id.v);
|
||||
}
|
||||
if (_secret.empty()) {
|
||||
Auth().data().forgetPassportCredentials();
|
||||
showForm();
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_passwordCheckRequestId = 0;
|
||||
Auth().data().forgetPassportCredentials();
|
||||
showForm();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void FormController::recoverPassword() {
|
||||
if (!_password.hasRecovery) {
|
||||
_view->show(Box<InformBox>(lang(lng_signin_no_email_forgot)));
|
||||
@ -489,14 +542,16 @@ void FormController::recoverPassword() {
|
||||
const auto box = _view->show(Box<RecoverBox>(
|
||||
pattern,
|
||||
_password.notEmptyPassport));
|
||||
box->connect(box, &RecoverBox::reloadPassword, [=] {
|
||||
|
||||
box->passwordCleared(
|
||||
) | rpl::start_with_next([=] {
|
||||
reloadPassword();
|
||||
});
|
||||
box->connect(box, &RecoverBox::recoveryExpired, [=] {
|
||||
if (box) {
|
||||
box->closeBox();
|
||||
}
|
||||
});
|
||||
}, box->lifetime());
|
||||
|
||||
box->recoveryExpired(
|
||||
) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
}).fail([=](const RPCError &error) {
|
||||
_recoverRequestId = 0;
|
||||
_view->show(Box<InformBox>(Lang::Hard::ServerError()
|
||||
@ -509,6 +564,11 @@ void FormController::reloadPassword() {
|
||||
requestPassword();
|
||||
}
|
||||
|
||||
void FormController::reloadAndSubmitPassword(const QByteArray &password) {
|
||||
_savedPasswordValue = password;
|
||||
requestPassword();
|
||||
}
|
||||
|
||||
void FormController::cancelPassword() {
|
||||
if (_passwordRequestId) {
|
||||
return;
|
||||
@ -534,22 +594,30 @@ void FormController::cancelPassword() {
|
||||
}
|
||||
|
||||
void FormController::validateSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password,
|
||||
bytes::const_span passwordHashForSecret,
|
||||
bytes::const_span passwordBytes,
|
||||
uint64 serverSecretId) {
|
||||
if (!salt.empty() && !encryptedSecret.empty()) {
|
||||
_secret = DecryptSecureSecret(salt, encryptedSecret, password);
|
||||
Expects(!passwordBytes.empty() || !passwordHashForSecret.empty());
|
||||
|
||||
if (!passwordHashForSecret.empty() && !encryptedSecret.empty()) {
|
||||
_secret = DecryptSecureSecret(
|
||||
encryptedSecret,
|
||||
passwordHashForSecret);
|
||||
if (_secret.empty()) {
|
||||
_secretId = 0;
|
||||
LOG(("API Error: Failed to decrypt secure secret."));
|
||||
suggestReset(bytes::make_vector(password));
|
||||
if (!passwordBytes.empty()) {
|
||||
suggestReset(bytes::make_vector(passwordBytes));
|
||||
}
|
||||
return;
|
||||
} else if (CountSecureSecretId(_secret) != serverSecretId) {
|
||||
_secret.clear();
|
||||
_secretId = 0;
|
||||
LOG(("API Error: Wrong secure secret id."));
|
||||
suggestReset(bytes::make_vector(password));
|
||||
if (!passwordBytes.empty()) {
|
||||
suggestReset(bytes::make_vector(passwordBytes));
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
_secretId = serverSecretId;
|
||||
@ -557,7 +625,7 @@ void FormController::validateSecureSecret(
|
||||
}
|
||||
}
|
||||
if (_secret.empty()) {
|
||||
generateSecret(password);
|
||||
generateSecret(passwordBytes);
|
||||
}
|
||||
_secretReady.fire({});
|
||||
}
|
||||
@ -569,13 +637,9 @@ void FormController::suggestReset(bytes::vector password) {
|
||||
// }
|
||||
}
|
||||
_view->suggestReset([=] {
|
||||
const auto hashForAuth = openssl::Sha256(bytes::concatenate(
|
||||
_password.salt,
|
||||
password,
|
||||
_password.salt));
|
||||
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
||||
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
|
||||
MTP_bytes(hashForAuth),
|
||||
MTP_bytes(passwordHashForAuth(password)),
|
||||
MTP_account_passwordInputSettings(
|
||||
MTP_flags(Flag::f_new_secure_salt
|
||||
| Flag::f_new_secure_secret
|
||||
@ -1696,6 +1760,8 @@ void FormController::valueSaveFailed(not_null<Value*> value) {
|
||||
}
|
||||
|
||||
void FormController::generateSecret(bytes::const_span password) {
|
||||
Expects(!password.empty());
|
||||
|
||||
if (_saveSecretRequestId) {
|
||||
return;
|
||||
}
|
||||
@ -1707,20 +1773,20 @@ void FormController::generateSecret(bytes::const_span password) {
|
||||
_password.newSecureSalt,
|
||||
randomSaltPart);
|
||||
|
||||
auto secureSecretId = CountSecureSecretId(secret);
|
||||
auto encryptedSecret = EncryptSecureSecret(
|
||||
auto saved = SavedCredentials();
|
||||
saved.hashForAuth = passwordHashForAuth(password);
|
||||
saved.hashForSecret = CountPasswordHashForSecret(
|
||||
newSecureSaltFull,
|
||||
secret,
|
||||
password);
|
||||
saved.secretId = CountSecureSecretId(secret);
|
||||
|
||||
const auto hashForAuth = openssl::Sha256(bytes::concatenate(
|
||||
_password.salt,
|
||||
password,
|
||||
_password.salt));
|
||||
auto encryptedSecret = EncryptSecureSecret(
|
||||
secret,
|
||||
saved.hashForSecret);
|
||||
|
||||
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
||||
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
|
||||
MTP_bytes(hashForAuth),
|
||||
MTP_bytes(saved.hashForAuth),
|
||||
MTP_account_passwordInputSettings(
|
||||
MTP_flags(Flag::f_new_secure_salt
|
||||
| Flag::f_new_secure_secret
|
||||
@ -1731,11 +1797,15 @@ void FormController::generateSecret(bytes::const_span password) {
|
||||
MTPstring(), // email
|
||||
MTP_bytes(newSecureSaltFull),
|
||||
MTP_bytes(encryptedSecret),
|
||||
MTP_long(secureSecretId))
|
||||
MTP_long(saved.secretId))
|
||||
)).done([=](const MTPBool &result) {
|
||||
Auth().data().rememberPassportCredentials(
|
||||
std::move(saved),
|
||||
kRememberCredentialsDelay);
|
||||
|
||||
_saveSecretRequestId = 0;
|
||||
_secret = secret;
|
||||
_secretId = secureSecretId;
|
||||
_secretId = saved.secretId;
|
||||
//_password.salt = newPasswordSaltFull;
|
||||
for (const auto &callback : base::take(_secretCallbacks)) {
|
||||
callback();
|
||||
@ -1988,6 +2058,7 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
|
||||
}
|
||||
|
||||
void FormController::formFail(const QString &error) {
|
||||
_savedPasswordValue = QByteArray();
|
||||
_serviceErrorText = error;
|
||||
_view->showCriticalError(
|
||||
lang(lng_passport_form_error) + "\n" + error);
|
||||
@ -2036,7 +2107,13 @@ void FormController::showForm() {
|
||||
return;
|
||||
}
|
||||
if (!_password.salt.empty()) {
|
||||
_view->showAskPassword();
|
||||
if (!_savedPasswordValue.isEmpty()) {
|
||||
submitPassword(base::duplicate(_savedPasswordValue));
|
||||
} else if (const auto saved = Auth().data().passportCredentials()) {
|
||||
checkSavedPasswordSettings(*saved);
|
||||
} else {
|
||||
_view->showAskPassword();
|
||||
}
|
||||
} else {
|
||||
_view->showNoPassword();
|
||||
}
|
||||
|
@ -24,6 +24,12 @@ class Controller;
|
||||
|
||||
namespace Passport {
|
||||
|
||||
struct SavedCredentials {
|
||||
bytes::vector hashForAuth;
|
||||
bytes::vector hashForSecret;
|
||||
uint64 secretId = 0;
|
||||
};
|
||||
|
||||
class ViewController;
|
||||
|
||||
struct FormRequest {
|
||||
@ -247,11 +253,12 @@ public:
|
||||
UserData *bot() const;
|
||||
QString privacyPolicyUrl() const;
|
||||
std::vector<not_null<const Value*>> submitGetErrors();
|
||||
void submitPassword(const QString &password);
|
||||
void submitPassword(const QByteArray &password);
|
||||
void recoverPassword();
|
||||
rpl::producer<QString> passwordError() const;
|
||||
const PasswordSettings &passwordSettings() const;
|
||||
void reloadPassword();
|
||||
void reloadAndSubmitPassword(const QByteArray &password);
|
||||
void cancelPassword();
|
||||
|
||||
bool canAddScan(not_null<const Value*> value) const;
|
||||
@ -335,10 +342,11 @@ private:
|
||||
bool applyPassword(const MTPDaccount_password &settings);
|
||||
bool applyPassword(PasswordSettings &&settings);
|
||||
bytes::vector passwordHashForAuth(bytes::const_span password) const;
|
||||
void checkSavedPasswordSettings(const SavedCredentials &credentials);
|
||||
void validateSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password,
|
||||
bytes::const_span passwordHashForSecret,
|
||||
bytes::const_span passwordBytes,
|
||||
uint64 serverSecretId);
|
||||
void decryptValues();
|
||||
void decryptValue(Value &value);
|
||||
@ -413,6 +421,7 @@ private:
|
||||
mtpRequestId _passwordCheckRequestId = 0;
|
||||
|
||||
PasswordSettings _password;
|
||||
QByteArray _savedPasswordValue;
|
||||
Form _form;
|
||||
bool _cancelled = false;
|
||||
mtpRequestId _recoverRequestId = 0;
|
||||
|
@ -362,9 +362,8 @@ PanelController::PanelController(not_null<FormController*> form)
|
||||
, _scopes(ComputeScopes(_form)) {
|
||||
_form->secretReadyEvents(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_panel) {
|
||||
_panel->showForm();
|
||||
}
|
||||
ensurePanelCreated();
|
||||
_panel->showForm();
|
||||
}, lifetime());
|
||||
|
||||
_form->verificationNeeded(
|
||||
@ -435,7 +434,7 @@ void PanelController::submitForm() {
|
||||
}
|
||||
}
|
||||
|
||||
void PanelController::submitPassword(const QString &password) {
|
||||
void PanelController::submitPassword(const QByteArray &password) {
|
||||
_form->submitPassword(password);
|
||||
}
|
||||
|
||||
@ -467,7 +466,10 @@ void PanelController::setupPassword() {
|
||||
Expects(_panel != nullptr);
|
||||
|
||||
const auto &settings = _form->passwordSettings();
|
||||
Assert(settings.salt.empty());
|
||||
if (!settings.salt.empty()) {
|
||||
showAskPassword();
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr auto kRandomPart = 8;
|
||||
auto newPasswordSalt = QByteArray(
|
||||
@ -494,9 +496,22 @@ void PanelController::setupPassword() {
|
||||
notEmptyPassport,
|
||||
hint,
|
||||
newSecureSecretSalt));
|
||||
box->connect(box, &PasscodeBox::reloadPassword, [=] {
|
||||
box->newPasswordSet(
|
||||
) | rpl::filter([=](const QByteArray &password) {
|
||||
return !password.isEmpty();
|
||||
}) | rpl::start_with_next([=](const QByteArray &password) {
|
||||
_form->reloadAndSubmitPassword(password);
|
||||
}, box->lifetime());
|
||||
|
||||
rpl::merge(
|
||||
box->passwordReloadNeeded(),
|
||||
box->newPasswordSet(
|
||||
) | rpl::filter([=](const QByteArray &password) {
|
||||
return password.isEmpty();
|
||||
}) | rpl::map([] { return rpl::empty_value(); })
|
||||
) | rpl::start_with_next([=] {
|
||||
_form->reloadPassword();
|
||||
});
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void PanelController::cancelPasswordSubmit() {
|
||||
|
@ -68,7 +68,7 @@ public:
|
||||
not_null<UserData*> bot() const;
|
||||
QString privacyPolicyUrl() const;
|
||||
void submitForm();
|
||||
void submitPassword(const QString &password);
|
||||
void submitPassword(const QByteArray &password);
|
||||
void recoverPassword();
|
||||
rpl::producer<QString> passwordError() const;
|
||||
QString passwordHint() const;
|
||||
|
@ -93,7 +93,7 @@ void PanelAskPassword::hideError() {
|
||||
}
|
||||
|
||||
void PanelAskPassword::submit() {
|
||||
_controller->submitPassword(_password->getLastText());
|
||||
_controller->submitPassword(_password->getLastText().toUtf8());
|
||||
}
|
||||
|
||||
void PanelAskPassword::recover() {
|
||||
|
@ -83,7 +83,12 @@ void CloudPasswordState::onEdit() {
|
||||
_notEmptyPassport,
|
||||
_curPasswordHint,
|
||||
_newSecureSecretSalt));
|
||||
connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword()));
|
||||
rpl::merge(
|
||||
box->newPasswordSet() | rpl::map([] { return rpl::empty_value(); }),
|
||||
box->passwordReloadNeeded()
|
||||
) | rpl::start_with_next([=] {
|
||||
onReloadPassword();
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void CloudPasswordState::onTurnOff() {
|
||||
@ -113,7 +118,13 @@ void CloudPasswordState::onTurnOff() {
|
||||
_curPasswordHint,
|
||||
_newSecureSecretSalt,
|
||||
true));
|
||||
connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword()));
|
||||
rpl::merge(
|
||||
box->newPasswordSet(
|
||||
) | rpl::map([] { return rpl::empty_value(); }),
|
||||
box->passwordReloadNeeded()
|
||||
) | rpl::start_with_next([=] {
|
||||
onReloadPassword();
|
||||
}, box->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user