diff --git a/API/api.cpp b/API/api.cpp index 79821b7..81c5e6e 100644 --- a/API/api.cpp +++ b/API/api.cpp @@ -9,6 +9,20 @@ constexpr const char* json = "application/json"; constexpr const char* urlEncoded = "application/x-www-form-urlencoded"; +const std::map testStructure({ + {"type", QMetaType::QString}, + {"version", QMetaType::QString}, +}); + +const std::map resultStructure({ + {"result", QMetaType::Double}, +}); + +const std::map tokensStructure({ + {"accessToken", QMetaType::QString}, + {"renewToken", QMetaType::QString} +}); + struct NetworkReplyDeleter { void operator () (QNetworkReply* reply) { reply->deleteLater(); @@ -30,6 +44,13 @@ API::State API::getState() const { return state; } +void API::setTokens(const QString access, const QString &renew) { + accessToken = access; + renewToken = renew; + state = Authenticating; + emit stateChanged(state); +} + void API::setAddress(const QUrl& path) { if (address == path) return; @@ -45,6 +66,40 @@ void API::setAddress(const QUrl& path) { emit stateChanged(state); } +std::optional API::readResult(QNetworkReply* reply) { + QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); + if (! + contentType.isValid() || + contentType.userType() != QMetaType::QString || + contentType.toString() != json + ) + return std::nullopt; + + QByteArray data = reply->readAll(); + QJsonDocument document = QJsonDocument::fromJson(data); + if (!document.isObject()) + return std::nullopt; + + QJsonObject object = document.object(); + return object.toVariantMap(); +} + +bool API::validateResponse(const std::optional& data, const std::map& structure) { + if (!data.has_value()) + return false; + + for (const std::pair& pair : structure) { + QVariantMap::ConstIterator itr = data->find(pair.first); + if (itr == data->end()) + return false; + + if (itr->userType() != pair.second) + return false; + } + + return true; +} + void API::test(const QString& path, const QJSValue& finished) { qDebug() << "Testing" << path; if (state == Offline) @@ -66,29 +121,17 @@ void API::onTestFinished(QNetworkReply* reply, const QUrl& addr, const QJSValue& if (error != QNetworkReply::NoError) return callCallback(finished, reply->errorString()); - QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); - if (! - contentType.isValid() || - !contentType.canConvert() || - contentType.toString() != json - ) { - return callCallback(finished, "wrong response content type"); - } + std::optional data = readResult(reply); + if (!validateResponse(data, testStructure)) + return callCallback(finished, "Malformed response"); - QByteArray data = reply->readAll(); - QJsonDocument document = QJsonDocument::fromJson(data); - QJsonObject rootObj = document.object(); + QString type = data->value("type").toString(); + if (type != "pica") + return callCallback(finished, "server of this type (" + type + ") is not supported"); - QJsonValue type = rootObj.value("type"); - QJsonValue version = rootObj.value("version"); - if (!type.isString() || !version.isString()) - return callCallback(finished, "malformed json"); - - if (type.toString() != "pica") - return callCallback(finished, "server of this type (" + type.toString() + ") is not supported"); - - if (version.toString() != "0.0.1") - return callCallback(finished, "server of this version (" + version.toString() + ") is not supported"); + QString version = data->value("version").toString(); + if (version != "0.0.1") + return callCallback(finished, "server of this version (" + version + ") is not supported"); callCallback(finished, QString(), {QJSValue(true)}); address = ""; //to provoke singal change even if it's the same server @@ -144,59 +187,57 @@ void API::sendLogin(const QString& login, const QString& password, const QJSValu void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const { std::unique_ptr rpl(reply); QNetworkReply::NetworkError error = reply->error(); - if (error != QNetworkReply::NoError) - return callCallback(finished, reply->errorString()); - - QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); - if (! - contentType.isValid() || - !contentType.canConvert() || - contentType.toString() != json - ) { - return callCallback(finished, "wrong response content type"); + std::optional data = readResult(reply); + std::optional code; + QString detail; + bool success = false; + if (validateResponse(data, resultStructure)) { + code = Codes::convertRegister(data->value("result").toInt()); + success = code.value() == Codes::Register::success; + if (!success) + detail = Codes::description(code.value()); } - QByteArray data = reply->readAll(); - QJsonDocument document = QJsonDocument::fromJson(data); - QJsonObject rootObj = document.object(); + if (error != QNetworkReply::NoError) + return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail)); - QJsonValue res = rootObj.value("result"); - if (!res.isDouble()) - return callCallback(finished, "malformed json"); + if (!code) + return callCallback(finished, "Malformed result"); - Codes::Register result = Codes::convertRegister(res.toInt()); - bool success = result == Codes::Register::success; - QString err; - if (!success) - err = Codes::description(result); - - callCallback(finished, err, {QJSValue(success)}); + callCallback(finished, detail, {QJSValue(success)}); } void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) { std::unique_ptr rpl(reply); QNetworkReply::NetworkError error = reply->error(); - if (error != QNetworkReply::NoError) - return callCallback(finished, reply->errorString()); - - QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); - if (! - contentType.isValid() || - !contentType.canConvert() || - contentType.toString() != json - ) { - return callCallback(finished, "wrong response content type"); + std::optional data = readResult(reply); + std::optional code; + QString detail; + bool success = false; + if (validateResponse(data, resultStructure)) { + code = Codes::convertLogin(data->value("result").toInt()); + success = code.value() == Codes::Login::success; + if (!success) + detail = Codes::description(code.value()); } - QByteArray data = reply->readAll(); - QJsonDocument document = QJsonDocument::fromJson(data); - QJsonObject rootObj = document.object(); + if (error != QNetworkReply::NoError) + return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail)); - QJsonValue res = rootObj.value("result"); - if (!res.isDouble()) - return callCallback(finished, "malformed json"); + if (!code) + return callCallback(finished, "Malformed result"); - //todo + if (!validateResponse(data, tokensStructure)) + return callCallback(finished, "Malformed result: missing tokens"); + + + callCallback(finished, detail, {QJSValue(success)}); + + state = Authenticated; + accessToken = data->value("accessToken").toString(); + renewToken = data->value("renewToken").toString(); + emit storeTokens(accessToken, renewToken); + emit stateChanged(state); } void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const { diff --git a/API/api.h b/API/api.h index 19be01d..1f12713 100644 --- a/API/api.h +++ b/API/api.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include @@ -14,7 +16,13 @@ class API : public QObject { friend class Root; Q_OBJECT public: - enum State {Offline, NoServer, NotAuthenticated, Authenticated}; + enum State { + Offline, + NoServer, + NotAuthenticated, + Authenticating, + Authenticated + }; Q_ENUM(State) private: @@ -26,11 +34,14 @@ public: QUrl getAddress() const; State getState() const; + void setTokens(const QString access, const QString& renew); signals: void addressChanged(const QUrl& path); void stateChanged(State state); + void storeTokens(const QString& access, const QString& renew); + public slots: void test(const QString& path, const QJSValue& finished = QJSValue()); void sendRegister(const QString& login, const QString& password, const QJSValue& finished = QJSValue()); @@ -44,9 +55,13 @@ private slots: private: void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const; void setAddress(const QUrl& path); + static std::optional readResult(QNetworkReply* reply); + static bool validateResponse(const std::optional& data, const std::map& structure); private: QUrl address; QNetworkAccessManager network; State state; + QString accessToken; + QString renewToken; }; diff --git a/API/codes.cpp b/API/codes.cpp index 7411f6f..5b2e74d 100644 --- a/API/codes.cpp +++ b/API/codes.cpp @@ -14,6 +14,16 @@ constexpr std::array re "Unknown registration error" }); +constexpr std::array loginErrors({ + "Successfully logged in", + "Login have not been provided", + "The login can not be empty", + "Password have not been provided", + "Password can not be empty", + "Provided login or (and) password are wrong", + "Unknown login error" +}); + Codes::Register Codes::convertRegister (int source) { if (source < 0) { std::cerr << "Converting Register response code which is smaller than zero, falling back to unknownError" << std::endl; @@ -31,3 +41,21 @@ Codes::Register Codes::convertRegister (int source) { QString Codes::description(Register code) { return registerErrors[(uint8_t)code]; } + +Codes::Login Codes::convertLogin(int source) { + if (source < 0) { + std::cerr << "Converting Login response code which is smaller than zero, falling back to unknownError" << std::endl; + return Login::unknownError; + } + + if (source > (int)Login::unknownError) { + std::cerr << "Converting Login response code which is bigger than biggest known, falling back to unknownError" << std::endl; + return Login::unknownError; + } + + return static_cast(source); +} + +QString Codes::description(Login code) { + return loginErrors[(uint8_t)code]; +} diff --git a/API/codes.h b/API/codes.h index 4d22dad..5059d0f 100644 --- a/API/codes.h +++ b/API/codes.h @@ -15,8 +15,20 @@ enum class Register { unknownError }; +enum class Login { + success, + noLogin, + emptyLogin, + noPassword, + emptyPassword, + wrongCredentials, + unknownError +}; + Register convertRegister (int source); +Login convertLogin (int source); QString description (Register code); +QString description (Login code); } diff --git a/qml/ServerPick.qml b/qml/ServerPick.qml index 6df2e3f..cd3e7a2 100644 --- a/qml/ServerPick.qml +++ b/qml/ServerPick.qml @@ -10,7 +10,7 @@ Page { signal back() - title: qsTr("Chosing a server") + // title: qsTr("Chosing a server") Column { anchors.centerIn: parent diff --git a/qml/Welcome.qml b/qml/Welcome.qml index b6783e2..a167cdd 100644 --- a/qml/Welcome.qml +++ b/qml/Welcome.qml @@ -9,7 +9,7 @@ Page { id: page signal pickServer(address: string) - title: qsTr("Welcome") + // title: qsTr("Welcome") Column { id: column diff --git a/root.cpp b/root.cpp index 9028662..7950ce2 100644 --- a/root.cpp +++ b/root.cpp @@ -28,6 +28,7 @@ Root::Root(const QUrl& root, int& argc, char* argv[]) : qRegisterMetaType("API"); connect(&api, &API::addressChanged, this, &Root::onAPIAddressChanged); + connect(&api, &API::storeTokens, this, &Root::onStoreTokens); qmlRegisterSingletonType("magpie.API", 1, 0, "API", [this] (QQmlEngine *engine, QJSEngine *scriptEngine) { return &api; @@ -68,3 +69,9 @@ void Root::onAPIAddressChanged(const QUrl& url) { QSettings settings; settings.setValue("address", url); } + +void Root::onStoreTokens(const QString& access, const QString& renew) { + QSettings settings; + settings.setValue("assessToken", access); + settings.setValue("renewToken", renew); +} diff --git a/root.h b/root.h index 79d4706..f2d6f04 100644 --- a/root.h +++ b/root.h @@ -21,6 +21,7 @@ public: private slots: void onObjectCreated(QObject* obj, const QUrl& objUrl); void onAPIAddressChanged(const QUrl& url); + void onStoreTokens(const QString& access, const QString& renew); private: QUrl root;