summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorJoseph Donofry <joedonofry@gmail.com>2020-05-14 20:53:01 -0400
committerJoseph Donofry <joedonofry@gmail.com>2020-05-14 20:53:01 -0400
commit6d2789f4d5ffcd4e3e6ad8dfaae6925b0bf7d6ae (patch)
tree2ce43f344f002d2747f5f68b9d87b81873ed87ce /src
parentUpdate emoji picker and translations (diff)
parentMerge branch 'master' of ssh://github.com/Nheko-Reborn/nheko (diff)
downloadnheko-6d2789f4d5ffcd4e3e6ad8dfaae6925b0bf7d6ae.tar.xz
Merge master into reactions
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp2
-rw-r--r--src/ChatPage.cpp12
-rw-r--r--src/LoginPage.cpp127
-rw-r--r--src/LoginPage.h11
-rw-r--r--src/RegisterPage.cpp11
-rw-r--r--src/SSOHandler.cpp53
-rw-r--r--src/SSOHandler.h24
-rw-r--r--src/timeline/TimelineModel.cpp11
-rw-r--r--src/timeline/TimelineModel.h1
-rw-r--r--src/ui/TextField.cpp5
10 files changed, 224 insertions, 33 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp

index 8cfc4b55..3a388bb9 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp
@@ -1338,7 +1338,7 @@ Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id) auto time = QDateTime::fromMSecsSinceEpoch(ts); fallbackDesc = DescInfo{QString::fromStdString(obj["event"]["event_id"]), local_user, - tr("You joined this room"), + tr("You joined this room."), utils::descriptiveTime(time), ts, time}; diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 19339e59..bfefd7bb 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp
@@ -666,7 +666,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) "This can have different reasons. Please open an " "issue and try to use an older version in the mean " "time. Alternatively you can try deleting the cache " - "manually")); + "manually.")); QCoreApplication::quit(); } loadStateFromCache(); @@ -994,8 +994,12 @@ ChatPage::trySync() const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); const int status_code = static_cast<int>(err->status_code); - if (http::is_logged_in() && err->matrix_error.errcode == - mtx::errors::ErrorCode::M_UNKNOWN_TOKEN) { + if ((http::is_logged_in() && + (err->matrix_error.errcode == + mtx::errors::ErrorCode::M_UNKNOWN_TOKEN || + err->matrix_error.errcode == + mtx::errors::ErrorCode::M_MISSING_TOKEN)) || + !http::is_logged_in()) { emit dropToLoginPageCb(msg); return; } @@ -1086,7 +1090,7 @@ ChatPage::createRoom(const mtx::requests::CreateRoom &req) } emit showNotification( - tr("Room %1 created").arg(QString::fromStdString(res.room_id.to_string()))); + tr("Room %1 created.").arg(QString::fromStdString(res.room_id.to_string()))); }); } diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp
index 20fb3888..bb329699 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp
@@ -15,28 +15,35 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <QDesktopServices> #include <QPainter> #include <QStyleOption> #include <mtx/identifiers.hpp> +#include <mtx/requests.hpp> #include <mtx/responses/login.hpp> #include "Config.h" #include "Logging.h" #include "LoginPage.h" #include "MatrixClient.h" +#include "SSOHandler.h" #include "ui/FlatButton.h" #include "ui/LoadingIndicator.h" #include "ui/OverlayModal.h" #include "ui/RaisedButton.h" #include "ui/TextField.h" +Q_DECLARE_METATYPE(LoginPage::LoginMethod) + using namespace mtx::identifiers; LoginPage::LoginPage(QWidget *parent) : QWidget(parent) , inferredServerAddress_() { + qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod"); + top_layout_ = new QVBoxLayout(); top_bar_layout_ = new QHBoxLayout(); @@ -81,6 +88,14 @@ LoginPage::LoginPage(QWidget *parent) matrixid_input_ = new TextField(this); matrixid_input_->setLabel(tr("Matrix ID")); matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); + matrixid_input_->setToolTip( + tr("Your login name. A mxid should start with @ followed by the user id. After the user " + "id you need to include your server name after a :.\nYou can also put your homeserver " + "address there, if your server doesn't support .well-known lookup.\nExample: " + "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a " + "field to enter the server manually.")); + matrixid_input_->setValidator( + new QRegularExpressionValidator(QRegularExpression("@.+?:.{3,}"), this)); spinner_ = new LoadingIndicator(this); spinner_->setFixedHeight(40); @@ -97,13 +112,19 @@ LoginPage::LoginPage(QWidget *parent) password_input_ = new TextField(this); password_input_->setLabel(tr("Password")); password_input_->setEchoMode(QLineEdit::Password); + password_input_->setToolTip("Your password."); deviceName_ = new TextField(this); deviceName_->setLabel(tr("Device name")); + deviceName_->setToolTip( + tr("A name for this device, which will be shown to others, when verifying your devices. " + "If none is provided, a random string is used for privacy purposes.")); serverInput_ = new TextField(this); serverInput_->setLabel("Homeserver address"); serverInput_->setPlaceholderText("matrix.org"); + serverInput_->setToolTip(tr("The address that can be used to contact you homeservers " + "client API.\nExample: https://server.my:8787")); serverInput_->hide(); serverLayout_ = new QHBoxLayout(); @@ -212,7 +233,8 @@ LoginPage::onMatrixIdEntered() emit versionErrorCb(tr("Autodiscovery failed. Unknown error when " "requesting .well-known.")); nhlog::net()->error("Autodiscovery failed. Unknown error when " - "requesting .well-known."); + "requesting .well-known. {}", + err->error_code.message()); return; } @@ -249,7 +271,16 @@ LoginPage::checkHomeserverVersion() return; } - emit versionOkCb(); + http::client()->get_login( + [this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) { + if (err || flows.flows.empty()) + emit versionOkCb(LoginMethod::Password); + + if (flows.flows[0].type == mtx::user_interactive::auth_types::sso) + emit versionOkCb(LoginMethod::SSO); + else + emit versionOkCb(LoginMethod::Password); + }); }); } @@ -280,12 +311,22 @@ LoginPage::versionError(const QString &error) } void -LoginPage::versionOk() +LoginPage::versionOk(LoginMethod loginMethod_) { + this->loginMethod = loginMethod_; + serverLayout_->removeWidget(spinner_); matrixidLayout_->removeWidget(spinner_); spinner_->stop(); + if (loginMethod == LoginMethod::SSO) { + password_input_->hide(); + login_button_->setText(tr("SSO LOGIN")); + } else { + password_input_->show(); + login_button_->setText(tr("LOGIN")); + } + if (serverInput_->isVisible()) serverInput_->hide(); } @@ -303,29 +344,68 @@ LoginPage::onLoginButtonClicked() return loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org"); } - if (password_input_->text().isEmpty()) - return loginError(tr("Empty password")); + if (loginMethod == LoginMethod::Password) { + if (password_input_->text().isEmpty()) + return loginError(tr("Empty password")); - http::client()->login( - user.localpart(), - password_input_->text().toStdString(), - deviceName_->text().trimmed().isEmpty() ? initialDeviceName() - : deviceName_->text().toStdString(), - [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { - if (err) { - emit loginError(QString::fromStdString(err->matrix_error.error)); - emit errorOccurred(); - return; - } + http::client()->login( + user.localpart(), + password_input_->text().toStdString(), + deviceName_->text().trimmed().isEmpty() ? initialDeviceName() + : deviceName_->text().toStdString(), + [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { + if (err) { + emit loginError(QString::fromStdString(err->matrix_error.error)); + emit errorOccurred(); + return; + } - if (res.well_known) { - http::client()->set_server(res.well_known->homeserver.base_url); - nhlog::net()->info("Login requested to user server: " + - res.well_known->homeserver.base_url); - } + if (res.well_known) { + http::client()->set_server(res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } - emit loginOk(res); - }); + emit loginOk(res); + }); + } else { + auto sso = new SSOHandler(); + connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) { + mtx::requests::Login req{}; + req.token = token; + req.type = mtx::user_interactive::auth_types::token; + req.device_id = deviceName_->text().trimmed().isEmpty() + ? initialDeviceName() + : deviceName_->text().toStdString(); + http::client()->login( + req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { + if (err) { + emit loginError( + QString::fromStdString(err->matrix_error.error)); + emit errorOccurred(); + return; + } + + if (res.well_known) { + http::client()->set_server( + res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } + + emit loginOk(res); + }); + sso->deleteLater(); + }); + connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() { + emit loginError(tr("SSO login failed")); + emit errorOccurred(); + sso->deleteLater(); + }); + + QDesktopServices::openUrl( + QString::fromStdString(http::client()->login_sso_redirect(sso->url()))); + } emit loggingIn(); } @@ -335,6 +415,7 @@ LoginPage::reset() { matrixid_input_->clear(); password_input_->clear(); + password_input_->show(); serverInput_->clear(); spinner_->stop(); diff --git a/src/LoginPage.h b/src/LoginPage.h
index 4b84abfc..8a402aea 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h
@@ -38,6 +38,12 @@ class LoginPage : public QWidget Q_OBJECT public: + enum class LoginMethod + { + Password, + SSO, + }; + LoginPage(QWidget *parent = nullptr); void reset(); @@ -50,7 +56,7 @@ signals: //! Used to trigger the corresponding slot outside of the main thread. void versionErrorCb(const QString &err); void loginErrorCb(const QString &err); - void versionOkCb(); + void versionOkCb(LoginPage::LoginMethod method); void loginOk(const mtx::responses::Login &res); @@ -77,7 +83,7 @@ private slots: // Callback for errors produced during server probing void versionError(const QString &error_message); // Callback for successful server probing - void versionOk(); + void versionOk(LoginPage::LoginMethod method); private: bool isMatrixIdValid(); @@ -123,4 +129,5 @@ private: TextField *password_input_; TextField *deviceName_; TextField *serverInput_; + LoginMethod loginMethod = LoginMethod::Password; }; diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp
index 2833381d..03e9ab34 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp
@@ -85,17 +85,26 @@ RegisterPage::RegisterPage(QWidget *parent) username_input_ = new TextField(); username_input_->setLabel(tr("Username")); + username_input_->setValidator( + new QRegularExpressionValidator(QRegularExpression("[a-z0-9._=/-]+"), this)); + username_input_->setToolTip(tr("The username must not be empty, and must contain only the " + "characters a-z, 0-9, ., _, =, -, and /.")); password_input_ = new TextField(); password_input_->setLabel(tr("Password")); password_input_->setEchoMode(QLineEdit::Password); + password_input_->setToolTip(tr("Please choose a secure password. The exact requirements " + "for password strength may depend on your server.")); password_confirmation_ = new TextField(); password_confirmation_->setLabel(tr("Password confirmation")); password_confirmation_->setEchoMode(QLineEdit::Password); server_input_ = new TextField(); - server_input_->setLabel(tr("Home Server")); + server_input_->setLabel(tr("Homeserver")); + server_input_->setToolTip( + tr("A server that allows registration. Since matrix is decentralized, you need to first " + "find a server you can register on or host your own.")); form_layout_->addWidget(username_input_, Qt::AlignHCenter, nullptr); form_layout_->addWidget(password_input_, Qt::AlignHCenter, nullptr); diff --git a/src/SSOHandler.cpp b/src/SSOHandler.cpp new file mode 100644
index 00000000..cacbbaa9 --- /dev/null +++ b/src/SSOHandler.cpp
@@ -0,0 +1,53 @@ +#include "SSOHandler.h" + +#include <QTimer> + +#include <thread> + +#include "Logging.h" + +SSOHandler::SSOHandler(QObject *) +{ + QTimer::singleShot(120000, this, &SSOHandler::ssoFailed); + + using namespace httplib; + + svr.set_logger([](const Request &req, const Response &res) { + nhlog::net()->info("req: {}, res: {}", req.path, res.status); + }); + + svr.Get("/sso", [this](const Request &req, Response &res) { + if (req.has_param("loginToken")) { + auto val = req.get_param_value("loginToken"); + res.set_content("SSO success", "text/plain"); + emit ssoSuccess(val); + } else { + res.set_content("Missing loginToken for SSO login!", "text/plain"); + emit ssoFailed(); + } + }); + + std::thread t([this]() { + this->port = svr.bind_to_any_port("localhost"); + svr.listen_after_bind(); + }); + t.detach(); + + while (!svr.is_running()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +SSOHandler::~SSOHandler() +{ + svr.stop(); + while (svr.is_running()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +std::string +SSOHandler::url() const +{ + return "http://localhost:" + std::to_string(port) + "/sso"; +} diff --git a/src/SSOHandler.h b/src/SSOHandler.h new file mode 100644
index 00000000..325b7a58 --- /dev/null +++ b/src/SSOHandler.h
@@ -0,0 +1,24 @@ +#include "httplib.h" + +#include <QObject> +#include <string> + +class SSOHandler : public QObject +{ + Q_OBJECT + +public: + SSOHandler(QObject *parent = nullptr); + + ~SSOHandler(); + + std::string url() const; + +signals: + void ssoSuccess(std::string token); + void ssoFailed(); + +private: + httplib::Server svr; + int port = 0; +}; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 4068148b..f8334d9b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp
@@ -224,6 +224,7 @@ TimelineModel::roleNames() const {Id, "id"}, {State, "state"}, {IsEncrypted, "isEncrypted"}, + {IsRoomEncrypted, "isRoomEncrypted"}, {ReplyTo, "replyTo"}, {Reactions, "reactions"}, {RoomId, "roomId"}, @@ -294,6 +295,10 @@ TimelineModel::data(const QString &id, int role) const if (isReply) formattedBody_ = formattedBody_.remove(replyFallback); } + + formattedBody_.replace("<img src=\"mxc:&#47;&#47;", "<img src=\"image://mxcImage/"); + formattedBody_.replace("<img src=\"mxc://", "<img src=\"image://mxcImage/"); + return QVariant(utils::replaceEmoji( utils::linkifyMessage(utils::escapeBlacklistedHtml(formattedBody_)))); } @@ -346,6 +351,9 @@ TimelineModel::data(const QString &id, int role) const return std::holds_alternative< mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(events[id]); } + case IsRoomEncrypted: { + return cache::isRoomEncrypted(room_id_.toStdString()); + } case ReplyTo: return QVariant(QString::fromStdString(in_reply_to_event(event))); case Reactions: @@ -383,6 +391,7 @@ TimelineModel::data(const QString &id, int role) const m.insert(names[Id], data(id, static_cast<int>(Id))); m.insert(names[State], data(id, static_cast<int>(State))); m.insert(names[IsEncrypted], data(id, static_cast<int>(IsEncrypted))); + m.insert(names[IsRoomEncrypted], data(id, static_cast<int>(IsRoomEncrypted))); m.insert(names[ReplyTo], data(id, static_cast<int>(ReplyTo))); m.insert(names[RoomName], data(id, static_cast<int>(RoomName))); m.insert(names[RoomTopic], data(id, static_cast<int>(RoomTopic))); @@ -566,7 +575,7 @@ TimelineModel::updateLastMessage() room_id_, DescInfo{QString::fromStdString(mtx::accessors::event_id(event)), QString::fromStdString(http::client()->user_id().to_string()), - tr("You joined this room"), + tr("You joined this room."), utils::descriptiveTime(time), ts, time}); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 65f6b38c..ea7eaffd 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h
@@ -159,6 +159,7 @@ public: Id, State, IsEncrypted, + IsRoomEncrypted, ReplyTo, Reactions, RoomId, diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp
index 4bb7596a..27584693 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp
@@ -147,7 +147,10 @@ QColor TextField::underlineColor() const { if (!underline_color_.isValid()) { - return QPalette().color(QPalette::Highlight); + if (hasAcceptableInput() || !isModified()) + return QPalette().color(QPalette::Highlight); + else + return Qt::red; } return underline_color_;