diff --git a/.travis.yml b/.travis.yml
index bea561f1..ac3512bd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -33,6 +33,7 @@ matrix:
- ninja
- openssl
- qt5
+ update: true # workaround for broken travis homebrew
- os: linux
compiler: gcc-7
env:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index eebac250..66e9dcd2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json
index 45007e86..ddc1f1a0 100644
--- a/io.github.NhekoReborn.Nheko.json
+++ b/io.github.NhekoReborn.Nheko.json
@@ -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"
}
]
},
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 17a04a41..fb64f0fe 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -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_);
+}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 59f29d50..e3e04698 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -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();
diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp
index 2688e9a9..39a69a34 100644
--- a/src/RegisterPage.cpp
+++ b/src/RegisterPage.cpp
@@ -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();
-
- auto captchaDialog =
- new dialogs::ReCaptcha(QString::fromStdString(session), this);
-
- connect(captchaDialog,
- &dialogs::ReCaptcha::confirmation,
- this,
- [this, user, pass, session, captchaDialog]() {
- captchaDialog->close();
- captchaDialog->deleteLater();
-
- emit registering();
-
- 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;
- }
-
- http::client()->set_user(res.user_id);
- http::client()->set_access_token(res.access_token);
-
- emit registerOk();
- });
- });
-
- QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); });
+ [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;
+
+ nhlog::ui()->info("Completed stages: {}", completed_stages.size());
+
+ 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());
+
+ if (flows.empty()) {
+ nhlog::net()->error("No available registration flows!");
+ emit registerErrorCb(tr("No supported registration flows!"));
+ return;
+ }
+
+ auto current_stage = flows.front().stages.at(completed_stages.size());
+
+ if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
+ auto captchaDialog =
+ new dialogs::ReCaptcha(QString::fromStdString(session), this);
+
+ connect(captchaDialog,
+ &dialogs::ReCaptcha::confirmation,
+ this,
+ [this, user, pass, session, captchaDialog]() {
+ captchaDialog->close();
+ captchaDialog->deleteLater();
+
+ 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;
- }
-
- emit registrationFlow(username, password, res.session);
- });
+ 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, 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();
diff --git a/src/RegisterPage.h b/src/RegisterPage.h
index 96a5b3ca..ebc24bb1 100644
--- a/src/RegisterPage.h
+++ b/src/RegisterPage.h
@@ -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();
diff --git a/src/dialogs/FallbackAuth.cpp b/src/dialogs/FallbackAuth.cpp
new file mode 100644
index 00000000..a0633c1e
--- /dev/null
+++ b/src/dialogs/FallbackAuth.cpp
@@ -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();
+ });
+}
diff --git a/src/dialogs/FallbackAuth.h b/src/dialogs/FallbackAuth.h
new file mode 100644
index 00000000..245fa03e
--- /dev/null
+++ b/src/dialogs/FallbackAuth.h
@@ -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
diff --git a/src/dialogs/ReCaptcha.cpp b/src/dialogs/ReCaptcha.cpp
index 7849aa4f..21dc8c77 100644
--- a/src/dialogs/ReCaptcha.cpp
+++ b/src/dialogs/ReCaptcha.cpp
@@ -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();
+ });
}
diff --git a/src/dialogs/ReCaptcha.h b/src/dialogs/ReCaptcha.h
index f8407640..88ff3722 100644
--- a/src/dialogs/ReCaptcha.h
+++ b/src/dialogs/ReCaptcha.h
@@ -15,6 +15,7 @@ public:
signals:
void confirmation();
+ void cancel();
private:
QPushButton *openCaptchaBtn_;
|