diff --git a/src/Cache.cpp b/src/Cache.cpp
index 58eb2630..0fdf8dd3 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -199,7 +199,6 @@ Cache::Cache(const QString &userId, QObject *parent)
, env_{nullptr}
, localUserId_{userId}
{
- setup();
connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection);
connect(
this,
@@ -212,6 +211,7 @@ Cache::Cache(const QString &userId, QObject *parent)
}
},
Qt::QueuedConnection);
+ setup();
}
void
@@ -308,7 +308,178 @@ Cache::setup()
txn.commit();
- databaseReady_ = true;
+ loadSecrets({
+ {mtx::secret_storage::secrets::cross_signing_master, false},
+ {mtx::secret_storage::secrets::cross_signing_self_signing, false},
+ {mtx::secret_storage::secrets::cross_signing_user_signing, false},
+ {mtx::secret_storage::secrets::megolm_backup_v1, false},
+ {"pickle_secret", true},
+ });
+}
+
+static void
+fatalSecretError()
+{
+ QMessageBox::critical(
+ ChatPage::instance(),
+ QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
+ QCoreApplication::translate(
+ "SecretStorage",
+ "Nheko could not connect to the secure storage to save encryption secrets to. This can "
+ "have multiple reasons. Check if your D-Bus service is running and you have configured a "
+ "service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If "
+ "you are having trouble, feel free to open an issue here: "
+ "https://github.com/Nheko-Reborn/nheko/issues"));
+
+ QCoreApplication::exit(1);
+ exit(1);
+}
+
+static QString
+secretName(std::string name, bool internal)
+{
+ auto settings = UserSettings::instance();
+ return (internal ? "nheko." : "matrix.") +
+ QString(
+ QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
+ .toBase64()) +
+ "." + QString::fromStdString(name);
+}
+
+void
+Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad)
+{
+ if (toLoad.empty()) {
+ this->databaseReady_ = true;
+ emit databaseReady();
+ return;
+ }
+
+ auto [name_, internal] = toLoad.front();
+
+ auto job = new QKeychain::ReadPasswordJob(QCoreApplication::applicationName());
+ job->setAutoDelete(true);
+ job->setInsecureFallback(true);
+ job->setSettings(UserSettings::instance()->qsettings());
+ auto name = secretName(name_, internal);
+ job->setKey(name);
+
+ connect(job,
+ &QKeychain::ReadPasswordJob::finished,
+ this,
+ [this, name, toLoad, job](QKeychain::Job *) mutable {
+ const QString secret = job->textData();
+ if (job->error() && job->error() != QKeychain::Error::EntryNotFound) {
+ nhlog::db()->error("Restoring secret '{}' failed ({}): {}",
+ name.toStdString(),
+ job->error(),
+ job->errorString().toStdString());
+
+ fatalSecretError();
+ }
+ if (secret.isEmpty()) {
+ nhlog::db()->debug("Restored empty secret '{}'.", name.toStdString());
+ } else {
+ std::unique_lock lock(secret_storage.mtx);
+ secret_storage.secrets[name.toStdString()] = secret.toStdString();
+ }
+
+ // load next secret
+ toLoad.erase(toLoad.begin());
+
+ // You can't start a job from the finish signal of a job.
+ QTimer::singleShot(0, [this, toLoad] { loadSecrets(toLoad); });
+ });
+ job->start();
+}
+
+std::optional<std::string>
+Cache::secret(const std::string name_, bool internal)
+{
+ auto name = secretName(name_, internal);
+ std::unique_lock lock(secret_storage.mtx);
+ if (auto secret = secret_storage.secrets.find(name.toStdString());
+ secret != secret_storage.secrets.end())
+ return secret->second;
+ else
+ return std::nullopt;
+}
+
+void
+Cache::storeSecret(const std::string name_, const std::string secret, bool internal)
+{
+ auto name = secretName(name_, internal);
+ {
+ std::unique_lock lock(secret_storage.mtx);
+ secret_storage.secrets[name.toStdString()] = secret;
+ }
+
+ auto settings = UserSettings::instance();
+ auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName());
+ job->setAutoDelete(true);
+ job->setInsecureFallback(true);
+ job->setSettings(UserSettings::instance()->qsettings());
+
+ job->setKey(name);
+
+ job->setTextData(QString::fromStdString(secret));
+
+ QObject::connect(
+ job,
+ &QKeychain::WritePasswordJob::finished,
+ this,
+ [name_, this](QKeychain::Job *job) {
+ if (job->error()) {
+ nhlog::db()->warn(
+ "Storing secret '{}' failed: {}", name_, job->errorString().toStdString());
+ fatalSecretError();
+ } else {
+ // if we emit the signal directly, qtkeychain breaks and won't execute new
+ // jobs. You can't start a job from the finish signal of a job.
+ QTimer::singleShot(0, [this, name_] { emit secretChanged(name_); });
+ nhlog::db()->info("Storing secret '{}' successful", name_);
+ }
+ },
+ Qt::ConnectionType::DirectConnection);
+ job->start();
+}
+
+void
+Cache::deleteSecret(const std::string name, bool internal)
+{
+ auto name_ = secretName(name, internal);
+ {
+ std::unique_lock lock(secret_storage.mtx);
+ secret_storage.secrets.erase(name_.toStdString());
+ }
+
+ auto settings = UserSettings::instance();
+ auto job = new QKeychain::DeletePasswordJob(QCoreApplication::applicationName());
+ job->setAutoDelete(true);
+ job->setInsecureFallback(true);
+ job->setSettings(UserSettings::instance()->qsettings());
+
+ job->setKey(name_);
+
+ job->connect(
+ job, &QKeychain::Job::finished, this, [this, name]() { emit secretChanged(name); });
+ job->start();
+}
+
+std::string
+Cache::pickleSecret()
+{
+ if (pickle_secret_.empty()) {
+ auto s = secret("pickle_secret", true);
+ if (!s) {
+ this->pickle_secret_ = mtx::client::utils::random_token(64, true);
+ storeSecret("pickle_secret", pickle_secret_, true);
+ } else {
+ this->pickle_secret_ = *s;
+ }
+ }
+
+ return pickle_secret_;
}
void
@@ -758,144 +929,6 @@ Cache::backupVersion()
}
}
-static void
-fatalSecretError()
-{
- QMessageBox::critical(
- ChatPage::instance(),
- QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
- QCoreApplication::translate(
- "SecretStorage",
- "Nheko could not connect to the secure storage to save encryption secrets to. This can "
- "have multiple reasons. Check if your D-Bus service is running and you have configured a "
- "service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If "
- "you are having trouble, feel free to open an issue here: "
- "https://github.com/Nheko-Reborn/nheko/issues"));
-
- QCoreApplication::exit(1);
- exit(1);
-}
-
-void
-Cache::storeSecret(const std::string name, const std::string secret, bool internal)
-{
- auto settings = UserSettings::instance();
- auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName());
- job->setAutoDelete(true);
- job->setInsecureFallback(true);
- job->setSettings(UserSettings::instance()->qsettings());
-
- job->setKey(
- (internal ? "nheko." : "matrix.") +
- QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
- .toBase64()) +
- "." + QString::fromStdString(name));
-
- job->setTextData(QString::fromStdString(secret));
-
- QObject::connect(
- job,
- &QKeychain::WritePasswordJob::finished,
- this,
- [name, this](QKeychain::Job *job) {
- if (job->error()) {
- nhlog::db()->warn(
- "Storing secret '{}' failed: {}", name, job->errorString().toStdString());
- fatalSecretError();
- } else {
- // if we emit the signal directly, qtkeychain breaks and won't execute new
- // jobs. You can't start a job from the finish signal of a job.
- QTimer::singleShot(100, [this, name] { emit secretChanged(name); });
- nhlog::db()->info("Storing secret '{}' successful", name);
- }
- },
- Qt::ConnectionType::DirectConnection);
- job->start();
-}
-
-void
-Cache::deleteSecret(const std::string name, bool internal)
-{
- auto settings = UserSettings::instance();
- QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
- job.setAutoDelete(false);
- job.setInsecureFallback(true);
- job.setSettings(UserSettings::instance()->qsettings());
-
- job.setKey(
- (internal ? "nheko." : "matrix.") +
- QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
- .toBase64()) +
- "." + QString::fromStdString(name));
-
- // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
- // time!
- QEventLoop loop;
- job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
- job.start();
- loop.exec();
-
- emit secretChanged(name);
-}
-
-std::optional<std::string>
-Cache::secret(const std::string name, bool internal)
-{
- auto settings = UserSettings::instance();
- QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
- job.setAutoDelete(false);
- job.setInsecureFallback(true);
- job.setSettings(UserSettings::instance()->qsettings());
-
- job.setKey(
- (internal ? "nheko." : "matrix.") +
- QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
- .toBase64()) +
- "." + QString::fromStdString(name));
-
- // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
- // time!
- QEventLoop loop;
- job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
- job.start();
- loop.exec();
-
- const QString secret = job.textData();
- if (job.error()) {
- if (job.error() == QKeychain::Error::EntryNotFound)
- return std::nullopt;
- nhlog::db()->error("Restoring secret '{}' failed ({}): {}",
- name,
- job.error(),
- job.errorString().toStdString());
-
- fatalSecretError();
- return std::nullopt;
- }
- if (secret.isEmpty()) {
- nhlog::db()->debug("Restored empty secret '{}'.", name);
- return std::nullopt;
- }
-
- return secret.toStdString();
-}
-
-std::string
-Cache::pickleSecret()
-{
- if (pickle_secret_.empty()) {
- auto s = secret("pickle_secret", true);
- if (!s) {
- this->pickle_secret_ = mtx::client::utils::random_token(64, true);
- storeSecret("pickle_secret", pickle_secret_, true);
- } else {
- this->pickle_secret_ = *s;
- }
- }
-
- return pickle_secret_;
-}
-
void
Cache::removeInvite(lmdb::txn &txn, const std::string &room_id)
{
@@ -3182,9 +3215,11 @@ Cache::clearTimeline(const std::string &room_id)
break;
}
- do {
- lmdb::cursor_del(msgCursor);
- } while (msgCursor.get(indexVal, val, MDB_PREV));
+ if (!start) {
+ do {
+ lmdb::cursor_del(msgCursor);
+ } while (msgCursor.get(indexVal, val, MDB_PREV));
+ }
cursor.close();
msgCursor.close();
@@ -4066,8 +4101,9 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
(void)status;
emit verificationStatusChanged(user);
}
+ } else {
+ emit verificationStatusChanged(user_id);
}
- emit verificationStatusChanged(user_id);
}
}
@@ -4276,8 +4312,9 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
(void)status;
emit verificationStatusChanged(user);
}
+ } else {
+ emit verificationStatusChanged(user_id);
}
- emit verificationStatusChanged(user_id);
}
void
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index b7461848..f56c8685 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -141,6 +141,14 @@ struct VerificationStorage
std::mutex verification_storage_mtx;
};
+//! In memory cache of verification status
+struct SecretsStorage
+{
+ //! secret name -> secret
+ std::map<std::string, std::string> secrets;
+ std::mutex mtx;
+};
+
// this will store the keys of the user with whom a encrypted room is shared with
struct UserKeyCache
{
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 651d73d7..a529bc37 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -304,6 +304,7 @@ public:
return get_skey(a).compare(get_skey(b));
}
+
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
@@ -312,8 +313,11 @@ signals:
void verificationStatusChanged(const std::string &userid);
void selfVerificationStatusChanged();
void secretChanged(const std::string name);
+ void databaseReady();
private:
+ void loadSecrets(std::vector<std::pair<std::string, bool>> toLoad);
+
//! Save an invited room.
void saveInvite(lmdb::txn &txn,
lmdb::dbi &statesdb,
@@ -684,6 +688,7 @@ private:
std::string pickle_secret_;
VerificationStorage verification_storage;
+ SecretsStorage secret_storage;
bool databaseReady_ = false;
};
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index d262387c..77a8edcf 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -316,6 +316,66 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
try {
cache::init(userid);
+ connect(cache::client(), &Cache::databaseReady, this, [this]() {
+ nhlog::db()->info("database ready");
+
+ const bool isInitialized = cache::isInitialized();
+ const auto cacheVersion = cache::formatVersion();
+
+ try {
+ if (!isInitialized) {
+ cache::setCurrentFormat();
+ } else {
+ if (cacheVersion == cache::CacheVersion::Current) {
+ loadStateFromCache();
+ return;
+ } else if (cacheVersion == cache::CacheVersion::Older) {
+ if (!cache::runMigrations()) {
+ QMessageBox::critical(
+ this,
+ tr("Cache migration failed!"),
+ tr("Migrating the cache to the current version failed. "
+ "This can have different reasons. Please open an "
+ "issue and try to use an older version in the mean "
+ "time. Alternatively you can try deleting the cache "
+ "manually."));
+ QCoreApplication::quit();
+ }
+ loadStateFromCache();
+ return;
+ } else if (cacheVersion == cache::CacheVersion::Newer) {
+ QMessageBox::critical(
+ this,
+ tr("Incompatible cache version"),
+ tr("The cache on your disk is newer than this version of Nheko "
+ "supports. Please update Nheko or clear your cache."));
+ QCoreApplication::quit();
+ return;
+ }
+ }
+
+ // It's the first time syncing with this device
+ // There isn't a saved olm account to restore.
+ nhlog::crypto()->info("creating new olm account");
+ olm::client()->create_new_account();
+ cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret()));
+ } catch (const lmdb::error &e) {
+ nhlog::crypto()->critical("failed to save olm account {}", e.what());
+ emit dropToLoginPageCb(QString::fromStdString(e.what()));
+ return;
+ } catch (const mtx::crypto::olm_exception &e) {
+ nhlog::crypto()->critical("failed to create new olm account {}", e.what());
+ emit dropToLoginPageCb(QString::fromStdString(e.what()));
+ return;
+ }
+
+ getProfileInfo();
+ getBackupVersion();
+ tryInitialSync();
+ callManager_->refreshTurnServer();
+ emit MainWindow::instance()->reload();
+ });
+
connect(cache::client(),
&Cache::newReadReceipts,
view_manager_,
@@ -326,66 +386,10 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
¬ificationsManager,
&NotificationsManager::removeNotification);
- const bool isInitialized = cache::isInitialized();
- const auto cacheVersion = cache::formatVersion();
-
- callManager_->refreshTurnServer();
-
- if (!isInitialized) {
- cache::setCurrentFormat();
- } else {
- if (cacheVersion == cache::CacheVersion::Current) {
- loadStateFromCache();
- return;
- } else if (cacheVersion == cache::CacheVersion::Older) {
- if (!cache::runMigrations()) {
- QMessageBox::critical(this,
- tr("Cache migration failed!"),
- tr("Migrating the cache to the current version failed. "
- "This can have different reasons. Please open an "
- "issue and try to use an older version in the mean "
- "time. Alternatively you can try deleting the cache "
- "manually."));
- QCoreApplication::quit();
- }
- loadStateFromCache();
- return;
- } else if (cacheVersion == cache::CacheVersion::Newer) {
- QMessageBox::critical(
- this,
- tr("Incompatible cache version"),
- tr("The cache on your disk is newer than this version of Nheko "
- "supports. Please update or clear your cache."));
- QCoreApplication::quit();
- return;
- }
- }
-
} catch (const lmdb::error &e) {
nhlog::db()->critical("failure during boot: {}", e.what());
- cache::deleteData();
- nhlog::net()->info("falling back to initial sync");
+ emit dropToLoginPageCb(tr("Failed to open database, logging out!"));
}
-
- try {
- // It's the first time syncing with this device
- // There isn't a saved olm account to restore.
- nhlog::crypto()->info("creating new olm account");
- olm::client()->create_new_account();
- cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret()));
- } catch (const lmdb::error &e) {
- nhlog::crypto()->critical("failed to save olm account {}", e.what());
- emit dropToLoginPageCb(QString::fromStdString(e.what()));
- return;
- } catch (const mtx::crypto::olm_exception &e) {
- nhlog::crypto()->critical("failed to create new olm account {}", e.what());
- emit dropToLoginPageCb(QString::fromStdString(e.what()));
- return;
- }
-
- getProfileInfo();
- getBackupVersion();
- tryInitialSync();
}
void
@@ -522,6 +526,8 @@ ChatPage::tryInitialSync()
for (const auto &entry : res.one_time_key_counts)
nhlog::net()->info("uploaded {} {} one-time keys", entry.second, entry.first);
+ cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()});
+
startInitialSync();
});
}
@@ -1139,7 +1145,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
if (!decrypted.empty()) {
cache::storeSecret(secretName, decrypted);
- if (deviceKeys &&
+ if (deviceKeys && deviceKeys->device_keys.count(http::client()->device_id()) &&
secretName == mtx::secret_storage::secrets::cross_signing_self_signing) {
auto myKey = deviceKeys->device_keys.at(http::client()->device_id());
if (myKey.user_id == http::client()->user_id().to_string() &&
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 340709a6..52567289 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -950,7 +950,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle."));
boxWrap(tr("Use identicons"),
useIdenticon_,
- tr("Display an identicon instead of a letter when a user has not set an avatar."));
+ tr("Display an identicon instead of a letter when no avatar is set."));
boxWrap(tr("Group's sidebar"),
groupViewToggle_,
tr("Show a column containing groups and tags next to the room list."));
@@ -1098,7 +1098,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
backupSecretCached,
tr("The key to decrypt online key backups. If it is cached, you can enable online "
"key backup to store encryption keys securely encrypted on the server."));
- updateSecretStatus();
+ // updateSecretStatus();
auto scrollArea_ = new QScrollArea{this};
scrollArea_->setFrameShape(QFrame::NoFrame);
diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp
index ebb6b548..c4f4f196 100644
--- a/src/encryption/SelfVerificationStatus.cpp
+++ b/src/encryption/SelfVerificationStatus.cpp
@@ -4,6 +4,8 @@
#include "SelfVerificationStatus.h"
+#include <QApplication>
+
#include "Cache_p.h"
#include "ChatPage.h"
#include "Logging.h"
@@ -18,13 +20,13 @@
SelfVerificationStatus::SelfVerificationStatus(QObject *o)
: QObject(o)
{
- connect(MainWindow::instance(), &MainWindow::reload, this, [this] {
+ connect(ChatPage::instance(), &ChatPage::contentLoaded, this, [this] {
connect(cache::client(),
&Cache::selfVerificationStatusChanged,
this,
&SelfVerificationStatus::invalidate,
Qt::UniqueConnection);
- invalidate();
+ cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()});
});
}
@@ -82,7 +84,7 @@ SelfVerificationStatus::setupCrosssigning(bool useSSSS, QString password, bool u
ssss = olm::client()->create_ssss_key(password.toStdString());
if (!ssss) {
nhlog::crypto()->critical("Failed to setup secure server side secret storage!");
- emit setupFailed(tr("Failed to create keys secure server side secret storage!"));
+ emit setupFailed(tr("Failed to create keys for secure server side secret storage!"));
return;
}
@@ -259,15 +261,29 @@ SelfVerificationStatus::invalidate()
using namespace mtx::secret_storage;
nhlog::db()->info("Invalidating self verification status");
+ if (!cache::isInitialized()) {
+ nhlog::db()->warn("SelfVerificationStatus: cache not initialized");
+ return;
+ }
+
this->hasSSSS_ = false;
emit hasSSSSChanged();
auto keys = cache::client()->userKeys(http::client()->user_id().to_string());
if (!keys || keys->device_keys.find(http::client()->device_id()) == keys->device_keys.end()) {
+ if (keys && (keys->seen_device_ids.count(http::client()->device_id()) ||
+ keys->seen_device_keys.count(olm::client()->identity_keys().curve25519))) {
+ emit ChatPage::instance()->dropToLoginPageCb(
+ tr("Identity key changed. This breaks E2EE, so logging out."));
+ return;
+ }
+
cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()});
- cache::client()->query_keys(http::client()->user_id().to_string(),
- [](const UserKeyCache &, mtx::http::RequestErr) {});
- return;
+
+ QTimer::singleShot(1'000, [] {
+ cache::client()->query_keys(http::client()->user_id().to_string(),
+ [](const UserKeyCache &, mtx::http::RequestErr) {});
+ });
}
if (keys->master_keys.keys.empty()) {
diff --git a/src/main.cpp b/src/main.cpp
index f6373d2a..f2364c59 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -174,7 +174,7 @@ main(int argc, char *argv[])
SingleApplication::Mode::ExcludeAppVersion |
SingleApplication::Mode::SecondaryNotification,
100,
- userdata);
+ userdata == "default" ? "" : userdata);
QCommandLineParser parser;
parser.addHelpOption();
@@ -199,8 +199,9 @@ main(int argc, char *argv[])
// This check needs to happen _after_ process(), so that we actually print help for --help when
// Nheko is already running.
if (app.isSecondary()) {
- nhlog::ui()->info("Sending Matrix URL to main application: {}", matrixUri.toStdString());
- // open uri in main instance
+ std::cout << "Sending Matrix URL to main application: " << matrixUri.toStdString()
+ << std::endl;
+ // open uri in main instance
app.sendMessage(matrixUri.toUtf8());
return 0;
}
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index a1f4c67f..7bb985f8 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -447,7 +447,8 @@ EventStore::edits(const std::string &event_id)
auto event_ids = cache::client()->relatedEvents(room_id_, event_id);
auto original_event = get(event_id, "", false, false);
- if (!original_event)
+ if (!original_event ||
+ std::holds_alternative<mtx::events::RoomEvent<mtx::events::msg::Redacted>>(*original_event))
return {};
auto original_sender = mtx::accessors::sender(*original_event);
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 9b857dcf..1730f9fd 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -77,6 +77,7 @@ public:
mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
QVariantList reactions(const std::string &event_id);
+ std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
olm::DecryptionErrorCode decryptionError(std::string id);
void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
bool manual);
@@ -120,7 +121,6 @@ public slots:
void enableKeyRequests(bool suppressKeyRequests_);
private:
- std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
olm::DecryptionResult *decryptEvent(
const IdIndex &idx,
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 0e5ce510..aa7a68f3 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -344,6 +344,19 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
[](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
Qt::QueuedConnection);
+ connect(this, &TimelineModel::dataAtIdChanged, this, [this](QString id) {
+ relatedEventCacheBuster++;
+
+ auto idx = idToIndex(id);
+ if (idx != -1) {
+ auto pos = index(idx);
+ nhlog::ui()->debug("data changed at {}", id.toStdString());
+ emit dataChanged(pos, pos);
+ } else {
+ nhlog::ui()->debug("id not found {}", id.toStdString());
+ }
+ });
+
connect(this,
&TimelineModel::newMessageToSend,
this,
@@ -1095,7 +1108,8 @@ TimelineModel::showReadReceipts(QString id)
void
TimelineModel::redactEvent(QString id)
{
- if (!id.isEmpty())
+ if (!id.isEmpty()) {
+ auto edits = events.edits(id.toStdString());
http::client()->redact_event(
room_id_.toStdString(),
id.toStdString(),
@@ -1106,8 +1120,26 @@ TimelineModel::redactEvent(QString id)
return;
}
- emit eventRedacted(id);
+ emit dataAtIdChanged(id);
});
+
+ // redact all edits to prevent leaks
+ for (const auto &e : edits) {
+ auto id_ = mtx::accessors::event_id(e);
+ http::client()->redact_event(
+ room_id_.toStdString(),
+ id_,
+ [this, id, id_](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ emit redactionFailed(tr("Message redaction failed: %1")
+ .arg(QString::fromStdString(err->matrix_error.error)));
+ return;
+ }
+
+ emit dataAtIdChanged(id);
+ });
+ }
+ }
}
int
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index f16529e2..af067476 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -367,9 +367,9 @@ private slots:
void scrollTimerEvent();
signals:
+ void dataAtIdChanged(QString id);
void currentIndexChanged(int index);
void redactionFailed(QString id);
- void eventRedacted(QString id);
void mediaCached(QString mxcUrl, QString cacheUrl);
void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
void typingUsersChanged(std::vector<QString> users);
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 94e6a0d7..84aa2c90 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -248,7 +248,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_);
qmlRegisterSingletonType<SelfVerificationStatus>(
"im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
- return new SelfVerificationStatus();
+ auto ptr = new SelfVerificationStatus();
+ QObject::connect(ChatPage::instance(),
+ &ChatPage::initializeEmptyViews,
+ ptr,
+ &SelfVerificationStatus::invalidate);
+ return ptr;
});
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index b5a16f43..72b1d8e6 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -188,6 +188,7 @@ UserProfile::fetchDeviceList(const QString &userID)
nhlog::net()->warn("failed to query device keys: {},{}",
mtx::errors::to_string(err->matrix_error.errcode),
static_cast<int>(err->status_code));
+ return;
}
// Ensure local key cache is up to date
@@ -201,6 +202,7 @@ UserProfile::fetchDeviceList(const QString &userID)
nhlog::net()->warn("failed to query device keys: {},{}",
mtx::errors::to_string(err->matrix_error.errcode),
static_cast<int>(err->status_code));
+ return;
}
emit verificationStatiChanged();
|