Files
neochat/src/registration.cpp
Carl Schwan 64c5ad88f6 Remove system information from device display name
BUG: 496901


(cherry picked from commit 9d887ba3e7)

Co-authored-by: Tobias Fella <fella@posteo.de>
2024-12-02 16:07:56 +00:00

347 lines
11 KiB
C++

// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "registration.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <QThread>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "controller.h"
#include "login.h"
#include <KLocalizedString>
using namespace Quotient;
Registration::Registration()
{
auto server = new QTcpServer(this);
server->listen(QHostAddress("127.0.0.1"_ls), 20847);
connect(server, &QTcpServer::newConnection, this, [this, server]() {
auto conn = server->nextPendingConnection();
connect(conn, &QIODevice::readyRead, this, [this, conn]() {
auto code =
"HTTP/1.0 200\nContent-type: text/html\n\n<html><head><script src=\"https://www.google.com/recaptcha/api.js\" async defer></script></head><body style=\"background: #00000000\"><center><div class=\"g-recaptcha\" data-sitekey=\"%1\"></div></center></body></html>"_ls
.arg(m_recaptchaSiteKey);
conn->write(code.toLatin1().data(), code.length());
conn->close();
});
});
connect(this, &Registration::homeserverChanged, this, &Registration::testHomeserver);
connect(this, &Registration::usernameChanged, this, &Registration::testUsername);
}
void Registration::setRecaptchaResponse(const QString &recaptchaResponse)
{
m_recaptchaResponse = recaptchaResponse;
Q_EMIT recaptchaResponseChanged();
registerAccount();
}
QString Registration::recaptchaResponse() const
{
return m_recaptchaResponse;
}
void Registration::setRecaptchaSiteKey(const QString &recaptchaSiteKey)
{
m_recaptchaSiteKey = recaptchaSiteKey;
Q_EMIT recaptchaSiteKeyChanged();
}
QString Registration::recaptchaSiteKey() const
{
return m_recaptchaSiteKey;
}
void Registration::registerAccount()
{
setStatus(Working);
std::optional<QJsonObject> authData;
if (nextStep() == "m.login.recaptcha"_ls) {
authData = QJsonObject{
{"type"_ls, "m.login.recaptcha"_ls},
{"response"_ls, m_recaptchaResponse},
{"session"_ls, m_session},
};
} else if (nextStep() == "m.login.terms"_ls) {
authData = QJsonObject{
{"type"_ls, "m.login.terms"_ls},
{"session"_ls, m_session},
};
} else if (nextStep() == "m.login.email.identity"_ls) {
authData = QJsonObject{
{"type"_ls, "m.login.email.identity"_ls},
{"threepid_creds"_ls,
QJsonObject{
{"sid"_ls, m_sid},
{"client_secret"_ls, m_emailSecret},
}},
{"session"_ls, m_session},
};
}
auto job = m_connection->callApi<NeoChatRegisterJob>("user"_ls, authData, m_username, m_password, QString(), QString(), true);
connect(job, &BaseJob::result, this, [this, job]() {
if (job->status() == BaseJob::Success) {
setNextStep("loading"_ls);
auto connection = new NeoChatConnection(this);
auto matrixId = "@%1:%2"_ls.arg(m_username, m_homeserver);
connection->resolveServer(matrixId);
auto displayName = "NeoChat"_ls;
connection->loginWithPassword(matrixId, m_password, displayName);
connect(connection, &Connection::connected, this, [this, displayName, connection] {
AccountSettings account(connection->userId());
account.setKeepLoggedIn(true);
account.setHomeserver(connection->homeserver());
account.setDeviceId(connection->deviceId());
account.setDeviceName(displayName);
Controller::instance().saveAccessTokenToKeyChain(account.userId(), connection->accessToken());
account.sync();
Controller::instance().addConnection(connection);
Controller::instance().setActiveConnection(connection);
connect(
connection,
&Connection::syncDone,
this,
[]() {
Q_EMIT LoginHelper::instance().loaded();
},
Qt::SingleShotConnection);
m_connection = nullptr;
});
return;
}
const auto &data = job->jsonData();
m_session = data["session"_ls].toString();
const auto &params = data["params"_ls].toObject();
// I'm not motivated enough to figure out how we should handle the flow stuff, so:
// If there is a flow that requires e-mail, we use that, to make sure that the user can recover the account from a forgotten password.
// Otherwise, we're using the first flow.
auto selectedFlow = data["flows"_ls].toArray()[0].toObject()["stages"_ls].toArray();
for (const auto &flow : data["flows"_ls].toArray()) {
if (flow.toObject()["stages"_ls].toArray().contains("m.login.email.identity"_ls)) {
selectedFlow = flow.toObject()["stages"_ls].toArray();
}
}
setNextStep(selectedFlow[data["completed"_ls].toArray().size()].toString());
m_recaptchaSiteKey = params["m.login.recaptcha"_ls]["public_key"_ls].toString();
Q_EMIT recaptchaSiteKeyChanged();
m_terms.clear();
for (const auto &term : params["m.login.terms"_ls]["policies"_ls].toObject().keys()) {
QVariantMap termData;
termData["title"_ls] = params["m.login.terms"_ls]["policies"_ls][term]["en"_ls]["name"_ls].toString();
termData["url"_ls] = params["m.login.terms"_ls]["policies"_ls][term]["en"_ls]["url"_ls].toString();
m_terms += termData;
Q_EMIT termsChanged();
}
});
}
QString Registration::homeserver() const
{
return m_homeserver;
}
void Registration::setHomeserver(const QString &url)
{
m_homeserver = url;
Q_EMIT homeserverChanged();
}
void Registration::testHomeserver()
{
if (m_homeserver.isEmpty()) {
setStatus(NoServer);
return;
}
setStatus(TestingHomeserver);
if (m_connection) {
delete m_connection;
}
m_connection = new NeoChatConnection(this);
m_connection->resolveServer("@user:%1"_ls.arg(m_homeserver));
connect(
m_connection.data(),
&Connection::loginFlowsChanged,
this,
[this]() {
if (m_testServerJob) {
delete m_testServerJob;
}
m_testServerJob = m_connection->callApi<NeoChatRegisterJob>("user"_ls, std::nullopt, "user"_ls, QString(), QString(), QString(), false);
connect(m_testServerJob.data(), &BaseJob::finished, this, [this]() {
if (m_testServerJob->error() == BaseJob::StatusCode::ContentAccessError) {
setStatus(ServerNoRegistration);
return;
}
if (m_testServerJob->status().code != 106) {
setStatus(InvalidServer);
return;
}
if (!m_username.isEmpty()) {
setStatus(TestingUsername);
testUsername();
} else {
setStatus(NoUsername);
}
});
},
Qt::SingleShotConnection);
}
void Registration::setUsername(const QString &username)
{
m_username = username;
Q_EMIT usernameChanged();
}
QString Registration::username() const
{
return m_username;
}
void Registration::testUsername()
{
if (status() <= ServerNoRegistration) {
return;
}
setStatus(TestingUsername);
if (m_usernameJob) {
m_usernameJob->abandon();
}
if (m_username.isEmpty()) {
setStatus(NoUsername);
return;
}
m_usernameJob = m_connection->callApi<CheckUsernameAvailabilityJob>(m_username);
connect(m_usernameJob, &BaseJob::result, this, [this]() {
setStatus(m_usernameJob->error() == BaseJob::StatusCode::Success && *m_usernameJob->available() ? Ready : UsernameTaken);
});
}
QList<QVariantMap> Registration::terms() const
{
return m_terms;
}
QString Registration::password() const
{
return m_password;
}
void Registration::setPassword(const QString &password)
{
m_password = password;
Q_EMIT passwordChanged();
}
NeoChatRegisterJob::NeoChatRegisterJob(const QString &kind,
const std::optional<QJsonObject> &auth,
const QString &username,
const QString &password,
const QString &deviceId,
const QString &initialDeviceDisplayName,
std::optional<bool> inhibitLogin)
: BaseJob(HttpVerb::Post, "RegisterJob"_ls, QByteArrayLiteral("/_matrix/client/r0/register"), false)
{
QJsonObject _data;
if (auth) {
addParam<>(_data, "auth"_ls, auth);
}
addParam<>(_data, "username"_ls, username);
addParam<IfNotEmpty>(_data, "password"_ls, password);
addParam<IfNotEmpty>(_data, "device_id"_ls, deviceId);
addParam<IfNotEmpty>(_data, "initial_device_display_name"_ls, initialDeviceDisplayName);
addParam<IfNotEmpty>(_data, "inhibit_login"_ls, inhibitLogin);
addParam<IfNotEmpty>(_data, "kind"_ls, kind);
addParam<IfNotEmpty>(_data, "refresh_token"_ls, false);
setRequestData(_data);
}
QString Registration::email() const
{
return m_email;
}
void Registration::setEmail(const QString &email)
{
m_email = email;
Q_EMIT emailChanged();
}
QString Registration::nextStep() const
{
return m_nextStep;
}
void Registration::setNextStep(const QString &nextStep)
{
m_nextStep = nextStep;
Q_EMIT nextStepChanged();
}
Registration::Status Registration::status() const
{
return m_status;
}
QString Registration::statusString() const
{
switch (m_status) {
case NoServer:
return i18n("No server.");
case TestingHomeserver:
return i18n("Checking Server availability.");
case InvalidServer:
return i18n("This is not a valid server.");
case ServerNoRegistration:
return i18n("Registration for this server is disabled.");
case NoUsername:
return i18n("No username.");
case TestingUsername:
return i18n("Checking username availability.");
case UsernameTaken:
return i18n("This username is not available.");
case Ready:
return i18n("Continue");
case Working:
return i18n("Working");
}
return {};
}
void Registration::setStatus(Status status)
{
m_status = status;
Q_EMIT statusChanged();
}
void Registration::registerEmail()
{
m_emailSecret = QString::fromLatin1(QUuid::createUuid().toString().toLatin1().toBase64());
EmailValidationData data;
data.email = m_email;
data.clientSecret = m_emailSecret;
data.sendAttempt = 0;
auto job = m_connection->callApi<RequestTokenToRegisterEmailJob>(data);
connect(job, &BaseJob::finished, this, [this, job]() {
m_sid = job->jsonData()["sid"_ls].toString();
});
}
#include "moc_registration.cpp"