Fix Registration

fixes #97
fixes #51
This commit is contained in:
Nicolas Werner 2020-02-23 11:42:29 +01:00
parent fee00746c8
commit 3ef0d9db3c
11 changed files with 308 additions and 90 deletions

View File

@ -33,6 +33,7 @@ matrix:
- ninja - ninja
- openssl - openssl
- qt5 - qt5
update: true # workaround for broken travis homebrew
- os: linux - os: linux
compiler: gcc-7 compiler: gcc-7
env: env:

View File

@ -228,17 +228,18 @@ configure_file(cmake/nheko.h config/nheko.h)
set(SRC_FILES set(SRC_FILES
# Dialogs # Dialogs
src/dialogs/CreateRoom.cpp src/dialogs/CreateRoom.cpp
src/dialogs/FallbackAuth.cpp
src/dialogs/ImageOverlay.cpp src/dialogs/ImageOverlay.cpp
src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/InviteUsers.cpp src/dialogs/InviteUsers.cpp
src/dialogs/JoinRoom.cpp src/dialogs/JoinRoom.cpp
src/dialogs/MemberList.cpp
src/dialogs/LeaveRoom.cpp src/dialogs/LeaveRoom.cpp
src/dialogs/Logout.cpp src/dialogs/Logout.cpp
src/dialogs/UserProfile.cpp src/dialogs/MemberList.cpp
src/dialogs/ReadReceipts.cpp src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/ReCaptcha.cpp src/dialogs/ReCaptcha.cpp
src/dialogs/ReadReceipts.cpp
src/dialogs/RoomSettings.cpp src/dialogs/RoomSettings.cpp
src/dialogs/UserProfile.cpp
# Emoji # Emoji
src/emoji/Category.cpp src/emoji/Category.cpp
@ -333,7 +334,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG 5fbee216e640da45c05f25b1f84f03c88bca5910 GIT_TAG 5838f607d0e4c7595439249e8b9c213aec0667e9
) )
FetchContent_MakeAvailable(MatrixClient) FetchContent_MakeAvailable(MatrixClient)
else() else()
@ -424,18 +425,19 @@ feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAG
qt5_wrap_cpp(MOC_HEADERS qt5_wrap_cpp(MOC_HEADERS
# Dialogs # Dialogs
src/dialogs/CreateRoom.h src/dialogs/CreateRoom.h
src/dialogs/FallbackAuth.h
src/dialogs/ImageOverlay.h src/dialogs/ImageOverlay.h
src/dialogs/PreviewUploadOverlay.h
src/dialogs/InviteUsers.h src/dialogs/InviteUsers.h
src/dialogs/JoinRoom.h src/dialogs/JoinRoom.h
src/dialogs/MemberList.h
src/dialogs/LeaveRoom.h src/dialogs/LeaveRoom.h
src/dialogs/Logout.h src/dialogs/Logout.h
src/dialogs/UserProfile.h src/dialogs/MemberList.h
src/dialogs/PreviewUploadOverlay.h
src/dialogs/RawMessage.h src/dialogs/RawMessage.h
src/dialogs/ReadReceipts.h
src/dialogs/ReCaptcha.h src/dialogs/ReCaptcha.h
src/dialogs/ReadReceipts.h
src/dialogs/RoomSettings.h src/dialogs/RoomSettings.h
src/dialogs/UserProfile.h
# Emoji # Emoji
src/emoji/Category.h src/emoji/Category.h

View File

@ -147,9 +147,9 @@
"name": "mtxclient", "name": "mtxclient",
"sources": [ "sources": [
{ {
"sha256": "8cf5470570d2ed6affc0bbe0f4b6be9b0a2e2372e9f920b504126841bb73036f", "sha256": "df3fe7e3d59b5fc52ee3ca9a132a55fc325aa799c676e9e420073c56daeb1848",
"type": "archive", "type": "archive",
"url": "https://github.com/Nheko-Reborn/mtxclient/archive/5fbee216e640da45c05f25b1f84f03c88bca5910.tar.gz" "url": "https://github.com/Nheko-Reborn/mtxclient/archive/5838f607d0e4c7595439249e8b9c213aec0667e9.tar.gz"
} }
] ]
}, },

View File

@ -507,3 +507,33 @@ MainWindow::loadJdenticonPlugin()
nhlog::ui()->info("jdenticon plugin not found."); nhlog::ui()->info("jdenticon plugin not found.");
return false; return false;
} }
void
MainWindow::showWelcomePage()
{
removeOverlayProgressBar();
pageStack_->addWidget(welcome_page_);
pageStack_->setCurrentWidget(welcome_page_);
}
void
MainWindow::showLoginPage()
{
if (modal_)
modal_->hide();
pageStack_->addWidget(login_page_);
pageStack_->setCurrentWidget(login_page_);
}
void
MainWindow::showRegisterPage()
{
pageStack_->addWidget(register_page_);
pageStack_->setCurrentWidget(register_page_);
}
void
MainWindow::showUserSettingsPage()
{
pageStack_->setCurrentWidget(userSettingsPage_);
}

View File

@ -24,16 +24,17 @@
#include <QStackedWidget> #include <QStackedWidget>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include "LoginPage.h"
#include "RegisterPage.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "WelcomePage.h"
#include "dialogs/UserProfile.h" #include "dialogs/UserProfile.h"
#include "ui/OverlayModal.h" #include "ui/OverlayModal.h"
#include "jdenticoninterface.h" #include "jdenticoninterface.h"
class ChatPage; class ChatPage;
class RegisterPage;
class LoginPage;
class WelcomePage;
class LoadingIndicator; class LoadingIndicator;
class OverlayModal; class OverlayModal;
class SnackBar; class SnackBar;
@ -97,32 +98,16 @@ private slots:
void iconActivated(QSystemTrayIcon::ActivationReason reason); void iconActivated(QSystemTrayIcon::ActivationReason reason);
//! Show the welcome page in the main window. //! Show the welcome page in the main window.
void showWelcomePage() void showWelcomePage();
{
removeOverlayProgressBar();
pageStack_->addWidget(welcome_page_);
pageStack_->setCurrentWidget(welcome_page_);
}
//! Show the login page in the main window. //! Show the login page in the main window.
void showLoginPage() void showLoginPage();
{
if (modal_)
modal_->hide();
pageStack_->addWidget(login_page_);
pageStack_->setCurrentWidget(login_page_);
}
//! Show the register page in the main window. //! Show the register page in the main window.
void showRegisterPage() void showRegisterPage();
{
pageStack_->addWidget(register_page_);
pageStack_->setCurrentWidget(register_page_);
}
//! Show user settings page. //! Show user settings page.
void showUserSettingsPage() { pageStack_->setCurrentWidget(userSettingsPage_); } void showUserSettingsPage();
//! Show the chat page and start communicating with the given access token. //! Show the chat page and start communicating with the given access token.
void showChatPage(); void showChatPage();

View File

@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QMetaType>
#include <QPainter> #include <QPainter>
#include <QStyleOption> #include <QStyleOption>
#include <QTimer> #include <QTimer>
@ -30,11 +31,17 @@
#include "ui/RaisedButton.h" #include "ui/RaisedButton.h"
#include "ui/TextField.h" #include "ui/TextField.h"
#include "dialogs/FallbackAuth.h"
#include "dialogs/ReCaptcha.h" #include "dialogs/ReCaptcha.h"
Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized)
Q_DECLARE_METATYPE(mtx::user_interactive::Auth)
RegisterPage::RegisterPage(QWidget *parent) RegisterPage::RegisterPage(QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
qRegisterMetaType<mtx::user_interactive::Unauthorized>();
qRegisterMetaType<mtx::user_interactive::Auth>();
top_layout_ = new QVBoxLayout(); top_layout_ = new QVBoxLayout();
back_layout_ = new QHBoxLayout(); back_layout_ = new QHBoxLayout();
@ -133,46 +140,139 @@ RegisterPage::RegisterPage(QWidget *parent)
this, this,
&RegisterPage::registrationFlow, &RegisterPage::registrationFlow,
this, this,
[this](const std::string &user, const std::string &pass, const std::string &session) { [this](const std::string &user,
emit errorOccurred(); const std::string &pass,
const mtx::user_interactive::Unauthorized &unauthorized) {
auto completed_stages = unauthorized.completed;
auto flows = unauthorized.flows;
auto session = unauthorized.session;
auto captchaDialog = nhlog::ui()->info("Completed stages: {}", completed_stages.size());
new dialogs::ReCaptcha(QString::fromStdString(session), this);
connect(captchaDialog, if (!completed_stages.empty())
&dialogs::ReCaptcha::confirmation, flows.erase(std::remove_if(
this, flows.begin(),
[this, user, pass, session, captchaDialog]() { flows.end(),
captchaDialog->close(); [completed_stages](auto flow) {
captchaDialog->deleteLater(); if (completed_stages.size() > flow.stages.size())
return true;
for (size_t f = 0; f < completed_stages.size(); f++)
if (completed_stages[f] != flow.stages[f])
return true;
return false;
}),
flows.end());
emit registering(); if (flows.empty()) {
nhlog::net()->error("No available registration flows!");
emit registerErrorCb(tr("No supported registration flows!"));
return;
}
http::client()->flow_response( auto current_stage = flows.front().stages.at(completed_stages.size());
user,
pass,
session,
"m.login.recaptcha",
[this](const mtx::responses::Register &res,
mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn(
"failed to retrieve registration flows: {}",
err->matrix_error.error);
emit errorOccurred();
emit registerErrorCb(QString::fromStdString(
err->matrix_error.error));
return;
}
http::client()->set_user(res.user_id); if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
http::client()->set_access_token(res.access_token); auto captchaDialog =
new dialogs::ReCaptcha(QString::fromStdString(session), this);
emit registerOk(); connect(captchaDialog,
}); &dialogs::ReCaptcha::confirmation,
}); this,
[this, user, pass, session, captchaDialog]() {
captchaDialog->close();
captchaDialog->deleteLater();
QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); emit registerAuth(
user,
pass,
mtx::user_interactive::Auth{
session, mtx::user_interactive::auth::Fallback{}});
});
connect(captchaDialog,
&dialogs::ReCaptcha::cancel,
this,
&RegisterPage::errorOccurred);
QTimer::singleShot(
1000, this, [captchaDialog]() { captchaDialog->show(); });
} else if (current_stage == mtx::user_interactive::auth_types::dummy) {
emit registerAuth(user,
pass,
mtx::user_interactive::Auth{
session, mtx::user_interactive::auth::Dummy{}});
} else {
// use fallback
auto dialog =
new dialogs::FallbackAuth(QString::fromStdString(current_stage),
QString::fromStdString(session),
this);
connect(dialog,
&dialogs::FallbackAuth::confirmation,
this,
[this, user, pass, session, dialog]() {
dialog->close();
dialog->deleteLater();
emit registerAuth(
user,
pass,
mtx::user_interactive::Auth{
session, mtx::user_interactive::auth::Fallback{}});
});
connect(dialog,
&dialogs::FallbackAuth::cancel,
this,
&RegisterPage::errorOccurred);
dialog->show();
}
});
connect(
this,
&RegisterPage::registerAuth,
this,
[this](const std::string &user,
const std::string &pass,
const mtx::user_interactive::Auth &auth) {
http::client()->registration(
user,
pass,
auth,
[this, user, pass](const mtx::responses::Register &res,
mtx::http::RequestErr err) {
if (!err) {
http::client()->set_user(res.user_id);
http::client()->set_access_token(res.access_token);
emit registerOk();
return;
}
// The server requires registration flows.
if (err->status_code == boost::beast::http::status::unauthorized) {
if (err->matrix_error.unauthorized.session.empty()) {
nhlog::net()->warn(
"failed to retrieve registration flows: ({}) "
"{}",
static_cast<int>(err->status_code),
err->matrix_error.error);
emit registerErrorCb(
QString::fromStdString(err->matrix_error.error));
return;
}
emit registrationFlow(
user, pass, err->matrix_error.unauthorized);
return;
}
nhlog::net()->warn("failed to register: status_code ({})",
static_cast<int>(err->status_code));
emit registerErrorCb(QString::fromStdString(err->matrix_error.error));
});
}); });
setLayout(top_layout_); setLayout(top_layout_);
@ -225,31 +325,27 @@ RegisterPage::onRegisterButtonClicked()
// The server requires registration flows. // The server requires registration flows.
if (err->status_code == boost::beast::http::status::unauthorized) { if (err->status_code == boost::beast::http::status::unauthorized) {
http::client()->flow_register( if (err->matrix_error.unauthorized.session.empty()) {
username, nhlog::net()->warn(
password, "failed to retrieve registration flows: ({}) "
[this, username, password]( "{}",
const mtx::responses::RegistrationFlows &res, static_cast<int>(err->status_code),
mtx::http::RequestErr err) { err->matrix_error.error);
if (res.session.empty() && err) { emit errorOccurred();
nhlog::net()->warn( emit registerErrorCb(
"failed to retrieve registration flows: ({}) " QString::fromStdString(err->matrix_error.error));
"{}", return;
static_cast<int>(err->status_code), }
err->matrix_error.error);
emit errorOccurred();
emit registerErrorCb(QString::fromStdString(
err->matrix_error.error));
return;
}
emit registrationFlow(username, password, res.session); emit registrationFlow(
}); username, password, err->matrix_error.unauthorized);
return; return;
} }
nhlog::net()->warn("failed to register: status_code ({})", nhlog::net()->warn(
static_cast<int>(err->status_code)); "failed to register: status_code ({}), matrix_error({})",
static_cast<int>(err->status_code),
err->matrix_error.error);
emit registerErrorCb(QString::fromStdString(err->matrix_error.error)); emit registerErrorCb(QString::fromStdString(err->matrix_error.error));
emit errorOccurred(); emit errorOccurred();

View File

@ -21,6 +21,8 @@
#include <QLayout> #include <QLayout>
#include <memory> #include <memory>
#include <mtx/user_interactive.hpp>
class FlatButton; class FlatButton;
class RaisedButton; class RaisedButton;
class TextField; class TextField;
@ -43,7 +45,10 @@ signals:
void registerErrorCb(const QString &msg); void registerErrorCb(const QString &msg);
void registrationFlow(const std::string &user, void registrationFlow(const std::string &user,
const std::string &pass, const std::string &pass,
const std::string &session); const mtx::user_interactive::Unauthorized &unauthorized);
void registerAuth(const std::string &user,
const std::string &pass,
const mtx::user_interactive::Auth &auth);
private slots: private slots:
void onBackButtonClicked(); void onBackButtonClicked();

View File

@ -0,0 +1,69 @@
#include <QDesktopServices>
#include <QLabel>
#include <QPushButton>
#include <QUrl>
#include <QVBoxLayout>
#include "dialogs/FallbackAuth.h"
#include "Config.h"
#include "MatrixClient.h"
using namespace dialogs;
FallbackAuth::FallbackAuth(const QString &authType, const QString &session, QWidget *parent)
: QWidget(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(8);
buttonLayout->setMargin(0);
openBtn_ = new QPushButton(tr("Open Fallback in Browser"), this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
confirmBtn_ = new QPushButton(tr("Confirm"), this);
confirmBtn_->setDefault(true);
buttonLayout->addStretch(1);
buttonLayout->addWidget(openBtn_);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
auto label = new QLabel(
tr("Open the fallback, follow the steps and confirm after completing them."), this);
label->setFont(font);
layout->addWidget(label);
layout->addLayout(buttonLayout);
connect(openBtn_, &QPushButton::clicked, [session, authType]() {
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/%4/"
"fallback/web?session=%3")
.arg(QString::fromStdString(http::client()->server()))
.arg(http::client()->port())
.arg(session)
.arg(authType);
QDesktopServices::openUrl(url);
});
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit confirmation();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel();
emit close();
});
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <QWidget>
class QPushButton;
class QLabel;
namespace dialogs {
class FallbackAuth : public QWidget
{
Q_OBJECT
public:
FallbackAuth(const QString &authType, const QString &session, QWidget *parent = nullptr);
signals:
void confirmation();
void cancel();
private:
QPushButton *openBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
};
} // dialogs

View File

@ -60,5 +60,8 @@ ReCaptcha::ReCaptcha(const QString &session, QWidget *parent)
emit confirmation(); emit confirmation();
emit close(); emit close();
}); });
connect(cancelBtn_, &QPushButton::clicked, this, &dialogs::ReCaptcha::close); connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel();
emit close();
});
} }

View File

@ -15,6 +15,7 @@ public:
signals: signals:
void confirmation(); void confirmation();
void cancel();
private: private:
QPushButton *openCaptchaBtn_; QPushButton *openCaptchaBtn_;