From 0feaf09d83b47bf15361891de5aebccf6d407d06 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Fri, 8 May 2026 14:44:40 +0200 Subject: [PATCH] Auth token is stored in settings and deleted if a request is not authorized (with automatic re-login). Failed request need to be resend. --- data/settingshandler.cpp | 44 ++++++++++++++++++++++++++ data/settingshandler.h | 3 ++ formats/jsonparser.cpp | 24 ++++++++++++++ formats/jsonparser.h | 5 +++ genericcore.cpp | 57 ++++++++++++++++++++++++++++++++-- genericcore.h | 5 +++ network/apiroutes.h | 2 ++ network/servercommunicator.cpp | 48 +++++++++++++++++++++++++--- network/servercommunicator.h | 7 +++++ 9 files changed, 189 insertions(+), 6 deletions(-) diff --git a/data/settingshandler.cpp b/data/settingshandler.cpp index eb11006..691854b 100644 --- a/data/settingshandler.cpp +++ b/data/settingshandler.cpp @@ -40,4 +40,48 @@ void SettingsHandler::saveSettings(QVariantMap settingMap, QString group) { settings.sync(); } +void SettingsHandler::deleteSettings(QStringList keys, QString group) { + qInfo() << "deleting settings..."; + + QSettings settings; + if (!group.isEmpty()) { + qDebug() << "starting group:" << group; + settings.beginGroup(group); + } + + foreach (QString key, keys) { + qDebug() << "removing:" << key; + settings.remove(key); + } + if (!group.isEmpty()) { + settings.endGroup(); + } + + settings.sync(); +} + +QVariantMap SettingsHandler::getChangeset(QVariantMap newSettings, QString group) { + const QVariantMap oldSettings = getSettings(group); + + QVariantMap result; + + for (QVariantMap::const_iterator iter = newSettings.begin(); iter != newSettings.end(); ++iter) { + qDebug() << iter.key() << iter.value(); + QString key = iter.key(); + QVariant newValue = iter.value(); + QVariant oldValue = oldSettings.value(key); + + if (oldValue == newValue) { + qInfo() << "oldValue == newValue -> ignoring..."; + } else { + const QString debugString = + QString("oldValue != newValue -> adding '%1' to changeset...").arg(key); + qInfo() << debugString; + result.insert(key, newValue); + } + } + + return result; +} + SettingsHandler::SettingsHandler() {} diff --git a/data/settingshandler.h b/data/settingshandler.h index a636cf6..3488939 100644 --- a/data/settingshandler.h +++ b/data/settingshandler.h @@ -7,6 +7,9 @@ class SettingsHandler { public: static QVariantMap getSettings(QString group = ""); static void saveSettings(QVariantMap settingMap, QString group = ""); + static void deleteSettings(QStringList keys, QString group = ""); + + static QVariantMap getChangeset(QVariantMap newSettings, QString group = ""); private: SettingsHandler(); diff --git a/formats/jsonparser.cpp b/formats/jsonparser.cpp index 559ea20..b41c1e2 100644 --- a/formats/jsonparser.cpp +++ b/formats/jsonparser.cpp @@ -74,6 +74,30 @@ QJsonObject JsonParser::itemValuesToJsonObject(const ModelItemValues& itemValues return result; } +QByteArray JsonParser::userCredentialsToJsonDocument(const QString email, const QString password) { + QJsonDocument jsonDoc; + QJsonObject rootObject; + + QJsonObject userObject; + userObject.insert("email", email); + userObject.insert("password", password); + + rootObject.insert("user", userObject); + jsonDoc.setObject(rootObject); + + return jsonDoc.toJson(QJsonDocument::Compact); +} + +QVariant JsonParser::getValueFromJson(const QByteArray& jsonData, + const QString key, + const QString objectName) { + QJsonDocument doc = QJsonDocument::fromJson(jsonData); + QJsonObject rootObject = doc.object(); + QJsonObject userObject = rootObject.value(objectName).toObject(); + + return userObject.value(key); +} + JsonParser::JsonParser() {} QJsonArray JsonParser::extractItemArray(const QJsonDocument& doc, const QString& objectName) { diff --git a/formats/jsonparser.h b/formats/jsonparser.h index ba2a95f..fa805af 100644 --- a/formats/jsonparser.h +++ b/formats/jsonparser.h @@ -20,6 +20,11 @@ class JsonParser { const QString& objectName = ""); static QJsonObject itemValuesToJsonObject(const ModelItemValues& itemValues); + static QByteArray userCredentialsToJsonDocument(const QString email, const QString password); + static QVariant getValueFromJson(const QByteArray& jsonData, + const QString key, + const QString objectName = ""); + private: explicit JsonParser(); diff --git a/genericcore.cpp b/genericcore.cpp index a3a3ea1..c5d7f76 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -14,6 +14,7 @@ #include "constants.h" #include "data/filehandler.h" #include "data/settingshandler.h" +#include "formats/jsonparser.h" #include "model/generalsortfiltermodel.h" #include "model/metadata.h" #include "model/tablemodel.h" @@ -126,6 +127,24 @@ QVariantMap GenericCore::getSettings(QString group) const { } void GenericCore::applySettings(QVariantMap settingMap, QString group) { + const QVariantMap changeset = SettingsHandler::getChangeset(settingMap, group); + + if (changeset.isEmpty()) { + return; + } + + if (group == "Server") { + const bool urlChanged = changeset.contains("url"); + const bool emailChanged = changeset.contains("email"); + const bool passwordChanged = changeset.contains("password"); + if (urlChanged || emailChanged || passwordChanged) { + if (!changeset.contains("authToken")) { + qInfo() << "Account settings changed, but no new token present. Deleting old token..."; + SettingsHandler::deleteSettings({"authToken"}, "Server"); + } + } + } + SettingsHandler::saveSettings(settingMap, group); if (group == "Server") { @@ -158,6 +177,30 @@ void GenericCore::saveItems() { } } +void GenericCore::onLoginSuccessful(const QByteArray jsonData) { + emit displayStatusMessage("Login successful."); + qInfo() << "Storing auth token..."; + QString token = JsonParser::getValueFromJson(jsonData, "token", "user").toString(); + SettingsHandler::saveSettings({{"authToken", token}}, "Server"); + applyServerConfiguration(); +} + +void GenericCore::onLoginFailure(const QString errorString) { + emit displayStatusMessage(QString("Error: %1").arg(errorString)); +} + +void GenericCore::onNotAuthorized(const QString /*path*/) { + const QVariantMap serverSettings = SettingsHandler::getSettings("Server"); + const QString tokenValue = serverSettings.value("authToken").toString(); + if (!tokenValue.isEmpty()) { + SettingsHandler::deleteSettings({"authToken"}, "Server"); + displayStatusMessage("Not authorized! Deleted token. Please retry."); + } else { + displayStatusMessage("Not authorized! But no token was present. Please check your settings."); + } + applyServerConfiguration(); +} + void GenericCore::onSendItemTriggered(const QByteArray& jsonData) { m_serverCommunicator->sendItem(jsonData); } @@ -258,6 +301,14 @@ void GenericCore::setupServerCommunication() { &ServerCommunicator::deleteItem); /// response connections + connect(m_serverCommunicator.get(), &ServerCommunicator::loginSuccessful, this, + &GenericCore::onLoginSuccessful); + connect(m_serverCommunicator.get(), &ServerCommunicator::loginFailure, this, + &GenericCore::onLoginFailure); + + connect(m_serverCommunicator.get(), &ServerCommunicator::notAuthorized, this, + &GenericCore::onNotAuthorized); + connect(m_serverCommunicator.get(), &ServerCommunicator::itemsFetched, this, &GenericCore::onItemsFetched); connect(m_serverCommunicator.get(), &ServerCommunicator::itemsFetchFailure, this, @@ -277,8 +328,10 @@ void GenericCore::setupServerCommunication() { void GenericCore::applyServerConfiguration() { const QVariantMap serverSettings = SettingsHandler::getSettings("Server"); const QString urlValue = serverSettings.value("url").toString(); - // NEXT if urlValue is empty -> remove authToken from settings? - if (!urlValue.isEmpty()) { + if (urlValue.isEmpty()) { + SettingsHandler::deleteSettings({"authToken"}, "Server"); + } else { + /// urlValue in NOT empty const QString emailValue = serverSettings.value("email").toString(); const QString passwordValue = serverSettings.value("password").toString(); const QString authTokenValue = serverSettings.value("authToken").toString(); diff --git a/genericcore.h b/genericcore.h index 6711b7c..22df81a 100644 --- a/genericcore.h +++ b/genericcore.h @@ -39,6 +39,11 @@ class GenericCore : public QObject { public slots: void saveItems(); + void onLoginSuccessful(const QByteArray jsonData); + void onLoginFailure(const QString errorString); + + void onNotAuthorized(const QString /*path*/); + void onSendItemTriggered(const QByteArray& jsonData); void onItemsFetched(const QByteArray jsonData); void onItemsFetchFailure(const QString errorString); diff --git a/network/apiroutes.h b/network/apiroutes.h index e89da3a..12b25d6 100644 --- a/network/apiroutes.h +++ b/network/apiroutes.h @@ -7,6 +7,8 @@ static const QString apiPrefix = "/api/"; +static const QString ROUTE_LOG_IN = apiPrefix + "log_in"; + static const QString ROUTE_ITEMS = apiPrefix + "items"; #endif // APIROUTES_H diff --git a/network/servercommunicator.cpp b/network/servercommunicator.cpp index b3c10b2..1684bf1 100644 --- a/network/servercommunicator.cpp +++ b/network/servercommunicator.cpp @@ -6,6 +6,8 @@ #include #include +#include "../formats/jsonparser.h" + using namespace Qt::StringLiterals; ServerCommunicator::ServerCommunicator(QObject* parent) @@ -46,7 +48,14 @@ void ServerCommunicator::setServerConfiguration(const QString url, m_password = password; m_authToken = authToken; - if (!authToken.isEmpty()) { + if (authToken.isEmpty()) { + if (validLoginCredentials()) { + const QByteArray userCredentials = + JsonParser::userCredentialsToJsonDocument(m_email, m_password); + sendPostRequest(ROUTE_LOG_IN, userCredentials); + } + } else { + /// authToken not empty: m_serviceApi->setBearerToken(authToken.toLatin1()); } } @@ -62,6 +71,19 @@ void ServerCommunicator::deleteItem(const QString& id) { sendDeleteRequest(path); } +bool ServerCommunicator::validLoginCredentials() { + if (url().isEmpty()) { + return false; + } + if (m_email.isEmpty()) { + return false; + } + if (m_password.isEmpty()) { + return false; + } + return true; +} + void ServerCommunicator::sendGetRequest(const QString& path) { // TODO check for valid path, instead of emptiness if (path.isEmpty()) { @@ -83,7 +105,11 @@ void ServerCommunicator::sendGetRequest(const QString& path) { } else { int statusCode = reply.httpStatus(); qWarning() << "Request not successful:" << statusCode; - onGetReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + if (statusCode == 401) { + notAuthorized(path); + } else { + onGetReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + } } } }); @@ -123,7 +149,11 @@ void ServerCommunicator::sendPostRequest(const QString& path, const QByteArray d int statusCode = reply.httpStatus(); qWarning() << "Request not successful:" << statusCode; qInfo() << "Content:" << reply.readJson(); - onPostReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + if (statusCode == 401) { + notAuthorized(path); + } else { + onPostReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + } } } }); @@ -132,6 +162,9 @@ void ServerCommunicator::sendPostRequest(const QString& path, const QByteArray d void ServerCommunicator::onPostReplySuccessful(const QString& path, const QJsonDocument doc) { if (path == ROUTE_ITEMS) { emit sendItemSuccessful(doc.toJson()); + } else if (path == ROUTE_LOG_IN) { + qCritical() << "Login success:" << doc.toJson(QJsonDocument::Compact); + emit loginSuccessful(doc.toJson(QJsonDocument::Compact)); } else { qWarning() << "Can't match request path:" << path; } @@ -140,6 +173,9 @@ void ServerCommunicator::onPostReplySuccessful(const QString& path, const QJsonD void ServerCommunicator::onPostReplyFailure(const QString& path, const QString errorString) { if (path == ROUTE_ITEMS) { emit sendItemFailure(errorString); + } else if (path == ROUTE_LOG_IN) { + qCritical() << "Login failure:" << errorString; + emit loginFailure(errorString); } else { qWarning() << "Can't match request path:" << path; } @@ -160,7 +196,11 @@ void ServerCommunicator::sendDeleteRequest(const QString& path) { } else { int statusCode = reply.httpStatus(); qWarning() << "Request not successful:" << statusCode; - onDeleteReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + if (statusCode == 401) { + notAuthorized(path); + } else { + onDeleteReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + } } } }); diff --git a/network/servercommunicator.h b/network/servercommunicator.h index fcffe66..77b4d00 100644 --- a/network/servercommunicator.h +++ b/network/servercommunicator.h @@ -30,6 +30,11 @@ class ServerCommunicator : public QObject { signals: void urlChanged(); + void loginSuccessful(const QByteArray responseData); + void loginFailure(const QString errorString); + + void notAuthorized(const QString path); + void itemsFetched(const QByteArray jsonDoc); void itemsFetchFailure(const QString errorString); @@ -48,6 +53,8 @@ class ServerCommunicator : public QObject { QString m_password; QString m_authToken; + bool validLoginCredentials(); + void sendGetRequest(const QString& path); void onGetReplySuccessful(const QString& path, const QJsonDocument doc); void onGetReplyFailure(const QString& path, const QString errorString);