aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/accountregistry.cpp55
-rw-r--r--lib/accountregistry.h31
-rw-r--r--lib/connection.cpp39
3 files changed, 118 insertions, 7 deletions
diff --git a/lib/accountregistry.cpp b/lib/accountregistry.cpp
index 616b54b4..9b148b51 100644
--- a/lib/accountregistry.cpp
+++ b/lib/accountregistry.cpp
@@ -5,6 +5,7 @@
#include "accountregistry.h"
#include "connection.h"
+#include <QtCore/QCoreApplication>
using namespace Quotient;
@@ -15,6 +16,7 @@ void AccountRegistry::add(Connection* a)
beginInsertRows(QModelIndex(), size(), size());
push_back(a);
endInsertRows();
+ emit accountCountChanged();
}
void AccountRegistry::drop(Connection* a)
@@ -54,8 +56,6 @@ QHash<int, QByteArray> AccountRegistry::roleNames() const
return { { AccountRole, "connection" } };
}
-
-
Connection* AccountRegistry::get(const QString& userId)
{
for (const auto &connection : *this) {
@@ -64,3 +64,54 @@ Connection* AccountRegistry::get(const QString& userId)
}
return nullptr;
}
+
+QKeychain::ReadPasswordJob* AccountRegistry::loadAccessTokenFromKeychain(const QString& userId)
+{
+ qCDebug(MAIN) << "Reading access token from keychain for" << userId;
+ auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
+ job->setKey(userId);
+ job->start();
+
+ return job;
+}
+
+void AccountRegistry::invokeLogin()
+{
+ const auto accounts = SettingsGroup("Accounts").childGroups();
+ for (const auto& accountId : accounts) {
+ AccountSettings account{accountId};
+ m_accountsLoading += accountId;
+ emit accountsLoadingChanged();
+
+ if (!account.homeserver().isEmpty()) {
+ auto accessTokenLoadingJob = loadAccessTokenFromKeychain(account.userId());
+ connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob]() {
+ AccountSettings account{accountId};
+ if (accessTokenLoadingJob->error() != QKeychain::Error::NoError) {
+ emit keychainError(accessTokenLoadingJob->error());
+ return;
+ }
+
+ auto connection = new Connection(account.homeserver());
+ connect(connection, &Connection::connected, this, [connection] {
+ connection->loadState();
+ connection->setLazyLoading(true);
+
+ connection->syncLoop();
+ });
+ connect(connection, &Connection::loginError, this, [this, connection](const QString& error, const QString& details) {
+ emit loginError(connection, error, details);
+ });
+ connect(connection, &Connection::resolveError, this, [this, connection](QString error) {
+ emit resolveError(connection, error);
+ });
+ connection->assumeIdentity(account.userId(), accessTokenLoadingJob->binaryData(), account.deviceId());
+ });
+ }
+ }
+}
+
+QStringList AccountRegistry::accountsLoading() const
+{
+ return m_accountsLoading;
+}
diff --git a/lib/accountregistry.h b/lib/accountregistry.h
index 2f6dffdf..38cfe6c6 100644
--- a/lib/accountregistry.h
+++ b/lib/accountregistry.h
@@ -5,15 +5,31 @@
#pragma once
#include "quotient_export.h"
+#include "settings.h"
#include <QtCore/QAbstractListModel>
+#if QT_VERSION_MAJOR >= 6
+# include <qt6keychain/keychain.h>
+#else
+# include <qt5keychain/keychain.h>
+#endif
+
+namespace QKeychain {
+class ReadPasswordJob;
+}
+
namespace Quotient {
class Connection;
class QUOTIENT_API AccountRegistry : public QAbstractListModel,
private QVector<Connection*> {
Q_OBJECT
+ /// Number of accounts that are currently fully loaded
+ Q_PROPERTY(int accountCount READ rowCount NOTIFY accountCountChanged)
+ /// List of accounts that are currently in some stage of being loaded (Reading token from keychain, trying to contact server, etc).
+ /// Can be used to inform the user or to show a login screen if size() == 0 and no accounts are loaded
+ Q_PROPERTY(QStringList accountsLoading READ accountsLoading NOTIFY accountsLoadingChanged)
public:
using const_iterator = QVector::const_iterator;
using const_reference = QVector::const_reference;
@@ -52,6 +68,21 @@ public:
[[nodiscard]] int rowCount(
const QModelIndex& parent = QModelIndex()) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
+
+ QStringList accountsLoading() const;
+
+ void invokeLogin();
+Q_SIGNALS:
+ void accountCountChanged();
+ void accountsLoadingChanged();
+
+ void keychainError(QKeychain::Error error);
+ void loginError(Connection* connection, QString message, QString details);
+ void resolveError(Connection* connection, QString error);
+
+private:
+ QKeychain::ReadPasswordJob* loadAccessTokenFromKeychain(const QString &userId);
+ QStringList m_accountsLoading;
};
inline QUOTIENT_API AccountRegistry Accounts {};
diff --git a/lib/connection.cpp b/lib/connection.cpp
index ab4a7dea..7e36b3c9 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -42,12 +42,12 @@
# include "e2ee/qolmutility.h"
# include "e2ee/qolmutils.h"
-# if QT_VERSION_MAJOR >= 6
-# include <qt6keychain/keychain.h>
-# else
-# include <qt5keychain/keychain.h>
-# endif
#endif // Quotient_E2EE_ENABLED
+#if QT_VERSION_MAJOR >= 6
+# include <qt6keychain/keychain.h>
+#else
+# include <qt5keychain/keychain.h>
+#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
# include <QtCore/QCborValue>
@@ -386,6 +386,32 @@ public:
const QString& targetDeviceId, const QByteArray& sessionId,
const QByteArray& sessionKey) const;
#endif
+
+ void saveAccessTokenToKeychain()
+ {
+ qCDebug(MAIN) << "Saving access token to keychain for" << q->userId();
+ auto job = new QKeychain::WritePasswordJob(qAppName());
+ job->setAutoDelete(false);
+ job->setKey(q->userId());
+ job->setBinaryData(data->accessToken());
+ job->start();
+ //TODO error handling
+ }
+
+ void removeAccessTokenFromKeychain()
+ {
+ qCDebug(MAIN) << "Removing access token from keychain for" << q->userId();
+ auto job = new QKeychain::DeletePasswordJob(qAppName());
+ job->setAutoDelete(true);
+ job->setKey(q->userId());
+ job->start();
+
+ auto pickleJob = new QKeychain::DeletePasswordJob(qAppName());
+ pickleJob->setAutoDelete(true);
+ pickleJob->setKey(q->userId() + "-Pickle"_ls);
+ pickleJob->start();
+ //TODO error handling
+ }
};
Connection::Connection(const QUrl& server, QObject* parent)
@@ -564,6 +590,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs)
data->setToken(loginJob->accessToken().toLatin1());
data->setDeviceId(loginJob->deviceId());
completeSetup(loginJob->userId());
+ saveAccessTokenToKeychain();
#ifndef Quotient_E2EE_ENABLED
qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off.";
#else // Quotient_E2EE_ENABLED
@@ -702,6 +729,8 @@ void Connection::logout()
disconnect(d->syncLoopConnection);
d->data->setToken({});
emit loggedOut();
+ SettingsGroup("Accounts").remove(userId());
+ d->removeAccessTokenFromKeychain();
deleteLater();
} else { // logout() somehow didn't proceed - restore the session state
emit stateChanged();