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
- openssl
- qt5
update: true # workaround for broken travis homebrew
- os: linux
compiler: gcc-7
env:

View file

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

View file

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

View file

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

View file

@ -21,6 +21,8 @@
#include <QLayout>
#include <memory>
#include <mtx/user_interactive.hpp>
class FlatButton;
class RaisedButton;
class TextField;
@ -43,7 +45,10 @@ signals:
void registerErrorCb(const QString &msg);
void registrationFlow(const std::string &user,
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:
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 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:
void confirmation();
void cancel();
private:
QPushButton *openCaptchaBtn_;