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 #include -#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 . */ +#include #include #include #include @@ -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(); + qRegisterMetaType(); 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(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(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(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(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(err->status_code)); + nhlog::net()->warn( + "failed to register: status_code ({}), matrix_error({})", + static_cast(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 #include +#include + 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 +#include +#include +#include +#include + +#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 + +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_;