summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorjansol <jhs@psonet.com>2017-07-08 14:41:49 +0300
committermujx <mujx@users.noreply.github.com>2017-07-08 14:41:49 +0300
commitf5ba63946b46877111dc48b760f03e98cc10548d (patch)
treef45a65f6fe012dc98afe68e7769ba6171d88a302 /src
parentFix emoji alignment issue (#43) (diff)
downloadnheko-f5ba63946b46877111dc48b760f03e98cc10548d.tar.xz
Improve login flow (#35)
* Validate both inferred and explicitly entered server addresses by attempting to call the /versions endpoint
* If the domain from the mxid fails validation, try prefixing it with 'matrix'
* Only show server address field if address validation ultimately fails
Diffstat (limited to 'src')
-rw-r--r--src/LoginPage.cc164
-rw-r--r--src/LoginSettings.cc61
-rw-r--r--src/MatrixClient.cc27
-rw-r--r--src/Versions.cc62
4 files changed, 205 insertions, 109 deletions
diff --git a/src/LoginPage.cc b/src/LoginPage.cc

index 4329baad..e3f51484 100644 --- a/src/LoginPage.cc +++ b/src/LoginPage.cc
@@ -21,10 +21,9 @@ #include "LoginPage.h" LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent) - : QWidget(parent) - , settings_modal_{nullptr} - , login_settings_{nullptr} - , client_{client} + : QWidget(parent) + , inferredServerAddress_() + , client_{client} { setStyleSheet("background-color: #f9f9f9"); @@ -38,9 +37,8 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent) back_button_->setMinimumSize(QSize(30, 30)); back_button_->setForegroundColor("#333333"); - advanced_settings_button_ = new FlatButton(this); - advanced_settings_button_->setMinimumSize(QSize(30, 30)); - advanced_settings_button_->setForegroundColor("#333333"); + top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); + top_bar_layout_->addStretch(1); QIcon icon; icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off); @@ -51,13 +49,6 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent) QIcon advanced_settings_icon; advanced_settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off); - advanced_settings_button_->setIcon(advanced_settings_icon); - advanced_settings_button_->setIconSize(QSize(24, 24)); - - top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - top_bar_layout_->addStretch(1); - top_bar_layout_->addWidget(advanced_settings_button_, 0, Qt::AlignRight | Qt::AlignVCenter); - logo_ = new QLabel(this); logo_->setPixmap(QPixmap(":/logos/nheko-128.png")); @@ -85,6 +76,19 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent) matrixid_input_->setBackgroundColor("#f9f9f9"); matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); + spinner_ = new CircularProgress(this); + spinner_->setColor("#acc7dc"); + spinner_->setSize(32); + spinner_->setMaximumWidth(spinner_->width()); + spinner_->hide(); + + errorIcon_ = new QLabel(this); + errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png")); + errorIcon_->hide(); + + matrixidLayout_ = new QHBoxLayout(); + matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); + password_input_ = new TextField(this); password_input_->setTextColor("#333333"); password_input_->setLabel(tr("Password")); @@ -92,8 +96,20 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent) password_input_->setBackgroundColor("#f9f9f9"); password_input_->setEchoMode(QLineEdit::Password); - form_layout_->addWidget(matrixid_input_, Qt::AlignHCenter, 0); + serverInput_ = new TextField(this); + serverInput_->setTextColor("#333333"); + serverInput_->setLabel("Homeserver address"); + serverInput_->setInkColor("#555459"); + serverInput_->setBackgroundColor("#f9f9f9"); + serverInput_->setPlaceholderText("matrix.org"); + serverInput_->hide(); + + serverLayout_ = new QHBoxLayout(); + serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); + + form_layout_->addLayout(matrixidLayout_); form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0); + form_layout_->addLayout(serverLayout_); button_layout_ = new QHBoxLayout(); button_layout_->setSpacing(0); @@ -128,8 +144,12 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent) connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked())); connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); connect(client_.data(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString))); - connect(advanced_settings_button_, SIGNAL(clicked()), this, SLOT(showSettingsModal())); + connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); + connect(client_.data(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString))); + connect(client_.data(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess())); + connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); matrixid_input_->setValidator(&InputValidator::Id); } @@ -139,63 +159,115 @@ void LoginPage::loginError(QString error) error_label_->setText(error); } -void LoginPage::onLoginButtonClicked() +void LoginPage::onMatrixIdEntered() { error_label_->setText(""); if (!matrixid_input_->hasAcceptableInput()) { loginError(tr("Invalid Matrix ID")); + return; } else if (password_input_->text().isEmpty()) { loginError(tr("Empty password")); - } else { - QString user = matrixid_input_->text().split(":").at(0).split("@").at(1); - QString password = password_input_->text(); + } - QString home_server = custom_domain_.isEmpty() - ? matrixid_input_->text().split(":").at(1) - : custom_domain_; + QString homeServer = matrixid_input_->text().split(":").at(1); + if (homeServer != inferredServerAddress_) { + serverInput_->hide(); + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + if (serverInput_->isVisible()) { + matrixidLayout_->removeWidget(spinner_); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->show(); + } else { + serverLayout_->removeWidget(spinner_); + matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->show(); + } - client_->setServer(home_server); - client_->login(user, password); + inferredServerAddress_ = homeServer; + serverInput_->setText(homeServer); + client_->setServer(homeServer); + client_->versions(); } } -void LoginPage::showSettingsModal() +void LoginPage::onServerAddressEntered() { - if (login_settings_ == nullptr) { - login_settings_ = new LoginSettings(this); - connect(login_settings_, &LoginSettings::closing, this, &LoginPage::closeSettingsModal); - } + error_label_->setText(""); + client_->setServer(serverInput_->text()); + client_->versions(); + + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->show(); +} - if (settings_modal_ == nullptr) { - settings_modal_ = new OverlayModal(this, login_settings_); - settings_modal_->setDuration(100); - settings_modal_->setColor(QColor(55, 55, 55, 170)); +void LoginPage::versionError(QString error) +{ + // Matrix homeservers are often kept on a subdomain called 'matrix' + // so let's try that next, unless the address was set explicitly or the domain part of the username already points to this subdomain + QUrl currentServer = client_->getHomeServer(); + QString mxidAddress = matrixid_input_->text().split(":").at(1); + if (currentServer.host() == inferredServerAddress_ && !currentServer.host().startsWith("matrix")) { + error_label_->setText(""); + currentServer.setHost(QString("matrix.")+currentServer.host()); + serverInput_->setText(currentServer.host()); + client_->setServer(currentServer.host()); + client_->versions(); + return; } - settings_modal_->fadeIn(); + error_label_->setText(error); + serverInput_->show(); + + spinner_->hide(); + serverLayout_->removeWidget(spinner_); + serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); + errorIcon_->show(); + matrixidLayout_->removeWidget(spinner_); +} + +void LoginPage::versionSuccess() +{ + serverLayout_->removeWidget(spinner_); + matrixidLayout_->removeWidget(spinner_); + spinner_->hide(); + + if (serverInput_->isVisible()) + serverInput_->hide(); } -void LoginPage::closeSettingsModal(const QString &server) +void LoginPage::onLoginButtonClicked() { - custom_domain_ = server; - settings_modal_->fadeOut(); + error_label_->setText(""); + + if (!matrixid_input_->hasAcceptableInput()) { + loginError("Invalid Matrix ID"); + } else if (password_input_->text().isEmpty()) { + loginError("Empty password"); + } else { + QString user = matrixid_input_->text().split(":").at(0).split("@").at(1); + QString password = password_input_->text(); + client_->setServer(serverInput_->text()); + client_->login(user, password); + } } void LoginPage::reset() { matrixid_input_->clear(); password_input_->clear(); + serverInput_->clear(); - if (settings_modal_ != nullptr) { - settings_modal_->deleteLater(); - settings_modal_ = nullptr; - } + spinner_->hide(); + errorIcon_->hide(); + serverLayout_->removeWidget(spinner_); + serverLayout_->removeWidget(errorIcon_); + matrixidLayout_->removeWidget(spinner_); - if (login_settings_ != nullptr) { - login_settings_->deleteLater(); - login_settings_ = nullptr; - } + inferredServerAddress_.clear(); } void LoginPage::onBackButtonClicked() diff --git a/src/LoginSettings.cc b/src/LoginSettings.cc deleted file mode 100644
index b3725caf..00000000 --- a/src/LoginSettings.cc +++ /dev/null
@@ -1,61 +0,0 @@ -/* - * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QLabel> -#include <QVBoxLayout> - -#include "LoginSettings.h" - -LoginSettings::LoginSettings(QWidget *parent) - : QFrame(parent) -{ - setMaximumSize(400, 400); - setStyleSheet("background-color: #f9f9f9"); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(30); - layout->setContentsMargins(20, 20, 20, 10); - - input_ = new TextField(this); - input_->setTextColor("#555459"); - input_->setLabel("Homeserver's domain"); - input_->setInkColor("#333333"); - input_->setBackgroundColor("#f9f9f9"); - input_->setPlaceholderText("e.g matrix.domain.org:3434"); - - submit_button_ = new FlatButton("OK", this); - submit_button_->setBackgroundColor("black"); - submit_button_->setForegroundColor("black"); - submit_button_->setCursor(QCursor(Qt::PointingHandCursor)); - submit_button_->setFontSize(15); - submit_button_->setFixedHeight(50); - submit_button_->setCornerRadius(3); - - auto label = new QLabel("Advanced Settings", this); - label->setStyleSheet("color: #333333"); - - layout->addWidget(label); - layout->addWidget(input_); - layout->addWidget(submit_button_); - - setLayout(layout); - - connect(input_, SIGNAL(returnPressed()), submit_button_, SIGNAL(clicked())); - connect(submit_button_, &QPushButton::clicked, [=]() { - emit closing(input_->text()); - }); -} diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index ebecb05a..11ac61c7 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc
@@ -30,6 +30,7 @@ #include "MatrixClient.h" #include "Profile.h" #include "Register.h" +#include "Versions.h" MatrixClient::MatrixClient(QString server, QObject *parent) : QNetworkAccessManager(parent) @@ -57,12 +58,34 @@ void MatrixClient::onVersionsResponse(QNetworkReply *reply) { reply->deleteLater(); - qDebug() << "Handling the versions response"; + int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status_code == 404) { + emit versionError("Versions endpoint was not found on the server. Possibly not a Matrix server"); + return; + } + + if (status_code >= 400) { + qWarning() << "API version error: " << reply->errorString(); + emit versionError("An unknown error occured. Please try again."); + return; + } auto data = reply->readAll(); auto json = QJsonDocument::fromJson(data); - qDebug() << json; + VersionsResponse response; + + try { + response.deserialize(json); + if (!response.isVersionSupported(0, 2, 0)) + emit versionError("Server does not support required API version."); + else + emit versionSuccess(); + } catch (DeserializationException &e) { + qWarning() << "Malformed JSON response" << e.what(); + emit versionError("Malformed response. Possibly not a Matrix server"); + } } void MatrixClient::onLoginResponse(QNetworkReply *reply) diff --git a/src/Versions.cc b/src/Versions.cc new file mode 100644
index 00000000..48895645 --- /dev/null +++ b/src/Versions.cc
@@ -0,0 +1,62 @@ +/* + * nheko Copyright (C) 2017 Jan Solanti <jhs@psonet.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> +#include <QRegExp> + +#include "Deserializable.h" +#include "Versions.h" + +void VersionsResponse::deserialize(const QJsonDocument &data) +{ + if (!data.isObject()) + throw DeserializationException("Versions response is not a JSON object"); + + QJsonObject object = data.object(); + + if (object.value("versions") == QJsonValue::Undefined) + throw DeserializationException("Versions: missing version list"); + + auto versions = object.value("versions").toArray(); + for (auto const &elem: versions) { + QString str = elem.toString(); + QRegExp rx("r(\\d+)\\.(\\d+)\\.(\\d+)"); + + if (rx.indexIn(str) == -1) + throw DeserializationException("Invalid version string in versions response"); + + struct Version_ v; + v.major_ = rx.cap(1).toUInt(); + v.minor_ = rx.cap(2).toUInt(); + v.patch_ = rx.cap(3).toUInt(); + + supported_versions_.push_back(v); + } +} + +bool VersionsResponse::isVersionSupported(unsigned int major, unsigned int minor, unsigned int patch) +{ + for (auto &v: supported_versions_) { + if (v.major_ == major && v.minor_ == minor && v.patch_ >= patch) + return true; + } + + return false; +}