summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-03-12 22:23:26 +0200
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-03-12 22:23:26 +0200
commit4659d0efc274f2a955b203ca5b00cf1dfc26d5fc (patch)
treede791031bf883a20932c878cd7a9a5d240dc3a49
parent39a8150faea00a6ba6202df3f0bc1a0bb4868646 (diff)
Implement user registration with reCAPTCHA
fixes #264
-rw-r--r--CMakeLists.txt3
-rw-r--r--README.md2
-rw-r--r--cmake/MatrixStructs.cmake2
-rw-r--r--include/LoginPage.h2
-rw-r--r--include/MainWindow.h1
-rw-r--r--include/MatrixClient.h7
-rw-r--r--include/Register.h52
-rw-r--r--include/RegisterPage.h9
-rw-r--r--include/dialogs/ReCaptcha.hpp28
-rw-r--r--resources/styles/nheko-dark.qss1
-rw-r--r--resources/styles/nheko.qss1
-rw-r--r--src/LoginPage.cc2
-rw-r--r--src/MainWindow.cc12
-rw-r--r--src/MatrixClient.cc67
-rw-r--r--src/Register.cc53
-rw-r--r--src/RegisterPage.cc30
-rw-r--r--src/dialogs/ReCaptcha.cpp75
17 files changed, 211 insertions, 136 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 225ad7a1..48c79496 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -144,6 +144,7 @@ set(SRC_FILES
src/dialogs/LeaveRoom.cc
src/dialogs/Logout.cc
src/dialogs/ReadReceipts.cc
+ src/dialogs/ReCaptcha.cpp
# Emoji
src/emoji/Category.cc
@@ -192,7 +193,6 @@ set(SRC_FILES
src/MainWindow.cc
src/MatrixClient.cc
src/QuickSwitcher.cc
- src/Register.cc
src/RegisterPage.cc
src/RoomInfoListItem.cc
src/RoomList.cc
@@ -238,6 +238,7 @@ qt5_wrap_cpp(MOC_HEADERS
include/dialogs/LeaveRoom.h
include/dialogs/Logout.h
include/dialogs/ReadReceipts.h
+ include/dialogs/ReCaptcha.hpp
# Emoji
include/emoji/Category.h
diff --git a/README.md b/README.md
index 1944a89c..d3c4519e 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@ nheko
[![Latest Release](https://img.shields.io/github/release/mujx/nheko.svg)](https://github.com/mujx/nheko/releases)
[![Chat on Matrix](https://img.shields.io/badge/chat-on%20matrix-blue.svg)](https://matrix.to/#/#nheko:matrix.org)
[![AUR: nheko-git](https://img.shields.io/badge/AUR-nheko--git-blue.svg)](https://aur.archlinux.org/packages/nheko-git)
+[![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko)
The motivation behind the project is to provide a native desktop app for [Matrix] that
feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IRC client.
@@ -14,6 +15,7 @@ feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IR
Most of the features you would expect from a chat application are missing right now
but we are getting close to a more feature complete client.
Specifically there is support for:
+- User registration.
- Creating, joining & leaving rooms.
- Sending & receiving invites.
- Sending & receiving files and emoji (inline widgets for images, audio and file messages).
diff --git a/cmake/MatrixStructs.cmake b/cmake/MatrixStructs.cmake
index 42071b61..61cf619e 100644
--- a/cmake/MatrixStructs.cmake
+++ b/cmake/MatrixStructs.cmake
@@ -23,7 +23,7 @@ ExternalProject_Add(
MatrixStructs
GIT_REPOSITORY https://github.com/mujx/matrix-structs
- GIT_TAG a1beea3b115f037e26c15f22ed911341b3893411
+ GIT_TAG 850100c0ac2b5a04720b2a1f09270749bf99f7dd
BUILD_IN_SOURCE 1
SOURCE_DIR ${MATRIX_STRUCTS_ROOT}
diff --git a/include/LoginPage.h b/include/LoginPage.h
index b2b40537..34835229 100644
--- a/include/LoginPage.h
+++ b/include/LoginPage.h
@@ -41,7 +41,7 @@ public:
signals:
void backButtonClicked();
void loggingIn();
- void errorOccured();
+ void errorOccurred();
protected:
void paintEvent(QPaintEvent *event) override;
diff --git a/include/MainWindow.h b/include/MainWindow.h
index e3a5e19d..d747f9b4 100644
--- a/include/MainWindow.h
+++ b/include/MainWindow.h
@@ -49,6 +49,7 @@ class InviteUsers;
class JoinRoom;
class LeaveRoom;
class Logout;
+class ReCaptcha;
}
class MainWindow : public QMainWindow
diff --git a/include/MatrixClient.h b/include/MatrixClient.h
index 46d946c7..69fa72bc 100644
--- a/include/MatrixClient.h
+++ b/include/MatrixClient.h
@@ -54,7 +54,8 @@ public:
void login(const QString &username, const QString &password) noexcept;
void registerUser(const QString &username,
const QString &password,
- const QString &server) noexcept;
+ const QString &server,
+ const QString &session = "") noexcept;
void versions() noexcept;
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
//! Download user's avatar.
@@ -109,6 +110,10 @@ public slots:
signals:
void loginError(const QString &error);
void registerError(const QString &error);
+ void registrationFlow(const QString &user,
+ const QString &pass,
+ const QString &server,
+ const QString &session);
void versionError(const QString &error);
void loggedOut();
diff --git a/include/Register.h b/include/Register.h
deleted file mode 100644
index ed903172..00000000
--- a/include/Register.h
+++ /dev/null
@@ -1,52 +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/>.
- */
-
-#pragma once
-
-#include "Deserializable.h"
-#include <QJsonDocument>
-
-class RegisterRequest
-{
-public:
- RegisterRequest();
- RegisterRequest(const QString &username, const QString &password);
-
- QByteArray serialize() noexcept;
-
- void setPassword(QString password) { password_ = password; };
- void setUser(QString username) { user_ = username; };
-
-private:
- QString user_;
- QString password_;
-};
-
-class RegisterResponse : public Deserializable
-{
-public:
- void deserialize(const QJsonDocument &data) override;
-
- QString getAccessToken() { return access_token_; };
- QString getHomeServer() { return home_server_; };
- QString getUserId() { return user_id_; };
-
-private:
- QString access_token_;
- QString home_server_;
- QString user_id_;
-};
diff --git a/include/RegisterPage.h b/include/RegisterPage.h
index b98e59de..32f2fcf2 100644
--- a/include/RegisterPage.h
+++ b/include/RegisterPage.h
@@ -20,12 +20,17 @@
#include <QLabel>
#include <QLayout>
#include <QSharedPointer>
+#include <memory>
class FlatButton;
class MatrixClient;
class RaisedButton;
class TextField;
+namespace dialogs {
+class ReCaptcha;
+}
+
class RegisterPage : public QWidget
{
Q_OBJECT
@@ -38,6 +43,8 @@ protected:
signals:
void backButtonClicked();
+ void errorOccurred();
+ void registering();
private slots:
void onBackButtonClicked();
@@ -70,4 +77,6 @@ private:
// Matrix client API provider.
QSharedPointer<MatrixClient> client_;
+ //! ReCaptcha dialog.
+ std::shared_ptr<dialogs::ReCaptcha> captchaDialog_;
};
diff --git a/include/dialogs/ReCaptcha.hpp b/include/dialogs/ReCaptcha.hpp
new file mode 100644
index 00000000..1eda40c7
--- /dev/null
+++ b/include/dialogs/ReCaptcha.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <QWidget>
+
+class FlatButton;
+class RaisedButton;
+
+namespace dialogs {
+
+class ReCaptcha : public QWidget
+{
+ Q_OBJECT
+
+public:
+ ReCaptcha(const QString &server, const QString &session, QWidget *parent = nullptr);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+signals:
+ void closing();
+
+private:
+ FlatButton *openCaptchaBtn_;
+ RaisedButton *confirmBtn_;
+ RaisedButton *cancelBtn_;
+};
+} // dialogs
diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss
index a9da770f..f70fd8e2 100644
--- a/resources/styles/nheko-dark.qss
+++ b/resources/styles/nheko-dark.qss
@@ -101,6 +101,7 @@ Avatar {
}
dialogs--Logout,
+dialogs--ReCaptcha,
dialogs--LeaveRoom,
dialogs--CreateRoom,
dialogs--InviteUsers,
diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss
index ec3537b9..7644b8bb 100644
--- a/resources/styles/nheko.qss
+++ b/resources/styles/nheko.qss
@@ -104,6 +104,7 @@ Avatar {
}
dialogs--Logout,
+dialogs--ReCaptcha,
dialogs--LeaveRoom,
dialogs--CreateRoom,
dialogs--InviteUsers,
diff --git a/src/LoginPage.cc b/src/LoginPage.cc
index 9a048a75..b8207226 100644
--- a/src/LoginPage.cc
+++ b/src/LoginPage.cc
@@ -144,7 +144,7 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent)
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(client_.data(), SIGNAL(loginError(QString)), this, SIGNAL(errorOccured()));
+ connect(client_.data(), SIGNAL(loginError(QString)), this, SIGNAL(errorOccurred()));
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()));
diff --git a/src/MainWindow.cc b/src/MainWindow.cc
index 4bdd7819..5d5cb598 100644
--- a/src/MainWindow.cc
+++ b/src/MainWindow.cc
@@ -85,7 +85,12 @@ MainWindow::MainWindow(QWidget *parent)
connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
connect(
- login_page_, &LoginPage::errorOccured, this, [this]() { removeOverlayProgressBar(); });
+ register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
+ connect(
+ login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
+ connect(register_page_, &RegisterPage::errorOccurred, this, [this]() {
+ removeOverlayProgressBar();
+ });
connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage()));
@@ -120,6 +125,11 @@ MainWindow::MainWindow(QWidget *parent)
this,
SLOT(showChatPage(QString, QString, QString)));
+ connect(client_.data(),
+ SIGNAL(registerSuccess(QString, QString, QString)),
+ this,
+ SLOT(showChatPage(QString, QString, QString)));
+
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index f4d2e66a..4a4fc67c 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -27,9 +27,10 @@
#include <QPixmap>
#include <QSettings>
#include <QUrlQuery>
+#include <mtx/errors.hpp>
+#include "Deserializable.h"
#include "MatrixClient.h"
-#include "Register.h"
MatrixClient::MatrixClient(QString server, QObject *parent)
: QNetworkAccessManager(parent)
@@ -193,50 +194,66 @@ MatrixClient::logout() noexcept
}
void
-MatrixClient::registerUser(const QString &user, const QString &pass, const QString &server) noexcept
+MatrixClient::registerUser(const QString &user,
+ const QString &pass,
+ const QString &server,
+ const QString &session) noexcept
{
setServer(server);
- QUrlQuery query;
- query.addQueryItem("kind", "user");
-
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/register");
- endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- RegisterRequest body(user, pass);
- auto reply = post(request, body.serialize());
+ QJsonObject body{{"username", user}, {"password", pass}};
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
+ // We trying to register using the response from the recaptcha.
+ if (!session.isEmpty())
+ body = QJsonObject{
+ {"username", user},
+ {"password", pass},
+ {"auth", QJsonObject{{"type", "m.login.recaptcha"}, {"session", session}}}};
+
+ auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
+
+ connect(reply, &QNetworkReply::finished, this, [this, reply, user, pass, server]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
auto data = reply->readAll();
- auto json = QJsonDocument::fromJson(data);
- if (status == 0 || status >= 400) {
- if (json.isObject() && json.object().contains("error"))
- emit registerError(json.object().value("error").toString());
- else
- emit registerError(reply->errorString());
+ // Try to parse a regular register response.
+ try {
+ mtx::responses::Register res = nlohmann::json::parse(data);
+ emit registerSuccess(QString::fromStdString(res.user_id.toString()),
+ QString::fromStdString(res.user_id.hostname()),
+ QString::fromStdString(res.access_token));
+ } catch (const std::exception &e) {
+ qWarning() << "Register" << e.what();
+ }
+ // Check if the server requires a registration flow.
+ try {
+ mtx::responses::RegistrationFlows res = nlohmann::json::parse(data);
+ emit registrationFlow(
+ user, pass, server, QString::fromStdString(res.session));
return;
+ } catch (const std::exception &) {
}
- RegisterResponse response;
-
- try {
- response.deserialize(json);
- emit registerSuccess(response.getUserId(),
- response.getHomeServer(),
- response.getAccessToken());
- } catch (DeserializationException &e) {
- qWarning() << "Register" << e.what();
- emit registerError("Received malformed response.");
+ // We encountered an unknown error.
+ if (status == 0 || status >= 400) {
+ try {
+ mtx::errors::Error res = nlohmann::json::parse(data);
+ emit registerError(QString::fromStdString(res.error));
+ return;
+ } catch (const std::exception &) {
+ }
+
+ emit registerError(reply->errorString());
}
});
}
diff --git a/src/Register.cc b/src/Register.cc
deleted file mode 100644
index d63f9229..00000000
--- a/src/Register.cc
+++ /dev/null
@@ -1,53 +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 "Register.h"
-
-RegisterRequest::RegisterRequest(const QString &username, const QString &password)
- : user_(username)
- , password_(password)
-{}
-
-QByteArray
-RegisterRequest::serialize() noexcept
-{
- QJsonObject body{{"username", user_}, {"password", password_}};
-
- return QJsonDocument(body).toJson(QJsonDocument::Compact);
-}
-
-void
-RegisterResponse::deserialize(const QJsonDocument &data)
-{
- if (!data.isObject())
- throw DeserializationException("Response is not a JSON object");
-
- QJsonObject object = data.object();
-
- if (!object.contains("access_token"))
- throw DeserializationException("Missing access_token param");
-
- if (!object.contains("home_server"))
- throw DeserializationException("Missing home_server param");
-
- if (!object.contains("user_id"))
- throw DeserializationException("Missing user_id param");
-
- access_token_ = object.value("access_token").toString();
- home_server_ = object.value("home_server").toString();
- user_id_ = object.value("user_id").toString();
-}
diff --git a/src/RegisterPage.cc b/src/RegisterPage.cc
index 044d2fcf..c0fa11a6 100644
--- a/src/RegisterPage.cc
+++ b/src/RegisterPage.cc
@@ -16,14 +16,18 @@
*/
#include <QStyleOption>
+#include <QTimer>
#include "Config.h"
#include "FlatButton.h"
+#include "MainWindow.h"
#include "MatrixClient.h"
#include "RaisedButton.h"
#include "RegisterPage.h"
#include "TextField.h"
+#include "dialogs/ReCaptcha.hpp"
+
RegisterPage::RegisterPage(QSharedPointer<MatrixClient> client, QWidget *parent)
: QWidget(parent)
, client_(client)
@@ -126,6 +130,30 @@ RegisterPage::RegisterPage(QSharedPointer<MatrixClient> client, QWidget *parent)
SIGNAL(registerError(const QString &)),
this,
SLOT(registerError(const QString &)));
+ connect(client_.data(),
+ &MatrixClient::registrationFlow,
+ this,
+ [this](const QString &user,
+ const QString &pass,
+ const QString &server,
+ const QString &session) {
+ emit errorOccurred();
+
+ if (!captchaDialog_) {
+ captchaDialog_ =
+ std::make_shared<dialogs::ReCaptcha>(server, session, this);
+ connect(captchaDialog_.get(),
+ &dialogs::ReCaptcha::closing,
+ this,
+ [this, user, pass, server, session]() {
+ captchaDialog_->close();
+ emit registering();
+ client_->registerUser(user, pass, server, session);
+ });
+ }
+
+ QTimer::singleShot(1000, this, [this]() { captchaDialog_->show(); });
+ });
setLayout(top_layout_);
}
@@ -139,6 +167,7 @@ RegisterPage::onBackButtonClicked()
void
RegisterPage::registerError(const QString &msg)
{
+ emit errorOccurred();
error_label_->setText(msg);
}
@@ -161,6 +190,7 @@ RegisterPage::onRegisterButtonClicked()
QString server = server_input_->text();
client_->registerUser(username, password, server);
+ emit registering();
}
}
diff --git a/src/dialogs/ReCaptcha.cpp b/src/dialogs/ReCaptcha.cpp
new file mode 100644
index 00000000..eb69a6a2
--- /dev/null
+++ b/src/dialogs/ReCaptcha.cpp
@@ -0,0 +1,75 @@
+#include <QDesktopServices>
+#include <QLabel>
+#include <QPaintEvent>
+#include <QStyleOption>
+#include <QVBoxLayout>
+
+#include "Config.h"
+#include "FlatButton.h"
+#include "RaisedButton.h"
+#include "Theme.h"
+
+#include "dialogs/ReCaptcha.hpp"
+
+using namespace dialogs;
+
+ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *parent)
+ : QWidget(parent)
+{
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(30);
+ layout->setMargin(20);
+
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(8);
+ buttonLayout->setMargin(0);
+
+ openCaptchaBtn_ = new FlatButton("OPEN reCAPTCHA", this);
+ openCaptchaBtn_->setFontSize(conf::btn::fontSize);
+
+ confirmBtn_ = new RaisedButton(tr("CONFIRM"), this);
+ confirmBtn_->setFontSize(conf::btn::fontSize);
+
+ cancelBtn_ = new RaisedButton(tr("CANCEL"), this);
+ cancelBtn_->setFontSize(conf::btn::fontSize);
+
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(openCaptchaBtn_);
+ buttonLayout->addWidget(confirmBtn_);
+ buttonLayout->addWidget(cancelBtn_);
+
+ QFont font;
+ font.setPixelSize(conf::headerFontSize);
+
+ auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
+ label->setFont(font);
+
+ layout->addWidget(label);
+ layout->addLayout(buttonLayout);
+
+ connect(openCaptchaBtn_, &QPushButton::clicked, [server, session, this]() {
+ const auto url =
+ QString(
+ "https://%1/_matrix/client/r0/auth/m.login.recaptcha/fallback/web?session=%2")
+ .arg(server)
+ .arg(session);
+
+ QDesktopServices::openUrl(url);
+ });
+
+ connect(confirmBtn_, &QPushButton::clicked, this, &dialogs::ReCaptcha::closing);
+ connect(cancelBtn_, &QPushButton::clicked, this, &dialogs::ReCaptcha::close);
+}
+
+void
+ReCaptcha::paintEvent(QPaintEvent *)
+{
+ QStyleOption opt;
+ opt.init(this);
+ QPainter p(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+}