From 99724f845c0bc827ebc77f8a35afb1e07af8ab4a Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Wed, 28 Nov 2018 21:33:27 +0100 Subject: [PATCH] Can perform user sign in. --- ComunicMessages.pro | 20 ++++++-- data/accountloginrequest.cpp | 26 ++++++++++ data/accountloginrequest.h | 43 ++++++++++++++++ data/apirequest.cpp | 54 ++++++++++++++++++++ data/apirequest.h | 88 ++++++++++++++++++++++++++++++++ data/apirequestparameter.cpp | 32 ++++++++++++ data/apirequestparameter.h | 29 +++++++++++ data/apirequestslist.cpp | 29 +++++++++++ data/apirequestslist.h | 25 +++++++++ helpers/accounthelper.cpp | 50 +++++++++++++++++- helpers/accounthelper.h | 28 ++++++++++ helpers/apihelper.cpp | 70 +++++++++++++++++++++++++ helpers/apihelper.h | 45 ++++++++++++++++ utils/accountutils.cpp | 16 ++++++ utils/accountutils.h | 26 ++++++++++ utils/jsonutils.cpp | 6 +++ utils/jsonutils.h | 17 +++++++ widgets/loginwidget.cpp | 96 ++++++++++++++++++++++++++++++++++ widgets/loginwidget.h | 28 ++++++++++ widgets/loginwidget.ui | 99 +++++++++++++++++++++++------------- 20 files changed, 787 insertions(+), 40 deletions(-) create mode 100644 data/accountloginrequest.cpp create mode 100644 data/accountloginrequest.h create mode 100644 data/apirequest.cpp create mode 100644 data/apirequest.h create mode 100644 data/apirequestparameter.cpp create mode 100644 data/apirequestparameter.h create mode 100644 data/apirequestslist.cpp create mode 100644 data/apirequestslist.h create mode 100644 helpers/apihelper.cpp create mode 100644 helpers/apihelper.h create mode 100644 utils/accountutils.cpp create mode 100644 utils/accountutils.h create mode 100644 utils/jsonutils.cpp create mode 100644 utils/jsonutils.h diff --git a/ComunicMessages.pro b/ComunicMessages.pro index 3271cfb..21f078f 100644 --- a/ComunicMessages.pro +++ b/ComunicMessages.pro @@ -1,14 +1,28 @@ -QT += widgets +QT += widgets network SOURCES += \ main.cpp \ helpers/accounthelper.cpp \ - widgets/loginwidget.cpp + widgets/loginwidget.cpp \ + utils/accountutils.cpp \ + data/accountloginrequest.cpp \ + data/apirequest.cpp \ + data/apirequestparameter.cpp \ + helpers/apihelper.cpp \ + utils/jsonutils.cpp \ + data/apirequestslist.cpp HEADERS += \ helpers/accounthelper.h \ config.h \ - widgets/loginwidget.h + widgets/loginwidget.h \ + utils/accountutils.h \ + data/accountloginrequest.h \ + data/apirequest.h \ + data/apirequestparameter.h \ + helpers/apihelper.h \ + utils/jsonutils.h \ + data/apirequestslist.h FORMS += \ widgets/loginwidget.ui diff --git a/data/accountloginrequest.cpp b/data/accountloginrequest.cpp new file mode 100644 index 0000000..f19fead --- /dev/null +++ b/data/accountloginrequest.cpp @@ -0,0 +1,26 @@ +#include "accountloginrequest.h" + +AccountLoginRequest::AccountLoginRequest() +{ + +} + +QString AccountLoginRequest::emailAddress() const +{ + return mEmailAddress; +} + +void AccountLoginRequest::setEmailAddress(const QString &emailAddress) +{ + mEmailAddress = emailAddress; +} + +QString AccountLoginRequest::password() const +{ + return mPassword; +} + +void AccountLoginRequest::setPassword(const QString &password) +{ + mPassword = password; +} diff --git a/data/accountloginrequest.h b/data/accountloginrequest.h new file mode 100644 index 0000000..4590212 --- /dev/null +++ b/data/accountloginrequest.h @@ -0,0 +1,43 @@ +/** + * Account login request + * + * @author Pierre HUBERT + */ + +#ifndef ACCOUNTLOGINREQUEST_H +#define ACCOUNTLOGINREQUEST_H + +#include + +/** + * Possible login results + */ +enum LoginResult { + LOGIN_SUCCESS, + INVALID_CREDENTIALS, + TOO_MANY_REQUEST, + NETWORK_ERROR, + OTHER_ERROR +}; + +/** + * Login request + */ +class AccountLoginRequest +{ + +public: + AccountLoginRequest(); + + QString emailAddress() const; + void setEmailAddress(const QString &emailAddress); + + QString password() const; + void setPassword(const QString &password); + +private: + QString mEmailAddress; + QString mPassword; +}; + +#endif // ACCOUNTLOGINREQUEST_H diff --git a/data/apirequest.cpp b/data/apirequest.cpp new file mode 100644 index 0000000..48ab61f --- /dev/null +++ b/data/apirequest.cpp @@ -0,0 +1,54 @@ +#include + +#include "apirequest.h" + +APIRequest::APIRequest(QObject *parent) : QObject(parent) +{ + +} + +APIRequest::~APIRequest() +{ + if(mNetworkReply != nullptr) + mNetworkReply->deleteLater(); +} + +QString APIRequest::URI() const +{ + return mURI; +} + +void APIRequest::setURI(const QString &uRI) +{ + mURI = uRI; +} + +void APIRequest::addString(QString name, QString value) +{ + mArguments.append(APIRequestParameter(name, value)); +} + +void APIRequest::addInt(QString name, int value) +{ + mArguments.append(APIRequestParameter(name, QString::number(value))); +} + +void APIRequest::addBool(QString name, bool value) +{ + mArguments.append(APIRequestParameter(name, value ? "true" : "false")); +} + +QList APIRequest::arguments() const +{ + return mArguments; +} + +QNetworkReply *APIRequest::networkReply() const +{ + return mNetworkReply; +} + +void APIRequest::setNetworkReply(QNetworkReply *networkReply) +{ + mNetworkReply = networkReply; +} diff --git a/data/apirequest.h b/data/apirequest.h new file mode 100644 index 0000000..109b513 --- /dev/null +++ b/data/apirequest.h @@ -0,0 +1,88 @@ +/** + * This object contains information about a single + * object. + * + * @author Pierre HUBERT + */ + +#ifndef APIREQUEST_H +#define APIREQUEST_H + +#include +#include + +#include "apirequestparameter.h" + +class QNetworkReply; + +class APIRequest : public QObject +{ + Q_OBJECT +public: + explicit APIRequest(QObject *parent = nullptr); + ~APIRequest(); + + //Get and set URI target + QString URI() const; + void setURI(const QString &URI); + + /** + * Add a string parameter to the request + * + * @param name The name of the field to add + * @param value The value of the argument to add + */ + void addString(QString name, QString value); + + /** + * Add an integer parameter to the request + * + * @param name The name of the field to add + * @param value The value of the argument to add + */ + void addInt(QString name, int value); + + /** + * Add a boolean parameter to the request + * + * @param name The name of the field to add + * @param value The value of the argument to add + */ + void addBool(QString name, bool value); + + /** + * Get the entire list of arguments of the request + * + * @return The list of arguments + */ + QList arguments() const; + + //Get and set network reply associated with the request + QNetworkReply *networkReply() const; + void setNetworkReply(QNetworkReply *networkReply); + +signals: + + /** + * Signal emitted when an error occurred + * + * @param code The code of the error + */ + void error(int code); + + /** + * Signal emitted once we have got the response for our request + * + * @param document JSON Response + */ + void success(const QJsonDocument &document); + +public slots: + +private: + QString mURI; + QList mArguments; + QNetworkReply *mNetworkReply = nullptr; +}; + +#endif // APIREQUEST_H diff --git a/data/apirequestparameter.cpp b/data/apirequestparameter.cpp new file mode 100644 index 0000000..b885f44 --- /dev/null +++ b/data/apirequestparameter.cpp @@ -0,0 +1,32 @@ +#include "apirequestparameter.h" + +APIRequestParameter::APIRequestParameter() +{ + +} + +APIRequestParameter::APIRequestParameter(const QString &name, const QString &value) : + mName(name), mValue(value) +{ + +} + +QString APIRequestParameter::name() const +{ + return mName; +} + +void APIRequestParameter::setName(const QString &name) +{ + mName = name; +} + +QString APIRequestParameter::value() const +{ + return mValue; +} + +void APIRequestParameter::setValue(const QString &value) +{ + mValue = value; +} diff --git a/data/apirequestparameter.h b/data/apirequestparameter.h new file mode 100644 index 0000000..04e058d --- /dev/null +++ b/data/apirequestparameter.h @@ -0,0 +1,29 @@ +/** + * Single API request parameter + * + * @author Pierre HUBERT + */ + +#ifndef APIREQUESTPARAMETER_H +#define APIREQUESTPARAMETER_H + +#include + +class APIRequestParameter +{ +public: + APIRequestParameter(); + APIRequestParameter(const QString &name, const QString &value); + + QString name() const; + void setName(const QString &name); + + QString value() const; + void setValue(const QString &value); + +private: + QString mName; + QString mValue; +}; + +#endif // APIREQUESTPARAMETER_H diff --git a/data/apirequestslist.cpp b/data/apirequestslist.cpp new file mode 100644 index 0000000..3a991eb --- /dev/null +++ b/data/apirequestslist.cpp @@ -0,0 +1,29 @@ +#include "apirequest.h" +#include "apirequestslist.h" + +APIRequestsList::APIRequestsList() : QList() +{ + +} + +APIRequest *APIRequestsList::findForReply(QNetworkReply *reply, bool delete_from_list) +{ + bool found = false; + int i; + for(i = 0; i < size(); i++){ + if(at(i)->networkReply() == reply){ + found = true; + break; + } + } + + APIRequest *request = nullptr; + + if(found) + request = at(i); + + if(found && delete_from_list) + removeAt(i); + + return request; +} diff --git a/data/apirequestslist.h b/data/apirequestslist.h new file mode 100644 index 0000000..117dcf4 --- /dev/null +++ b/data/apirequestslist.h @@ -0,0 +1,25 @@ +#ifndef APIREQUESTSLIST_H +#define APIREQUESTSLIST_H + +#include + +class APIRequest; +class QNetworkReply; + +class APIRequestsList : public QList +{ +public: + APIRequestsList(); + + /** + * Search and return the API Request that contains a specific NetworkReply + * + * @param reply The reply to search + * @param delete_from_list Specify whether the entry should be removed + * from the list or not + * @return The request / null if none found + */ + APIRequest *findForReply(QNetworkReply *reply, bool delete_from_list); +}; + +#endif // APIREQUESTSLIST_H diff --git a/helpers/accounthelper.cpp b/helpers/accounthelper.cpp index bef51a3..7018775 100644 --- a/helpers/accounthelper.cpp +++ b/helpers/accounthelper.cpp @@ -1,12 +1,60 @@ #include "accounthelper.h" +#include "../data/apirequest.h" +#include "../helpers/apihelper.h" AccountHelper::AccountHelper(QObject *parent) : QObject(parent) { - + mAPIHelper = new APIHelper; } bool AccountHelper::signedIn() { return false; //TODO : implement } + +void AccountHelper::login(const AccountLoginRequest &info) +{ + //Create API request + APIRequest *request = new APIRequest; + request->setURI("account/login"); + request->addString("userMail", info.emailAddress()); + request->addString("userPassword", info.password()); + + //Make connections + connect(request, &APIRequest::success, this, &AccountHelper::requestLoginResult); + connect(request, &APIRequest::error, this, &AccountHelper::loginError); + + //Execute request + mAPIHelper->execute(request); + +} + +void AccountHelper::loginError(int code) +{ + //Delete API request + qobject_cast(sender())->deleteLater(); + + LoginResult result = LoginResult::OTHER_ERROR; + + if(code < 100) + result = LoginResult::NETWORK_ERROR; + if(code == 401) + result = LoginResult::INVALID_CREDENTIALS; + if(code == 429) + result = LoginResult::TOO_MANY_REQUEST; + + //Inform about the result + emit loginResult(result); +} + +void AccountHelper::requestLoginResult(const QJsonDocument &document) +{ + //Delete API request + qobject_cast(sender())->deleteLater(); + + + + //Success + emit loginResult(LoginResult::LOGIN_SUCCESS); +} diff --git a/helpers/accounthelper.h b/helpers/accounthelper.h index 3da6af4..5ff943b 100644 --- a/helpers/accounthelper.h +++ b/helpers/accounthelper.h @@ -8,6 +8,11 @@ #define ACCOUNTHELPER_H #include +#include + +class APIHelper; + +#include "../data/accountloginrequest.h" class AccountHelper : public QObject { @@ -22,9 +27,32 @@ public: */ bool signedIn(); + /** + * Perform login + * + * @param info Request data (user credentials) + */ + void login(const AccountLoginRequest &info); + signals: + /** + * This signal is emitted once a login request has been completed + * + * @param result The state of the request + */ + void loginResult(LoginResult result); + public slots: + +private slots: + + //Login request callbacks + void loginError(int code); + void requestLoginResult(const QJsonDocument &document); + +private: + APIHelper *mAPIHelper; }; #endif // ACCOUNTHELPER_H diff --git a/helpers/apihelper.cpp b/helpers/apihelper.cpp new file mode 100644 index 0000000..49b33de --- /dev/null +++ b/helpers/apihelper.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include "apihelper.h" +#include "../config.h" + +APIHelper::APIHelper(QObject *parent) : QObject(parent) +{ + + //Make errors + QObject::connect(&mNetworkManager, &QNetworkAccessManager::sslErrors, this, &APIHelper::sslErrors); +} + +void APIHelper::execute(APIRequest *request) +{ + //Determine full request URL + QString requestURL = API_URL + request->URI(); + + //Add API credentials to parameters + request->addString("serviceName", API_SERVICE_NAME); + request->addString("serviceToken", API_SERVICE_TOKEN); + + //Prepare request + //See this SO question to learn more : https://stackoverflow.com/questions/2599423 + QUrlQuery queryData; + for(APIRequestParameter param : request->arguments()) + queryData.addQueryItem(param.name(), param.value()); + + //Send request + QNetworkRequest networkRequest((QUrl(requestURL))); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QNetworkReply *reply = mNetworkManager.post(networkRequest, queryData.toString(QUrl::FullyEncoded).toUtf8()); + + //Make connections + connect(reply, &QNetworkReply::finished, this, &APIHelper::finished); + connect(reply, static_cast(&QNetworkReply::error), this, &APIHelper::error); + + //Add the request to the list + request->setNetworkReply(reply); + mRequestsList.append(request); +} + +void APIHelper::finished() +{ + if(qobject_cast(sender())->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 200) + return; + + //Get the API request related to this + APIRequest *request = mRequestsList.findForReply(qobject_cast(sender()), true); + + //Process and return response + emit request->success(QJsonDocument::fromJson(request->networkReply()->readAll())); +} + +void APIHelper::error() +{ + //Get the API request related to this + APIRequest *request = mRequestsList.findForReply(qobject_cast(sender()), true); + + //Try to determine error code + int response_code = request->networkReply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + qWarning("An error occurred in an API request! (code: %d)", response_code); + + emit request->error(response_code); +} + +void APIHelper::sslErrors() +{ + qWarning("APIHelper: the SSL/TLS session encountered an error!"); +} diff --git a/helpers/apihelper.h b/helpers/apihelper.h new file mode 100644 index 0000000..b1f48ba --- /dev/null +++ b/helpers/apihelper.h @@ -0,0 +1,45 @@ +/** + * API Helper - handles the connections between + * the server and this application + * + * @author Pierre HUBERT + */ + +#ifndef APIHELPER_H +#define APIHELPER_H + +#include +#include +#include + +#include "../data/apirequest.h" +#include "../data/apirequestslist.h" + +class APIHelper : public QObject +{ + Q_OBJECT +public: + explicit APIHelper(QObject *parent = nullptr); + + /** + * Send a request to the server + * + * @param request The request to execute + */ + void execute(APIRequest *request); + +signals: + +public slots: + +private slots: + void finished(); + void error(); + void sslErrors(); + +private: + QNetworkAccessManager mNetworkManager; + APIRequestsList mRequestsList; +}; + +#endif // APIHELPER_H diff --git a/utils/accountutils.cpp b/utils/accountutils.cpp new file mode 100644 index 0000000..e932c2b --- /dev/null +++ b/utils/accountutils.cpp @@ -0,0 +1,16 @@ +#include + +#include "accountutils.h" + +AccountUtils::AccountUtils() +{ + +} + +bool AccountUtils::CheckEmailAddress(const QString &email) +{ + QRegExp regex("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b"); + regex.setCaseSensitivity(Qt::CaseInsensitive); + regex.setPatternSyntax(QRegExp::RegExp); + return regex.exactMatch(email); +} diff --git a/utils/accountutils.h b/utils/accountutils.h new file mode 100644 index 0000000..7058ab6 --- /dev/null +++ b/utils/accountutils.h @@ -0,0 +1,26 @@ +/** + * Account utilities + * + * @author Pierre HUBERT + */ + +#ifndef ACCOUNTUTILS_H +#define ACCOUNTUTILS_H + +#include + +class AccountUtils +{ +public: + AccountUtils(); + + /** + * Determine whether an email address is valid or not + * + * @param email The email address to check + * @return TRUE if the email is valid / FALSE else + */ + bool static CheckEmailAddress(const QString &email); +}; + +#endif // ACCOUNTUTILS_H diff --git a/utils/jsonutils.cpp b/utils/jsonutils.cpp new file mode 100644 index 0000000..8b12c96 --- /dev/null +++ b/utils/jsonutils.cpp @@ -0,0 +1,6 @@ +#include "jsonutils.h" + +JSONUtils::JSONUtils() +{ + +} diff --git a/utils/jsonutils.h b/utils/jsonutils.h new file mode 100644 index 0000000..d0ba670 --- /dev/null +++ b/utils/jsonutils.h @@ -0,0 +1,17 @@ +/** + * JSON utilities + * + * @author Pierre HUBERT + */ + +#ifndef JSONUTILS_H +#define JSONUTILS_H + + +class JSONUtils +{ +public: + JSONUtils(); +}; + +#endif // JSONUTILS_H diff --git a/widgets/loginwidget.cpp b/widgets/loginwidget.cpp index 5b33ef0..69184dd 100644 --- a/widgets/loginwidget.cpp +++ b/widgets/loginwidget.cpp @@ -1,14 +1,110 @@ +#include + #include "loginwidget.h" #include "ui_loginwidget.h" +#include "../utils/accountutils.h" +#include "../helpers/accounthelper.h" LoginWidget::LoginWidget(QWidget *parent) : QWidget(parent), ui(new Ui::LoginWidget) { ui->setupUi(this); + mAccountHelper = new AccountHelper(this); + connect(mAccountHelper, &AccountHelper::loginResult, this, &LoginWidget::loginResult); } LoginWidget::~LoginWidget() { delete ui; } + +void LoginWidget::loginResult(LoginResult result) +{ + //Check if login is successfull + if(result == LoginResult::LOGIN_SUCCESS){ + qDebug("User successfully signed in."); + + close(); + return; + } + + //Release login button + ui->loginButton->setEnabled(true); + + //Display appropriate error + switch (result) { + case LoginResult::INVALID_CREDENTIALS: + showError(tr("The email and / or the password was rejected by the server!")); + break; + + case LoginResult::NETWORK_ERROR: + showError(tr("A network occurred. Please check your Internet connection...")); + break; + + case LoginResult::TOO_MANY_REQUEST: + showError(tr("Too many login attempts from your computer. Please try again later...")); + break; + + case LoginResult::OTHER_ERROR: + default: + showError(tr("Login attempt did not succeed.")); + break; + } + +} + +void LoginWidget::on_loginButton_clicked() +{ + submitForm(); +} + +void LoginWidget::on_emailEdit_returnPressed() +{ + submitForm(); +} + +void LoginWidget::on_passwordEdit_returnPressed() +{ + submitForm(); +} + + +void LoginWidget::submitForm() +{ + showError(""); + + QString emailAddress = ui->emailEdit->text(); + QString password = ui->passwordEdit->text(); + + //Check input + if(emailAddress.length() < 5){ + showError(tr("Please specify an email address!")); + return; + } + + if(password.length() < 3){ + showError(tr("Please specify a valid password!")); + return; + } + + if(!AccountUtils::CheckEmailAddress(emailAddress)){ + showError(("Please specify a valid email address!")); + return; + } + + //Block login button + ui->loginButton->setEnabled(false); + + //Try to sign in user + AccountLoginRequest request; + request.setEmailAddress(emailAddress); + request.setPassword(password); + mAccountHelper->login(request); +} + +void LoginWidget::showError(const QString &msg) +{ + ui->errorLabel->setText(msg); +} + diff --git a/widgets/loginwidget.h b/widgets/loginwidget.h index 51d1014..8943ee7 100644 --- a/widgets/loginwidget.h +++ b/widgets/loginwidget.h @@ -3,10 +3,15 @@ #include +#include "../data/accountloginrequest.h" + namespace Ui { class LoginWidget; } +class AccountHelper; + + class LoginWidget : public QWidget { Q_OBJECT @@ -15,8 +20,31 @@ public: explicit LoginWidget(QWidget *parent = nullptr); ~LoginWidget(); +private slots: + + void loginResult(LoginResult result); + + //UI Slots + void on_loginButton_clicked(); + void on_emailEdit_returnPressed(); + void on_passwordEdit_returnPressed(); + private: + + /** + * Submit login form + */ + void submitForm(); + + /** + * Display an error message on the widget + * + * @param msg The message to show + */ + void showError(const QString &msg); + Ui::LoginWidget *ui; + AccountHelper *mAccountHelper; }; #endif // LOGINWIDGET_H diff --git a/widgets/loginwidget.ui b/widgets/loginwidget.ui index f5fc787..8a6f26e 100644 --- a/widgets/loginwidget.ui +++ b/widgets/loginwidget.ui @@ -6,8 +6,8 @@ 0 0 - 402 - 107 + 274 + 397 @@ -15,47 +15,74 @@ - - - - - - - - Your email address - - - - - - - Email address - - - - - - - Password - - - - - - - Your password - - - - + + + Qt::Vertical + + + + 20 + 40 + + + - + + + + 75 + true + + + + + + + Qt::AlignCenter + + + + + + + + + + Your email address + + + + + + + QLineEdit::Password + + + Your password + + + + + Login + + + + Qt::Vertical + + + + 20 + 40 + + + +