Merge master into reactions
This commit is contained in:
commit
6d2789f4d5
38 changed files with 6320 additions and 467 deletions
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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())));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
53
src/SSOHandler.cpp
Normal file
53
src/SSOHandler.cpp
Normal file
|
|
@ -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";
|
||||
}
|
||||
24
src/SSOHandler.h
Normal file
24
src/SSOHandler.h
Normal file
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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://", "<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});
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ public:
|
|||
Id,
|
||||
State,
|
||||
IsEncrypted,
|
||||
IsRoomEncrypted,
|
||||
ReplyTo,
|
||||
Reactions,
|
||||
RoomId,
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue